Really Simple Architecture components
In the previous post I’ve mentioned the idea of a Really Simple Architecture. This time I’d like to discuss the components of RSA in more detail. Because the whole concept evolves in my mind, by no means treat as any form of reference architecture. At least not yet This is just a thought experiment for now.
First, I must admit I borrowed most of the ideas from Rinat’s blog.
Receptors come in two flavours: internal and external. Both look and work exactly the same.
The difference is the source of the events they receive. An external receptor processes events coming from other service (or services) while an internal receptor reads from its own event store. External event receptors are the only way services can exchange data: one service publishes events that the other service reads and acts upon. Internal receptors can be used to integrate multiple command handlers into a pipeline: they turn events published by one handler into a commands for another.
Unfortunately I could not find a better name for this thing. At least it says exactly what it does: process commands and publish events as a result. In general command handlers can do anything to handle an event but there are two specialization that are especially worth mentioning.
A stateless handler executes logic that does not depend on any persistent state. It just transforms commands to events using some kind of an algorithm.
An aggregate-based handler uses Domain-Driven Design’s Aggregate pattern to structure the logic. An incoming command is mapped to a method call on aggregate object. The method results in generating on or more events. These events are then persisted in the event store in one transaction. Aggregate’s state is loaded into memory each time a command is processed so the processing logic can be based on past events.
This component updates so called view model (or read model) based on processed events.
As with receptors, view projections can be internal (as on picture above) or external (not shown).
They too come in two versions. First transforms commands into events using some external system.
When coupled with an internal receptor, this type of gateway can be used to send commands to an external system. The flow would be following:
- a command handler publishes
- an internal receptor transforms this event to
- a gateway receives the command, sends a message to an external system (e.g. via SOAP), receives a response and publishes the result as an
- an internal receptor transforms this event to
HandleSomethingDonecommand targeted to the command handler that initiated the communication
In short, this type of gateway can be used when external party we are dealing with can reject the message we are sending. Otherwise, the second version of gateway, which is easier to plug into the system, can be used
This type gateway combines two features. It processes an event stream and publishes the data to external systems and can also receive data from this system and inject it to our service in form of commands.
The above list is not complete nor final. It evolves constantly while I try to implement more and more use cases using my RSA. There are, however, much more stable concepts in the architecture. Let’s call them primitives. There are only four of them.
A command is a message that instructs somebody to perform some action. The architecture acknowledges that commands do get duplicated from time to time. Command handlers are expected to deal with this fact of life. To make it possible, a command always carries an ID.
An event is a message that denotes something that has happened in the past. An event may (but is not required to) point to a command that caused it. Events are organized in streams that have business meaning e.g. responses from external party X or events related to service Y. Event streams may or may not guarantee to be free of duplicates. Allowing duplication is a property of an event stream that is dependent on kind of a command handler(s) that emits events to the stream.
An event handler is a thing that processes events from one or more logical event stores. In a result it can either emit a command or update a view. Event handlers can read from views in order to provide context for the processing. An event handler is responsible for assigning proper IDs to generated commands. In case of an event stream that guarantee no event duplication (most published event stream are like that), the easiest way to assign an ID to command is to use event’s unique ID. Otherwise, a business-level ID has to be used.
A command handler is a thing that processes commands from a queue. In a result it emits events to some stream. Event handlers can read from views in order to provide context for the processing. Command handler might provide no duplication guarantee for the event stream(s) it generates. Deduplication can be achieved by loading events generated in the past and checking if any one of them is related to a command being processed. If so, a command is considered a duplicate.
Usually handlers can’t afford loading whole history for each processed command so it leaves two optimization options. Command handler can use some business-level ID carried by a command to decide what stream to emit resulting events to. In such case resulting streams can be short enough to allow to load them before processing each command. In other words, a concept of aggregate can be used. As an alternative, a sliding deduplication window can be used to limit the number of past events that needs to be loaded.
Anything that holds persistent state.
As you may expect, these four primitives are based on something even more fundamental. The Idea behind RSA is that any message in a distributed system (as in real life) can be duplicated. RSA does not try to create an illusion that it is not the case. Rather than, it provides means for creating deduplicated bubbles within the solution where it makes sense.