Distributed Events and loose coupling in a microservice architecture

Hi, I’m looking at Axon in the context of small restful services, that then of course use events internally for aggregate updates,etc but also distribute those events externally such that other interested services can act on them as necessary. One thing that I’m trying to address is the fact that each of these small app/services would need to ‘couple’ to the event class definitions themselves, such that they can be properly deserialized in another service that happens to be listening. What is a suggested approach for handling this? I was thinking of making a small library that only contained the event classes, that would be used by both the event producer and consumers, but that would again, seem to introduce coupling that I don’t necessarily want.

Hi,

normally, what I do with relatively strongly related components, is share the ‘core API’ module. This module contains the commands, events and some of the shared value objects and domain services. These objects form the api of your component.
However, this is still relatively tightly coupled. In less related contexts, it is safer to just share the serialized form in which events (and perhaps also commands) are communicated. Then, it is up to each component to figure out how (and which part of) this serialized form is deserialized into objects. Very similar to how soap web services or most rest(like) services work.

Also, not all events are relevant outside of a certain context. Identify which events are important inside, and which outside of the context they’re generated in. For example, information about checkout steps being performed are important within the checkout context. However, the finance context isn’t interested until the order has been confirmed, and will only want to listen to the confirmation event.

On the implementation side, you most likely won’t want to use XStream as serializer. Although it is capable of serializing almost any type of class, it doesn’t produce ‘beautiful’ xml. Jackson (json) could be a good start.

Does that answer your question?
Cheers,

Allard

Hi Allard, that was quite helpful. I'm really trying to get to a "tolerant reader" approach for say, in your example the "outside" finance service. Will play around with it.

Hi Guys,

just curious how would you approach this kind of setup - I have a project that is divided into 5 logical components / microservices by the task / domain they handle.
What I have done is I have a core module where I have the shared value objects and data structures that all the modules use. then each microservice has it’s API where it defines commands, events and DTOs.
The API has the core as a dependency. There are numerous cases when one microservice needs to communicate with another, so the API module of that module is included making the commands and events available. I know that this creates a coupling but it is acting as one system (using a distributed command bus + all modules share the same event store).

The other reason for this set-up was the creation of the query side where messages from all 5 microservices are aggregated and used to build 2 different read models for the APIs used by the management console and customer facing system. This is a separate module having all APIs as dependencies and receiving messages through AMQP.

Dňa streda, 14. januára 2015 22:21:29 UTC+1 erich oliphant napísal(-a):

Hi,

my approach is always to create a “core API” module, that contains the commands, events and (simple) value objects used in the commands and events. If I understand correctly, that’s also what you do. This couples the components only to eachother’s API.
However, in a microservice architecture, or any distributed architecture for that matter, it is a best practice to not depend on any implementation specific code. It’s up to you do decide how important that is. But do keep in mind that you also need to look at the serialized form in which you represent commands and events. Essentially, your coupling is on this representation. So make sure you have your serializer configured properly so that the XML/JSON messages sent over the wire make good sense. This will allow for other (non-java) applications to easily interact with your components.

Cheers,

Allard

Hi Allard,

I found this relatively “old” thread, because it addresses the same thoughts that I have atm.

Question: do I get you right that you suggest to process events indepentdently from their type and to deserialize the payload individually?
Woudn’t that mean that a listener gets may events that are not relevant for it?
I tried to figure out how to setup the listening method without speciying a type but using another attribute to select the event type… maybe could could give me some hint?
Currently I organize the complete inter-service traffic via REST, but maybe using event handlers is more efficient…

Thanks!
Markus

Hi Markus,

I’m not sure I completely understand what you mean with your question, but let me elaborate on my answer here.
The original questions was about whether it’s wise to share classes between services, or whether there should be another way to deal with events in the code base.

Within an Axon-based application, you use classes to represent events. However, between services, no such thing exists. The real thing that couples the services is the serialized form (the bytes) that are communicated between the services. If you use the default serializer (XStream based), these bytes are very tightly coupled to the classes, and therefor will couple your services very tightly.
In general, but especially in services that are part of different bounded contexts, we recommend paying good attention to the serialized form. A Jackson based serializer, for example, will generate output that is much less coupled to the original class structure.
Besides the payload, Axon will also pass the type and revision of an event. These are different field. The default revision is null (which is generally fine) and the default type is the fully qualified class name of the event payload. While this is coupling to the Java class, in general, this can be considered a name + namespace. If the package is chosen carefully, that package represents a good namespace for this event. Good examples are: com.mycompany.somecontext.SomethingHappenedEvent. Bad example: com.mycompany.myapplication.mymodule.domain.events.aggregatename.SomethingHappenedEvent.

Note that you can change these defaults by overriding the typeForClass and classForType methods on the serializer. In future versions, we’ll add an option to add a function in the builder, instead of requiring to override.

Hope this clarifies things.
Cheers,

Allard

Hi Allard,

Thanks a lot, you got me 100% right.

Coupling due to shared classes was exactly what I was worrying about and therefore right now, I couple through RESTful Services.

I also thought about what you suggest (de/serialize with Jackson).
However, in that case, my service would have to listen to any and every message and determine (for example: if (eventClassName.equals(“com.axxelia.MyEvent”))), if it’s relevant for it, right?
Doesn’t that cause a performance loss?

Thanks,
Markus

Hi Markus,

actually, Axon will handle “unknown” events very gracefully, for you. If there is no handler for a specific class, these events will just get ignored. So you would just create classes for the events you care about, even including only the data that you care about. Tell the serializer how to map incoming types to your classes (and tell it to ignore unknown fields) and that’s it. In that case, you just coupled to the type (namespace, name and version/revision of a message) and the elements inside the payload that you care about.

Axon will only deserialize an event when there is actually a handler for it that it interested in the payload.

I would say this makes it even more loosely coupled than when using REST endpoints for inter-service communication.

Cheers,

Allard

Hi, I’m interesting also in this kind of solution. I’ve developed as Allard said in the last response, listening the “unknown” events and compare the payload type as string and routing to corresponding command. So far, it works great and we don’t feel a low performance. In the future we will see.

I’ve been thinking in a alternative. I would like to share with you and in case you consider that it can be useful apply for future developments. To add an attribute, to annotation EventHandler or the manual subscribe, making possible a mapping payload type. This has a problem whether you refactor,move or duplicate the event type. To solve this, it can be considered set a generic id to event type and set up in the subscribing process.

Regards

Hi Carlos,

Could you share a code sample showing how you implemented your current solution?

Thanks,
Markus

Hi Markus, here you have,

`

@EventHandler
public void handle(UnknownSerializedType evt) {

String typeName = evt.serializedType().getName();
switch (typeName) {
case “com.eventclass1”:
handle(evt.readData(eventclass1.class));// To do that I’ve replicate the event class and create and register a converter ByteToeventclass1

break;
case “com.eventclass2”:
handle(evt.readData(eventclass2.class));
break;
}

}

`

To register the converter

`

@Configuration
public class AxonConfiguration {

@Bean
@Primary
public Serializer load(RevisionResolver revisionResolver, ApplicationContext applicationContext) {
Map<String, ObjectMapper> objectMapperBeans = applicationContext.getBeansOfType(ObjectMapper.class);
ObjectMapper objectMapper = objectMapperBeans.containsKey(“defaultAxonObjectMapper”)
? objectMapperBeans.get(“defaultAxonObjectMapper”)
: objectMapperBeans.values().stream().findFirst()
.orElseThrow(() -> new NoClassDefFoundError(“com/fasterxml/jackson/databind/ObjectMapper”));
ChainingConverter converter = new ChainingConverter(this.getClass().getClassLoader());
converter.registerConverter(new ConverterByteToEventClass1(objectMapper));
converter.registerConverter(new ConverterByteToEventClass2(objectMapper));
return JacksonSerializer.builder().revisionResolver(revisionResolver).converter(converter)
.objectMapper(objectMapper).build();
}

}

`

Probably there are better options to do this. It is my first aprouch and it is enough for my actually POC.

Regards

Hi,

for this usecase, you might actually consider overwriting the “classForType” method in your serializer. Implement it to return “eventclass1.class” for hte value “com.eventclass1”, “eventclass2.class” for “com.eventclass2” and otherwise return super.classForType(…). In that case, Axon will deserialize the event data into that specific class, and you can just declare your handlers using the class as parameter on your @EventHandler.

Note that this doesn’t work on the XStreamSerializer, as class data is embedded in the serialized form. For interoperability, we’d recommend using the Jackson based serializer anyway.

Cheers,