Event Generator – a simple approach

This is the third part of: How to drive an Event-driven Architecture for microservices and it covers the first code-sketch to create an event generator based on Spring Boot and Apache Camel.

The example used in this series of posts is:

A user does a purchase in an e-commerce shop and wants to get an invoice.

As described in part two: How to drive Event Generation the event generator should be able to send a message, which contains a header for meta-data and a body for the event-payload, to a message-bus respectively an event channel – in a lightweight way. Fortunately Apache Camel’s message structure provides natively such body/header concept and moreover it provides the ability to get connected to common message-busses in a few minutes.

Firstly an interface could be introduced that specifies a kind of an event service to fire events:

	public interface EventService {

		@InOnly
		void fireEvent(@Header("event-type") String eventType, @Body Object payload);
	}

Secondly an instance of the service can automatically be created by Camel with the @Produce annotation.

	@Produce(uri = "direct:eventService")
	public EventService eventService;

If the method fireEvent is called like:

eventService.fireEvent("purchaseCreated", purchasePayload);

Camel creates a message that contains the content of the parameter annotated with @Header and @Body and sends the message to the endpoint direct:eventService.

The method is annotated with @InOnly, because it should be an asynchronous request and we expect no response. Actually it is possible to replace the endpoint direct:eventService with the endpoint of the event-channel. However, to keep it flexible, to be able to add further meta-data and to do a kind of marshaling as needed, I introduced an event producer respectively another camel-route. This route realizes the connection to the event-channel.

@Component
public class EventProducer extends RouteBuilder {

	@Override
	public void configure() throws Exception {

		JacksonDataFormat jsonFormat = new JacksonDataFormat();
		from("direct:eventService")
		.setHeader("event-created").simple("${headers." + Exchange.CREATED_TIMESTAMP + "}")
        .marshal(jsonFormat)
        .to("");
	}

}

Basically it is a tiny route between the endpoint: direct:eventService and the message-bus. If any message is available at the endpoint direct:eventService further meta-data like a timestamp is added, the body is marshaled and finally the message containing an event is sent to the event-channel. In this approach the used message-bus has to provide a header/body concept like busses which work according to the JMS specification.

If a bus doesn’t support such concept, a tiny event-structure like

{
   "header":[
       "event-type":"any type",
       "event-created":"...",
       ...
    ],
    "body":{
      ..."any":"payload"...
    }
}

could be introduced. I go further with that structure to be able to use Apache Kafka – which supports only plain messages without any meta-data.

Eventually, as a kind of business related code, a controller that handles purchases can fire events with the EventService e.g. after a purchase has been created.

@RestController(value = "/")
public class PurchaseController {

	@Autowired
	EventService eventService;

	@RequestMapping(value = "/purchase/", method = { RequestMethod.POST })
	public Purchase createPurchase(@RequestBody Purchase purchase) {
		/*
		 * do what you need to do to create a purchase and then ...
		 */
		PurchasePayload purchasePayload = new PurchasePayload(purchase);

		/*
		 * where it's needed, fire an event reflecting what has happened above
		 */
		eventService.fireEvent("purchaseCreated", purchasePayload);
		return purchase;
	}
...
}

I have to underline that the sketch above describes only one of a couple of possibility to fire events. How to handle different event-types, different event-contents etc. has to be discussed, respectively has to be sketched. But we cross that bridge when we get there.

Obviously the next interesting part would be how to consume events. In our context one of the consumers could be an invoice service that is interested in ‚purchase-created‘-events. The service can decide whether an invoice has to be created and to create one if needed. Fortunately it is almost the same – with just another annotation. A method annotated with @Consumer can handle the messages that appear in the event-channel.

But there’s lot’s of more that can be done with events like: event-streaming, Complex Event Processing (CEP) and even exception handling will be a topic to be sketched. But I start with the simple consumer in the next post.
Stay tuned…

Find a complete runnable Spring Boot code-sketch in github.