forked from imatix/restms
-
Notifications
You must be signed in to change notification settings - Fork 0
/
4-amqp9.txt
556 lines (424 loc) · 28.7 KB
/
4-amqp9.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
This document defines the AMQP9 profile for RestMS. The AMQP9 profile defines the behaviour of a set of feed, join, and pipe types that provide an AMQP/0.9.1-interoperable messaging model.
* Name: www.restms.org/spec:4/AMQP9
* Version: draft/4
* Editor: Pieter Hintjens <[email protected]>
* Contributors: Steve Vinoski <[email protected]>, Brad Clements <[email protected]>
++ License
Copyright (c) 2009 by the Editor and Contributors.
This Specification is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
This Specification is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program; if not, see <http://www.gnu.org/licenses>.
++ Change Process
This document is governed by the Digital Standard Organization's [http://www.digistan.org/spec:1/COSS Consensus-Oriented Specification System].
++ Goals and structure of this document
This document defines the AMQP9 profile for RestMS. The AMQP9 profile defines the behaviour of a set of feed, join, and pipe types that provide an AMQP-interoperable messaging model.
We cover these aspects of the AMQP9 profile:
* What the profile is designed for;
* What the resources look like and how they work;
* Guidelines for client applications;
* Guidelines for server implementers.
++ Language
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119[((bibcite rfc2119))].
++ Purpose and scope
The AMQP9 profile aims to provide a messaging model that interoperates with networks running the AMQP/0.9.1 protocol[((bibcite amqp))] or earlier versions. It implements the AMQP exchange types as feed types, and adds workload-distribution feeds that implement AMQP shared queues. A RestMS server that implements the AMQP9 profile can operate as the client of an AMQP server, or as a node on a federated AMQP network.
++ Syntax and semantics
The Defaults profile is formally called "4/AMQP9" to distinguish it from future variations.
It defines these feed types:
* The **fanout** feed type routes the message to all joins attached to the feed without filtering. The server MUST implement the fanout feed type.
* The **direct** feed type routes the message to //each// join attached to the feed that has an address pattern that is identical to the message address. One message can be routed to multiple pipes. The server MUST implement the direct feed type.
* The **topic** feed type routes the message to all joins attached to the feed using a topic matching algorithm. The topic matching algorithm works as follows: the message address is a string of topic names, separated by dots. The join address pattern is a string of topic names mixed with wild cards. "*" matches a single topic name and "#" matches zero or more topic names. The server SHOULD implement the topic feed type.
* The **headers** feed type routes the message to all joins attached to the feed using a header matching algorithm. The header matching algorithm works as follows: the join specifies a set of header elements. The message matches if it has identical values for the header elements specified in the join. The server MAY implement the headers feed type.
* The **rotator** feed type routes the message to at most one pipe, using a round-robin algorithm to choose the next join from those attached to the feed. Does not apply any filtering. If the feed has no attached joins, the server queues the message for an arbitrary period. The server SHOULD implement the rotator feed type.
* The **service** feed type works as the rotator feed, with the additional property of self-deleting when the the number of joins attached to it drops from 1 to zero. This means that the presence or absence of the feed indicates the availability of the "service" it represents. The server SHOULD implement the service feed type.
A //fanout//, //direct//, //topic//, or //headers// feed maps to an AMQP exchange of the same type. In these feeds route by doing some kind of matching between the address attribute of a message (a literal string), and the address attribute of a join (a pattern). If the feed is private, a hashed name is used for the exchange so that it cannot be discovered by AMQP applications, otherwise the feed name and exchange name are identical. When a RestMS client posts a message to the feed, the RestMS server publishes this message to the corresponding exchange, using as AMQP routing-key the //address// property of the message.
A //rotator// or //service// feed maps to an AMQP shared queue. These feeds route by distributing messages to pipes on a round-robin basis. If the feed is private, a hashed name is used for the queue so that it cannot be discovered by AMQP applications, otherwise the feed name is used as the queue name. When a RestMS client posts a message to the feed, the server publishes this to the default exchange, using as routing-key the feed name. If there is an address specified in the message envelope, this is used as the message-id, unless a message-id is also specified, in which case the address is not passed to the AMQP system.
The AMQP9 profile defines these join types:
* the default join type which the client uses by creating a join with no type property. The default join type specifies an address pattern and selects messages depending on the feed semantics.
The AMQP9 profile defines these pipe types:
* the default pipe type is implemented according to the 3/Defaults profile.
* The **ondemand** pipe type works as the default pipe type, but contains at most one message at a time. The action of retrieving a message asynclet will fetch a message from the feed. The server SHOULD check that ondemand pipes are joined only to rotator or service feeds and respond with "403 Forbidden" if the client requests a join from an ondemand pipe to another feed type. The server MAY implement the ondemand pipe type.
The default pipe type is propagated to the AMQP network as bindings that use the server-assigned hash 'name' of the pipe. Pipes MAY be implemented as private queues on the AMQP server but this is not the only architecture.
The AMQP9 profile extends the message resource as follows:
[[code]]
<message address="{address literal}"
[ delivery_mode = "{delivery-mode}" ]
[ priority = "{priority}" ]
[ correlation_id = "{correlation-id}" ]
[ reply_to = "{reply-to}" ]
[ expiration = "{expiration}" ]
[ message_id = "{message-id}" ]
[ timestamp = "{timestamp}" ]
[ type = "{type}" ]
[ user_id = "{user-id}" ]
[ app_id = "{app-id}" ]
[ sender_id = "{sender-id}" ]
>
[ <header name="{header name}" value="{header value}" /> ] ...
</message>
[[/code]]
* The priority is a value from 0 to 9 and may be used by the AMQP server to prioritise message delivery.
* The timestamp is formatted according to the HTTP/1.1 specifications for date/time formats [((bibcite rfc2616))] and may be set by AMQP APIs to indicate the time of origin of a message.
* The semantics of delivery_mode, correlation_id, expiration, type, user_id, app_id and sender_id are defined by AMQP implementations, APIs, and client applications.
AMQP currently does not implement multicontent messages. AMQP9 profile implementations SHOULD for the purposes of interoperability with AMQP restrict the number of contents per message to one.
The AMQP9 profile specifies the following additional server behaviour:
* The server MUST implement the 3/Defaults profile.
* When the client successfully POSTs a message to the a default type feed, the server MUST respond with a null (empty) document.
++ Guidelines for clients
To illustrate these example we use the Perl RestMS class library.
+++ Publish-subscribe scenario
In this example we show how to implement a topic-based newsfeed service that distributes news articles using a hierarchical category structure. The newsfeed service works as follows:
* A news publisher sends a stream of news covering various categories.
* Clients subscribe to and receive messages from specific news categories.
We use the Parrot pattern with one publisher talking to an arbitrary number of clients. Here is the example news category tree:
[[code]]
rec
|
o- pets
| |
| o- cats
| |
| o- dogs
|
o- cars
[[/code]]
News categories are written with periods separating each level, like this: "rec.pets.cats", according to the AMQP topic exchange specifications. Subscribers can request specific categories, or use wildcards: '*' specifies any value for one level, '#' specifies any value for any number of levels.
Our sample news stream contains these news items (we show the news category and the item title):
[[code]]
rec.pets.dogs Montreal: Canine Championship series opens
rec.cars The oil shock: does it affect you?
rec.pets.dogs Steroids: the ugly truth from Montreal
rec.pets.cats Cat vs. dog: facts or fictions?
rec.pets.dogs Montreal in chaos: winner is a cat!
rec.cars Red, white, or blue: what it says about you
rec.cars Parking - who, when, where, why: a new survey
rec.pets.cats Superiority: it comes naturally
[[/code]]
Both applications start with standard code to work with the default domain:
[[code]]
use RestMS ();
my $hostname = (shift or "live.zyre.com");
my $domain = RestMS::Domain->new (hostname => $hostname, verbose => 0);
[[/code]]
The subscriber creates a pipe and creates a join with the address "rec.pets.*":
[[code]]
# Grab reference to topic feed called 'news'
my $newsfeed = $domain->feed (name => "news", type => "topic");
my $pipe = $domain->pipe ();
my $join = $pipe->join (feed => $newsfeed, address => "rec.pets.*");
# Receive and print messages
while (1) {
my $message = $pipe->recv;
$message->carp ($message->address.": ".$message->content);
}
[[/code]]
The publisher sends a series of messages to the feed:
[[code]]
# Create a topic feed called 'news'
my $newsfeed = $domain->feed (name => "news", type => "topic");
my $message = RestMS::Message->new;
# Create a message with embedded plain content
$message->content_type ("text/plain");
$message->encoding ("plain");
# Now send our articles to the news feed
$message->content ("Montreal: Canine Championship series opens");
$message->send ($newsfeed, address => "rec.pets.dogs");
$message->content ("The oil shock: does it affect you?");
$message->send ($newsfeed, address => "rec.cars");
$message->content ("Steroids: the ugly truth from Montreal");
$message->send ($newsfeed, address => "rec.pets.dogs");
$message->content ("Cat vs. dog: facts or fictions?");
$message->send ($newsfeed, address => "rec.pets.cats");
$message->content ("Montreal in chaos: winner is a cat!");
$message->send ($newsfeed, address => "rec.pets.dogs");
$message->content ("Red, white, or blue: what it says about you");
$message->send ($newsfeed, address => "rec.cars");
$message->content ("Parking - who, where, why: a new survey");
$message->send ($newsfeed, address => "rec.cars");
$message->content ("Superiority: it comes naturally");
$message->send ($newsfeed, address => "rec.pets.cats");
[[/code]]
We start the subscriber, and then run the publisher. The subscriber shows this output:
[[code]]
rec.pets.dogs: Montreal: Canine Championship series opens
rec.pets.dogs: Steroids: the ugly truth from Montreal
rec.pets.cats: Cat vs. dog: facts or fictions?
rec.pets.dogs: Montreal in chaos: winner is a cat!
rec.pets.cats: Superiority: it comes naturally
[[/code]]
+++ Request-response service scenario
In this example we show how to implement a service that returns a "fortune cookie". The fortune cookie service works as follows:
* A client sends a request message to a fortune service (Wolfpack pattern).
* The fortune service responds with a fortune message (Housecat pattern).
We have two applications, one being the 'service' and one being the 'client'. The service waits for incoming requests and responds to each request that it gets, in a loop. The client sends a request and waits for a response, a single time.
Both applications start with standard code to work with the default domain:
[[code]]
use RestMS ();
my $hostname = (shift or "live.zyre.com");
my $domain = RestMS::Domain->new (hostname => $hostname, verbose => 0);
[[/code]]
The fortune service then creates the wiring: a service feed for clients to send requests to, a pipe to hold incoming requests, and a join to bind the two together. It then loops to receive incoming requests, and for each one it formats and returns a fortune response:
[[code]]
# Create a feed called 'fortune' for clients to send requests to
my $fortune = $domain->feed (name => "fortune", type => "service");
# Create an unnamed pipe and bind it to the feed
my $pipe = $domain->pipe (cached => 1);
my $join = $pipe->join (feed => $fortune);
# Grab a reference to the default feed, to send replies to
my $default = $domain->feed (name => "default");
$default->carp ("Fortune cookie service initialized OK");
# Now loop forever, processing requests
while (1) {
# See how we don't poll - this will wait until a message arrives
my $request = $pipe->recv;
# Create a new response message
my $response = RestMS::Message->new;
# Grab a fortune via the shell (hopefully fortune is on path)
$response->content (`fortune`);
$response->content_type ("text/plain");
# Send the response back via the direct feed
# We use the reply-to address provided in the request
$response->send ($default, address => $request->reply_to);
}
[[/code]]
The fortune client is a bit simpler. It creates a pipe and joins this to the default feed. It posts a request message to the fortune feed, then waits for a response:
[[code]]
# Grab a reference to the 'default' feed, so we can get our replies
my $default = $domain->feed (name => "default");
# Create a pipe for our replies
my $pipe = $domain->pipe ();
# Grab a reference to the 'fortune' feed
my $fortune = $domain->feed (name => "fortune", type => "service");
# Send a request to the fortune feed
my $request = RestMS::Message->new;
$request->send ($fortune, reply_to => $pipe->name);
# Wait for the response, and print it
my $response = $pipe->recv;
print $response->content;
# Free up the pipe, which we no longer need
$pipe->delete;
[[/code]]
To see the low-level HTTP requests and responses these applications generate, set the verbose argument to '1' when you create the domain.
+++ Checking profile availability
Clients SHOULD check that the server properly announces its support for the AMQP9 profile before using it. The server indicates that it supports the AMQP9 profile by listing it as a child of the domain:
[[code]]
Client:
-------------------------------------------------
GET /restms/domain/default
Server:
-------------------------------------------------
HTTP/1.1 200 OK
Content-Type: application/restms+xml
<?xml version="1.0"?>
<restms xmlns = "http://www.restms.org/schema/restms">
<domain name = "default" title = "Default domain">
<profile name = "4/AMQP9"
href = "http://www.restms.org/spec:4/AMQP9" />
</domain>
</restms>
[[/code]]
++ Guidelines for server implementers
+++ Integration with AMQP networks
The AMQP9 profile is designed to be able to interoperate with AMQP. This works as follows:
* A RestMS domain maps to an AMQP network (an AMQP server plus connection credentials) according to the RestMS server configuration.
* A feed maps to an AMQP exchange or shared queue, depending on the feed type.
* A message posted by a RestMS application to a mapped feed will be sent to the AMQP server where it can be accessed by AMQP applications via the AMQP mechanisms of bindings and queues.
* A message posted by an AMQP application to a mapped feed will be sent to the RestMS server where it can be accessed by RestMS application via the RestMS mechanisms of joins and pipes.
+++ RestMS-AMQP message routing
Sane use of AMQP demands that applications agree in advance on their routing architecture. This requires agreement on the names and types of exchanges used, and the allowed values for routing keys used in bindings and messages. To assure routing between RestMS and AMQP applications we need similar conventions.
We start by re-iterating the three fundamental messaging patterns:
# In which a request is sent to a remote "service", implemented by one or more service applications (the Wolfpack pattern).
# In which the response to a service request is sent back to the original requesting application (the Housecat pattern).
# In which a publisher distributes data to multiple subscribers (the Parrot pattern).
To create dependable RestMS-AMQP message routing, these guidelines apply:
* A service MUST correspond to a shared queue in AMQP terms, or a service or rotator feed in terms of the AMQP9 profile. AMQP service applications MUST consume from the shared queue. RestMS service applications MUST create a pipe and create a join from the pipe to the feed. A service MAY be implemented transparently by any mix of AMQP and RestMS applications, with messages being served in a round-robin fashion to each implementing application.
* A request message that needs a reply MUST provide a reply_to attribute. For AMQP clients this MUST be the name of a private response queue. For RestMS clients this MUST be the name (server-generated hash) of a pipe.
* Services that wish to send replies MUST use the reply_to attribute of the request message. If they are AMQP service applications, they MUST publish the reply to the default exchange using the reply_to attribute as routing key. If they are RestMS service applications, they MUST post the reply to the default feed using the reply_to attribute as address.
* For data distribution, a fanout, topic, or headers feed maps to an AMQP exchange, and subscribers can be either RestMS clients or AMQP clients. RestMS clients create pipes and join them to the feeds they want to consume from, specifying the address pattern in each case. AMQP clients create private queues and bind them to the exchanges they want to consume from, specifying the routing key in each case.
+++ The RestMS extension class
In order to interoperate with a RestMS server, an AMQP server must be able to mirror resources. Thus, if a RestMS client creates a feed, a matching exchange or shared queue should be created on the AMQP server, so that AMQP applications have access to it. And vice-versa, if an AMQP client creates a shared queue or exchange, this should be mirrored to the RestMS server as a feed.
There are some significant differences between the RESTful model we implement in RestMS, and the AMQP model for resource management. It would be possible, in theory and in time, to modify the AMQP protocol to become compatible with RestMS. For example, commands like Queue.Delete would need to become idempotent so that attempting to delete a non-existent queue was safe. Today, AMQP treats such attempts as fatal errors.
Rather than attempt to modify existing AMQP semantics, we propose an AMQP "extension class", an extension mechanism that AMQP allows. The advantage of an extension class is that a RestMS server can detect immediately whether its target AMQP server supports it, or not. There is no ambiguity. This class uses index 61501, which falls into the space allotted for extension classes.
[[code]]
<?xml version="1.0"?>
<!--
Copyright (c) 1996-2009 iMatix Corporation
This code is licensed under both the GPLv3 and the IETF licence, in accordance
with the terms of the http://www.restms.org Intellectual Property Policy.
-->
<class
name = "restms"
handler = "connection"
index = "61501"
>
RestMS resource management class.
<doc>
Provides methods to work with server-side resources as defined by
the RestMS specification. All methods are request-only, without
response. Errors are logged at the server side and not reported
to the client. This model is designed to allow an AMQP client to
push state to an AMQP server, or vice-versa, rapidly and without
handshaking.
</doc>
<doc name = "grammar">
restms = C:PIPE-CREATE
/ C:PIPE-DESTROY
/ C:FEED-CREATE
/ C:FEED-DESTROY
/ C:JOIN-CREATE
/ C:JOIN-DESTROY
</doc>
<chassis name = "server" implement = "MAY" />
<chassis name = "client" implement = "MAY" />
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<method name = "pipe-create" index = "10">
create a pipe
<doc>
Creates a pipe of the specified type. The pipe may already exist,
if it has the same type. Pipe names are unique across all types.
</doc>
<chassis name = "server" implement = "MUST" />
<field name = "pipe type" type = "shortstr" >
pipe type
<doc>
Specifies the type of the pipe to create. Valid values are: pipe.
</doc>
</field>
<field name = "pipe name" type = "shortstr" >
Name of pipe
<doc>
Specifies the name of the pipe to create. Pipe names may not contain
slashes, spaces, or at signs.
</doc>
</field>
</method>
<method name = "pipe-delete" index = "20">
delete a pipe
<doc>
Deletes a specified pipe, if it exists. Safe to invoke on non-existent
or already-deleted pipes.
</doc>
<chassis name = "server" implement = "MUST" />
<field name = "pipe name" type = "shortstr" >
pipe name
<doc>
Specifies the name of the pipe to delete.
</doc>
</field>
</method>
<method name = "feed-create" index = "30">
create a feed
<doc>
Creates a feed of the specified type. The feed may already exist,
if it has the same type. Feed names are unique across all types.
</doc>
<chassis name = "server" implement = "MUST" />
<field name = "feed type" type = "shortstr" >
Feed type
<doc>
Specifies the type of the feed to create. Valid values are: fanout,
direct, topic, headers, system, rotator, and service.
</doc>
</field>
<field name = "feed name" type = "shortstr" >
Name of feed
<doc>
Specifies the name of the feed to create. Feed names may not contain
slashes, spaces, or at signs.
</doc>
</field>
</method>
<method name = "feed-delete" index = "40">
delete a feed
<doc>
Deletes a specified feed, if it exists. Safe to invoke on non-existent
or already-deleted feeds.
</doc>
<chassis name = "server" implement = "MUST" />
<field name = "feed name" type = "shortstr" >
feed name
<doc>
Specifies the name of the feed to delete.
</doc>
</field>
</method>
<method name = "join-create" index = "50">
create a join
<doc>
Creates a join on the specified pipe and feed. The join may already
exist, if it has the same properties. A join will causes messages to
be delivered on the connection. The consumer-tag property allows
messages to be routed into end-application pipes. Joins on exchange
feeds use the consumer tag "x:{pipe-name}" and joins on queue feeds
use the consumer tag "q:{pipe-name}". AMQP does not allow the same
tag to be used on multiple queues.
</doc>
<chassis name = "server" implement = "MUST" />
<field name = "pipe name" type = "shortstr" >
Name of pipe
<doc>
Specifies the name of the pipe, which must exist.
</doc>
</field>
<field name = "feed name" type = "shortstr" >
Name of feed
<doc>
Specifies the name of the feed, which must exist.
</doc>
</field>
<field name = "address" type = "shortstr" >
Join address
<doc>
Specifies the address to join. This is an address literal or
pattern who's semantics depend on the feed type. The address
may not contain slashes, spaces, or at signs.
</doc>
</field>
</method>
<method name = "join-delete" index = "60">
delete a join
<doc>
Deletes a specified join, if it exists. Safe to invoke on non-existent
or already-deleted joins, and referring to non-existent pipes and/or
feeds.
</doc>
<chassis name = "server" implement = "MUST" />
<field name = "pipe name" type = "shortstr" >
Name of pipe
<doc>
Specifies the name of the pipe, which does not need to exist.
</doc>
</field>
<field name = "feed name" type = "shortstr" >
Name of feed
<doc>
Specifies the name of the feed, which does not need to exist.
</doc>
</field>
<field name = "address" type = "shortstr" >
Join address
<doc>
Specifies the join address.
</doc>
</field>
</method>
</class>
[[/code]]
+++ Resource synchronisation
The RestMS extension class defined above provides a clean way for the RestMS server to synchronize all its resources with an AMQP server, and vice-versa. Since the commands are asynchronous and not confirmed, they can be executed very rapidly, so that one server can synchronize its resources on another (for example, after failover) at the rate of tens of thousands of resources per second.
To avoid "storms", a server SHOULD track the origin of resources, and synchronise only those which were created by its own local clients.
When a server receives resource specifications from another server, these resources SHOULD be treated as configured and clients SHOULD not be allowed to delete them except on the original server.
Server implementations MAY take any reasonable approach to resources that are "orphaned", i.e. where their original owning server has disconnected and/or gone off line.
+++ Message routing
The AMQP9 profile does not impose a specific architecture for interconnection with an AMQP network and there are several possibilities which we explain for the benefit of implementers.
In practice, AMQP servers may be paired for high-availability, and/or may be federated themselves into larger AMQP networks. In this discussion we will assume that the RestMS server speaks to exactly one AMQP server.
The first model relies on the AMQP server to do all routing. In this case, the RestMS server will synchronise all feeds, pipes, and joins with the AMQP server. All RestMS pipes and joins are instantiated as private queues and bindings on the AMQP server. Pipes are implemented in the simplest fashion: each pipe has an exclusive queue with the same name, with a consumer with tag x:{pipe-name}. When the RestMS server creates joins on the pipe, these are implemented as bindings that bring messages into the private queue. This is for exchange-based feeds. For queue-based feeds, the pipe is implemented as a consumer on the shared queue, with tag q:{pipe-name}. These distinct tags are used by the RestMS server to route messages coming from the AMQP server into separate pipes for end-application delivery.
When a RestMS client posts a message to a feed, the RestMS server forwards that message to the AMQP server. When an AMQP client publishes a message, this also arrives on the AMQP server. The AMQP server then routes that to all matching bindings, and thus into private queues. Message are then delivered to the RestMS server, which uses the consumer tag to sort the messages into their pipes. RestMS clients can then retrieve their messages from their pipes.
A second model is to open multiple connections or channels, e.g. one per pipe, and to use these to segment messages per pipes.
The advantage of these two model is that they are easy to understand and implement. The main disadvantage is that messages will be sent redundantly, if they match multiple joins/bindings. This will waste LAN bandwidth.
The optimal, but most complex model, is to use federation-style normalization. In this model, the RestMS server maintains its own routing data structures, and forwards binding requests to the AMQP server. When messages arrive, they are routed not on consumer tag, but according to the message routing key and/or other properties. This model demands that the RestMS implementation has the same routing capabilities as the AMQP server, i.e. implements exchanges and shared queues in much the same way. The advantage of this model is that it allows for stand-alone RestMS operation, and is the optimal design for RestMS-to-RestMS interoperation (with no extra hops to and from the AMQP server).
Finally, a RestMS server does not need to work with an AMQP network, it can be totally self-standing. This is plausible in a federated design, where the RestMS server may run alone, or as a federated node, depending on its configuration.
[[bibliography]]
: amqp : "The Advanced Message Queueing Protocol" - [http://www.amqp.org amqp.org]
: rfc2119 : "Key words for use in RFCs to Indicate Requirement Levels" - [http://tools.ietf.org/html/rfc2119 ietf.org]
: rfc2616 : "HTTP/1.1 Protocol Parameters" - http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html w3.org]
[[/bibliography]]