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, 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
The following interfaces are the core of the asynchronous messaging framework. Developers seeking to publish or receive an event message use these classes.
com.elasticpath.messaging.api.EventMessage
com.elasticpath.messaging.api.EventMessagePublisher
com.elasticpath.messaging.api.EventType
com.elasticpath.messaging.api.factory.EventMessageFactory
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
Following are the key classes:
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 (Java Messaging Service) broker, which can be customized as required.
Event Message Types
Out of the box, the Commerce Engine publishes messages for the following events:
- Change Set Events
- Change Set ready for publishing
- Commerce Manager User Events
- CM User created
- CM User password changed
- CM User password reset
- Customer Events
- Anonymous Customer registered
- Customer registered
- Customer password changed
- Customer password forgotten
- Wish List shared
- Data Import Events
- Import job completed
- Gift Certificate Events
- Gift Certificate purchased
- Order Events
- Order created
- Order held
- Order cancelled
- Order released
- 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.
}
...
}
Extensible enum?
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 newEventType
implementationEvent type lookup - an implementation of the
EventTypeLookup
interfaceThis is used to convert a String to an
EventType
.For example, the String "
INVENTORY_ALLOCATED
" to theINVENTORY_ALLOCATED
enum
tip
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"/>
<property name="eventTypeLookup">
<bean class="com.elasticpath.core.messaging.inventory.InventoryEventType$InventoryEventTypeLookup"/>
</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 brokerA Camel Proxy implementation of the
EventMessagePublisher
interfaceThis 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"/>
<endpoint id="inventoryEventInternalEndpoint" uri="direct:ep.inventory"/>
</camel:camelContext>
<bean id="inventoryEventMessagePublisher" class="org.apache.camel.component.bean.PojoProxyHelper" factory-method="createProxy">
<constructor-arg ref="inventoryEventInternalEndpoint"/>
<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"/>
<property name="outgoingEndpoint" value="jms:topic:ep.inventory"/> <!-- existing routes defer this definition to a Settings Framework value - see existing EventRouteBuilder bean definitions for an example -->
<property name="eventMessageDataFormat" ref="eventMessageDataFormat"/>
</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 Apache Camel documentation.