Event-Driven Orchestration : Sample implementation

Brijesh Deb
4 min readMay 22, 2022

--

Photo by Markus Winkler on Unsplash

Event-Driven Orchestration is an architectural pattern used to integrate services for implementing end-to-end business processes. It centralises orchestration using BPMN model while interacting with worker services via events. In this article we look into a sample implementation of a fictitious “shopping cart” business process using event-driven orchestration pattern.

For an overview of different architectural options to integrate distributed services, refer to an earlier article which explores advantages and disadvantages of i) Event-Driven Choreography ii) Centralise Orchestration and iii) Event-Driven Orchestration.

High Level System View

The overall system consists of an orchestrator service (shoppingcart) and multiple worker services (location, payment, inventory and order). Shoppingcart service has the BPMN model which orchestrates different steps in the process and delegates execution of these steps to corresponding worker services by publishing events to Kafka topic.

Figure 1: High Level System View

Sample Implementation

The sample implementation uses SpringBoot, Camunda 7 (community edition), Confluent Kafka and Schema Registry, Spring Cloud Stream. Source code is available in GitHub.

Note: This sample implements only happy path, and doesn’t include handling business exception and rollback in BPMN model.

Key aspects of the sample implementation are:

  • Embedded Camunda Process Engine

Orchestrator service (shoppingcart) is a SpringBoot microservice with Camunda 7 (Community edition) included as an embedded process engine. Camunda gets added as an application library and can be started, stopped and scaled along with shoppingcart service.

Camunda Spring Boot Intializr, a web app tool can be used to generate starter project for Spring Boot with embedded Camunda.

  • BPMN model

Two service tasks (validate address and reserve payment) as shown in high level System View(Figure 1) are included in the sample implementation. Other service tasks (allocate inventory, place order) will have similar implementation and are not included for brevity.

Figure 2: BPMN model

The model consists of following BPMN elements:

  • Service Tasks: Used to invoke or execute business logic. Herein service tasks (Validate Address and Reserve Payment) will delegate execution of actual business logic to Location and Payment services by publish commands to Kafka topic.
  • Intermediate Message Catch Event: Intermediate message events are process steps where respective process instance waits for an incoming message before the flow commences. Herein shoppingcart process instance will wait for AddressValidated and PaymentReserved message before it proceeds with next steps.
  • Start and Stop Events: As the name suggests they are used to start and stop process instance respectively.

BPMN model (shoppingcart_model.bpmn) is there in resources folder of shoppingcart service. The model gets auto-deployed in Camunda process engine when shoppingcart service is started.

  • Initiating process instance

Shoppingcart microservice provides an endpoint to initiate a process instance whenever “checkout” button is clicked from the web front end. ShoppingCartController includes following code to create a new process instance.

ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RuntimeService runtimeService=processEngine.getRuntimeService();

Map<String, Object> variables=new HashMap<>();
variables.put("cart",cart);

ProcessInstance instance = runtimeService.createProcessInstanceByKey("shoppingcart_workflow")
.businessKey(cart.getCart_id())
.setVariables(variables)
.execute();

Cart_id is used as Business Key while creating process instance. A business key is a domain-specific identifier of a process instance. The same will used for message co-relation (detailed in later section ) to send message to specific process instance.

  • Delegation of process steps

Shoppingcart service includes two delegates (ValidateAddressDelegate and ReservePaymentDelegate) which are used to delegate actual business logic to other services (Location and Payment). For example, ValidateAddressDelegate publishes ShippingAddress to Kafka which is consumed by Location service.

public void execute(DelegateExecution execution)
{
Cart cart = (Cart)execution.getVariable("cart");
String cart_id = cart.getCart_id();
String street = cart.getShippingAddress().getStreet();
String state = cart.getShippingAddress().getState();
String post_code = cart.getShippingAddress().getPost_code();
ShippingAddress address = new ShippingAddress(cart_id,street,state,post_code);

// Publish ShippingAddress to Kafka topic
ProducerRecord<String,ShippingAddress> producerRecord = new ProducerRecord<>(TOPIC,0,null,cart_id,address);
this.kafkaTemplate.send(TOPIC, address);
}
  • Canonical Data Model

Canonical Data Model (CDM) provides a common data model which is used to integrate processes across various services and systems having their individual data formats. In this sample implementation, event models are designed in Avro format and placed in resources/avro folder of respective microservices.

In real project, industry standard domain models are recommended, such as TM Forum SID (Telecommunications) or FHIR (Healthcare).

  • Message co-relation

Message co-relation updates a running process instance with a state update from an external system asynchronously. In the sample implementation message co-relation is used to update running “shoppingcart” process instance with “AddressValidated” and “PaymentReserved”. ShoppingCartConsumer has the logic for message co-relation based on status consumed from Kafka.

HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
if(status.equals("ValidAddress"))
message ="AddressValidated";
else if (status.equals("PaymentReserved"))
message ="PaymentReserved";
HttpEntity<RestMessageDto> entity = new HttpEntity<RestMessageDto>(
new RestMessageDto(message,cart_id),
headers);

ResponseEntity<Object> result = new RestTemplate().postForEntity(REST_ENDPOINT, entity, null);

Conclusion

In this article we have explored how to use event-driven orchestration for implementing a business process using Camunda BPM, SpringBoot, Confluent Kafka. Comments and suggestions are welcome !

--

--

Brijesh Deb

Architect | Cloud and Data Technologies | Platform and product engineering