Asynchronous Event Messaging
Asynchronous Event Messaging
Asynchronous messaging provides the ability to queue messages, allowing integrated systems to share data and process events without interrupting functionality by having to wait for a response to their message. Elastic Path Commerce uses asynchronous messaging to reduce coupling between the Commerce Manager Server, Search Server, and Cortex.
Asynchronous messaging benefits:
- Systems sending and receiving messages do not need to be operational at the same time.
- Time-sensitive operations can be processed before non-critical operations.
- Encourages system designs to be decoupled, cohesive components with robust error-handling characteristics.
Project overview
In this diagram, "other messaging modules" represents the following projects:
- ep-core-changeset-messaging
- ep-core-cmuser-messaging
- ep-core-dataimport-messaging
- ep-core-giftcertificate-messaging
ep-messaging-api
Key classes(ep-messaging-api)
- com.elasticpath.messaging.api.EventMessage
- com.elasticpath.messaging.api.EventMessagePublisher
- com.elasticpath.messaging.api.EventType
- com.elasticpath.messaging.api.factory.EventMessageFactory
These interfaces are the core of the asynchronous messaging framework. Developers seeking to publish or receive an event message will use these classes.
An Event Message in detail
The event message interface assumes that any system receiving an event message has access to the Elastic Path database and can use that data to enrich a message as required. The event message interface follows the Claim Check Enterprise Architecture Integration pattern.
An event message contains three items of data:
- Event Type - Defines the message context. For example, the OrderEventType.ORDER_CREATED Event Type indicates the message is sent in reaction to a new Order being created.
- GUID - The underlying object's Globally Unique Identifier. For a new order, this is the newly created Order's GUID.
- Map<String, Object> - Contains additional data required to identify the underlying data object. Can contain transient data that does not exist within the database.
When published, event messages are represented as JSON. The order example above produces the following JSON string:
{"eventType":{"@class":"OrderEventType","name":"ORDER_CREATED"},"guid":"20000","data":{}}
ep-messaging-camel
Key classes(ep-messaging-camel):
- com.elasticpath.messaging.camel.EventRouteBuilder
The ep-messaging-camel project implements the ep-messaging-api project and utilizes Apache Camel to handle message routing and transformation.
The EventRouteBuilder class is an Apache Camel RouteBuilder that publishes EventMessage instances to an external channel. The default and recommended messaging channel Elastic Path uses is a JMS broker, which can be customized as required.
Event Message Types
Out of the box, the Commerce Engine publishes messages for the following events:
Customer Events
- Anonymous Customer registered
- Customer registered
- Customer password changed
- Customer password forgotten
- Wish List shared
Order Events
- Order created
- Order exchanged
- Order shipment created
- Order shipment release failure
- Order shipment shipped
- Order returned
To add additional Event Message Types, see How to add a new Event Type.
Each event message type communicates on a separate JMS Topic. For example, all order events are sent to the ep.orders JMS topic, while all customer events are sent to the ep.customers topic. Each event type is contained in a separate project, e.g. ep-core-order-messaging. Each project defines its EventType and implements the EventMessagePublisher interface that directs messages to the appropriate JMS topic.
How to add a new Event Type
Creating the EventType
Existing EventTypes are extensible enum entries. This enables you to add a new event type by extending an existing event type class and adding your new entry. For example, to add a new ORDER_UPDATED event type, extend the OrderEventType class and add a new OrderEventTypeExt entry as follows:
public static final OrderEventType ORDER_UPDATED = new OrderEventType(ORDER_UPDATED_ORDINAL, "ORDER_UPDATED");
If the new event type does not fit into an existing event type class, create a new event type class. For consistency, create the new event type class by following the pattern found in the existing classes. That is, create an enum or ExtensibleEnum that implements the EventType interface and add each required EventType as an entry.
For example, to create a new EventType to communicate when inventory is allocated, create a new event type class to house a new INVENTORY_ALLOCATED EventType as the InventoryEventType class does not exist.
public class InventoryEventType extends AbstractExtensibleEnum<InventoryEventType> implements ExtensibleEnum, EventType { /** Ordinal constant for INVENTORY_ALLOCATED. */ public static final int INVENTORY_ALLOCATED_ORDINAL = 0; /** * Signals that inventory has been allocated. */ public static final OrderEventType INVENTORY_ALLOCATED = new OrderEventType(INVENTORY_ALLOCATED_ORDINAL, "INVENTORY_ALLOCATED"); // refer to the Extensible Enum documentation for details on how to implement the remained of this class. } ... }
It's not necessary to create an extensible enum in an extension project. Depending on your use case, a simple enum will do.
Unmarshalling from JSON
EventMessages are typically converted to JSON when published to an external broker. New EventTypes will marshal to JSON automatically; however, additional work is required to unmarshal these messages. Since EventType is an interface, the JSON unmarshaller must be aware of all available EventType implementations in order to construct the appropriate instance type.
Registration of a custom event type implementation with the unmarshaller is achieved by ensuring an EventTypeProvider bean is added to the Spring context. The BeanPostProcessor will detect the bean when the Spring context is initialized and register the new event type. The EventTypeProvider properties that must be set are:
- Event type class - a java.lang.Class instance representing the new EventType implementation
- Event type lookup - an implementation of the EventTypeLookup interface. This is used to convert a String to an EventType. For example, the String "INVENTORY_ALLOCATED" to the INVENTORY_ALLOCATED enum.
Refer to an existing implementation of EventTypeLookup for more information. For example, OrderEventType.OrderEventTypeLookup.
The configured bean should appear as follows:
<bean id="inventoryEventTypeRegistration" class="com.elasticpath.messaging.spi.impl.EventTypeProviderImpl"> <property name="eventTypeClass" value="com.elasticpath.core.messaging.inventory.InventoryEventType"/commerce-legacy/> <property name="eventTypeLookup"> <bean class="com.elasticpath.core.messaging.inventory.InventoryEventType$InventoryEventTypeLookup"/commerce-legacy/> </property> </bean>
Publishing to a new channel
Each event type category is published to its own channel, so you will need to for new EventType messages. You can create new message channels by adding some Spring beans to the context.
To publish the new EventType messages to a new JMS Topic, create a new Camel context with these three components:
- A direct endpoint. Used as the entry point to the messaging system. For example, direct:ep.inventory
- A RouteBuilder instance to consume messages from the direct endpoint and forward them to the external JMS broker
- A Camel Proxy implementation of the EventMessagePublisher interface. This is injected into classes that will publish messages to the direct endpoint
Here's an example of wiring the required beans for publishing to a new channel:
<camel:camelContext id="ep-inventory-messaging" xmlns="http://camel.apache.org/schema/spring" threadNamePattern="Camel (#camelId#) thread ##counter#"> <routeBuilder ref="inventoryEventBuilder"/commerce-legacy/> <endpoint id="inventoryEventInternalEndpoint" uri="direct:ep.inventory"/commerce-legacy/> </camel:camelContext> <bean id="inventoryEventMessagePublisher" class="org.apache.camel.component.bean.PojoProxyHelper" factory-method="createProxy"> <constructor-arg ref="inventoryEventInternalEndpoint"/commerce-legacy/> <constructor-arg> <util:list> <value>com.elasticpath.messaging.EventMessagePublisher</value> </util:list> </constructor-arg> </bean> <bean id="inventoryEventBuilder" class="com.elasticpath.messaging.camel.EventRouteBuilder"> <property name="incomingEndpoint" ref="giftCertificateEventInternalEndpoint"/commerce-legacy/> <property name="outgoingEndpoint" value="jms:topic:ep.inventory"/commerce-legacy/> <!-- existing routes defer this definition to a Settings Framework value - see existing EventRouteBuilder bean definitions for an example --> <property name="eventMessageDataFormat" ref="eventMessageDataFormat"/commerce-legacy/> </bean>
Once these beans are defined, event messages can now be created using the new EventType instance. Upon publishing, messages will be marshalled to JSON, transmitted to an external JMS Broker, and can then be unmarshalled by consumers.
How to consume an Event Message
Messages converted to JSON are published to a JMS broker. This is an industry standard, so external systems should be able to read the messages using their existing framework.
Within the Commerce Engine, messages are consumed using Apache Camel. A Camel DataFormat is provided that can unmarshal messages to the EventMessage class, which can then be inspected and used by a new Camel route or by any other system consuming these messages.
Here's an example of a message consumer:
public class EventMessageConsumingRouteBuilder extends RouteBuilder { private DataFormat eventMessageDataFormat; // and getter and setter @Override public void configure() throws Exception { from("jms:topic:ep.examplemessages") .unmarshal(eventMessageDataFormat) // converts the JSON message to a POJO that can be inspected by the predicate .process(...) // handle the message as required } }
How to route messages to an Alternative Destination
Out of the box, order events route to a JMS Topic called ep.orders. This can be updated by changing a system configuration setting in the Commerce Manager.
The URI specified should be an Apache Camel channel URI. For more information on valid Apache Camel URI syntax and available options, see http://camel.apache.org/uris.html.