|
Before diving into the details of send and receive, it is helpful to understand how message buses are configured for an application. Message buses are resources that are shared between applications. In configuration a message buses are defined independently of applications to provide the common connection details and the set of channels that they provide for applications. Applications will reference the buses that they will use to collaborate with other applications along with a subset of the channels that they will work with. Applications work abstractly with buses and channel to send and receive messages, allowing configuration changes at runtime to augment
<model> <!-- Define buses and channels that will be shared between applications--> <buses> <bus name="sample-bus"> <provider>activemq</provider> <address>localhost</address> <port>61616</port> <channels> <channel name="new-orders-channel"> <qos>Guaranteed</qos> <key>NEWORDERS/${Region}/${Department} </channel> <channel name="order-events"> <qos>Guaranteed</qos> <key>ORDEREVENTS/${Region}/${EventType}/${Department} </channel> <channels> </bus> </buses> <!-- Apps will reference the buses they use in their messaging config --> <apps> <app name="sample-app" mainClass="com.sample.SampleApp"> <messaging> <factories> <factory name="com.sample.messages.OrderMessagesFactory" /> </factories> <buses> <bus name="sample-bus"> <channel name="new-orders-channel" join="true"> <filter>Region=US|Canada</filter> </channel> </bus> </buses> <messaging> </app> </apps> </model> |
Understanding Message SendsMessages are sent over message bus channels in a fire and forget fashion - the application can rely on the underlying AepEngine to deliver the message according to the quality of service configured for a channel. An application views message channels as a logical, ordered conduit over which messages flow. The diagram below depicts the path of a message sent through an engine. The basic flow of sending is as follows:
Solicited vs Unsolicited SendsWhen an outbound message is sent from within an application event handler, it is referred to as a solicited send. When an application is sent from outside of a message handler, it is referred to as an unsolicited send.
Creating MessagesMessage types generated by the application data modeler can be created via their static create() method. Application code populates message contents as a regular pojo. Message Concurrency and PoolingOutbound messages sent by the application may be serialized in background threads.
An application that would like to send the same message multiple times can use the copy() method on the message. The copy can be modified and used in a subsequent send call. Messages may also be pooled by the platform. This is the reason that message construction is done through static create methods. Once the platform has finished sending them, their backing contents can be wiped and the view reused in a subsequent sent.
Using Aep Message SenderThe simplest way to send a message is to add an AppInjectionPoint annotation that allows the XVM to inject a message sender. The application then supplies the bus and channel name along with the message.
See also: Sending via the AepEngineWhen an application does not use the AepMessageSender, it must provide the MessageChannel in the AepEngine's sendMessage call. When using this pattern, the application can register event handlers for channel up and down events which are dispatched to the application when messaging is started or stopped. The application can qualify the EventHandler annotation to identify a particular channelName@busName to specify the channel of interest.
Channel Keys and Topic ResolutionThe message channel encapsulates the physical destination on which the message will be sent using a channel key. The channel key abstraction allows the actual message provider destination to be be controlled via configuration. Channel keys can be either static or dynamic. A static key uses the channel key as the the actual topic on which to send the message. A dynamic key contains variable components that are resolved at runtime either at startup or during a send call to resolve the specific topic. By default a message channel will prepend the channel name to the beginning of the topic as the first level in the topic name. This mechanism prevents multiple channel definitions from conflicting with one another. this behavior can be disabled by configuring the bus provider with the bus property Keyless ChannelsWhen no channel key is specified for a defined channel, the platform's default behavior is to use the channel name as the destination for the message. For this case it is not possible to set topic_starts_with_channel=false. Static KeysA message channel that is configured with no variable key components is said to be a static key, on every send call the key will be used as is for the topic (possibly prepended by the channel name). Dynamic KeysA channel key consists of static and variable portions with the variable portions consisting identified in the form of ${propName[::defaultValue]}. For example, a channel key of 'ORDERS/${salesRegion::US}/${productId}/Purchases' is interpreted as having variable key components of 'salesRegion' and '${productId}', with 'salesRegion' specifying a default value of 'US' in cases where it is unspecified. When a message is sent over a
If a variable key segment cannot be resolved by any of the above means at send time it results in an SMAException being thrown from the send call. Message ReflectionMessage reflection allows content based routing of the message. To use message reflection the channel key variable should match the name of the getter for the field in the message starting with a lower case character. For example for a message with a getter of:
The channel key variable should be defined as ${salesRegion} Valid Field TypesThe following message field types are supported for message key reflection and will be used as substitution values when the hasXXX() method corresponding to the field returns true.
If the field value is null or or hasXXX() returns false, channel key resolution will fall back to the key resolution table or default value if present. If the field value is not reflectable or the field value is null and there is the value is not found in a key resolution table, then an exception is thrown. Nested Field ReflectionKey values can come from embedded entities in the message by specifying the bean path of the field. For example if the message is a CustomerProfileUpdateMessage with an embedded entity Address field one might could specify a channel key variable ${address.zipCode} which would be substituted with
Key Resolution TablesWhen a variable key value is not present in a message because there is not field matching the name, the field is not set or not reflectable key resolution will fall back to a channel key resolution table if available. Channel Key Resolution TableIn many cases, substitution values for a dynamic key come from the application environment or configuration. Message channels can be configured with a key resolution table to allow substitution of key variables that don't come from the message being sent. Channel key resolution tables can be supplied programatically by registering an event handler for the AepChannelUpEvent and setting the key resolution table. The following shows an example of setting a global key resolution table that will be configured for every channel in the app:
Caller Provided Key Resolution TableAEP send methods allow the caller to pass in a key resolution with the send method call to augment key resolution on case by case basis. RawKeyResolutionTableIn addition to specifying a key resolution table using a java.util.Properties map, a RawKeyResolutionTable can be configured instead. A RawKeyResolutionTable stores the substitution value in an XString which can improve performance because the substitution value can be stored as pre encoded bytes and eliminate character encoding costs. The following example shows how a RawKeyResolutionTable can be used instead of Properties:
When using a RawKeyResolutionTable the following restriction apply:
Message Key ValidationWhen resolving topics dynamically from a messages fields it may be important to validate that value from the message field doesn't introduce additional levels in the topic or illegal characters or result in empty topic levels. To prevent this a message bus can be configured to 'clean' the dynamic portions of the topic at the cost of additional overhead by setting the following environment properties:
Caller Provided KeyIt is also possible for the caller to provide the message key directly in the send call. In this case the supplied key will be used as is for the topic. This can be useful in certain situations, but should be used with care because it breaks the ability for the topic to be changed at runtime and receivers will not have knowledge of how to subscribe to such sends.
When using caller supplied topic:
Understanding Message ReceiptAn application's Aep Engine creates and manages the lifecycle of the message buses that an application configures for use. When an application is configured to join one of more bus channels, subscriptions will be issued on behalf of the application to attract messages. Message dispatch occurs as follows:
Expressing InterestFor an application to receive messages, it must:
Configuring Channels For JoinIn order for the application's AepEninge to issue subscriptions for the message channel on which a message is sent, it must be joined. Buses and channels are configured via the platform's configuration DDL. The below configuration snippet demonstrates:
Adding an EventHandlerWhen a message is received by a message bus, it is enqueued into the application's Inbound Event Queue to be queued for dispatch, which the engine will pick up.
The application's underlying AepEngine will ensure that the message is acknowledged once state changes made by the handler have been stabilized. That, coupled with the engine's message deduplication feature, ensures that even in the event of failover, the handler will only be executed once. Channel FiltersA channel filter filters variable parts of a channel key to filter what is received over a message channel. It is used to determine the subscriptions issued on behalf of the application. Channel filter take the following form: For example, given a channel key of
If a variable portion of the channel key is omitted in a filter, it will result in the subscription being joined in a wildcard fashion (assuming the underlying bus implementation supports wildcards). So given a channel key of
Finally, if the channel filter is set to null for the channel key in the example above, then the resulting subscription would be:
Handling Received MessagesMessages dispatched to applications are read only, it is illegal for an application to modify a received message. As Messages may be pooled by the platform it is also not legal for an application to hold onto a message beyond the scope of its handler unless it first calls acquire() on the message. If the application does acquire() the message it should later dispose() of the message to allow the platform to reuse it. In most cases holding on to the message beyond the scope of the handler is not the best idea from a performance perspective. Instead applications will typically copy the data they need out of the message into either application state objects or into outbound messages. Inbound messages may not be resent as outbound messages. Applications that need to forward an inbound message as an outbound messages should first copy() the message and send the message copy(). Registering MessageView FactoriesMessage bus binding implementations receive messages in serialized form and wrap them with a MessageView that is passed to the application to work with. MessageViews are wrapped by locating the message MessageViewFactory for the message which is typically generated by ADM. To locate the factory and message type, a binding consults Message Metadata that is transported along with the serialized message. An application must therefore register the message factories it intends to use so that bus binding can find the factories. This can be done by registration or by programming. Registration via Config DDLMost often, applications will list message view factories that they use in their DDL Config.
ProgrammaticallyRegistration can also be done programmatically via the AepEngine. A common way to do this is to provide an AppInjectionPoint for the AepEngine in the application.
Message Sequencing And Duplicate Detection
HA ConsiderationsOnly the primary instance of an application will establish messaging connections. Preserving Subscriptions on ShutdownBy default, when an engine is stopped without an error, bus channels that were 'joined' will be 'left', meaning that any subscriptions or interests created by the message bus will be unsubscribed or unregistered. This behavior can be overridden by configuring an application to preserve channel joins on stop.
Note that this property has no effect when an engine shuts down with an error (e.g. This behavior can also be overridden programmatically on a case by case basis by an EventHandler for the See Also |