My Journey into CQRS and Event Sourcing

Using Elixir — Part 3

Rodrigo Botti
Nexa Digital
4 min readOct 10, 2019

--

Three small flasks sealed with corks. From left to right: blue, red and yellow dye are being slowly mixed in each one.
bottles-potions-ink-dye-magic by JalynBryce

Note: This is part of "My Journey into CQRS and Event Sourcing" series. If you're jumping in right now, please consider reading the previous ones and if you like what read here, please consider reading the next ones.
<< Part 1 | < Part 2 | Part 4>

Recap

Last time we exposed the Consent Service as an HTTP REST-ish API using the Phoenix Framework:

  • Command-side api exposed as POST requests
  • Read-side api exposed as GET requests

Introduction

This time we'll tackle another element of the CQRS + Event Sourcing architecture pattern: the Event Handler.

We'll develop an Event Handler that will be responsible for notifying the patient when another system actor — another patient or a health care professional — asks the patient for data access consent.

By using an Event Handler we'll handle the side effect of notifying a user totally separate from the command-side API i.e. we'll not impact request/command processing in any way. Plus we know that all the business logic has already been validated by our aggregates, command handlers and command middlewares. So notification will only be triggered after all business invariants have been validated.

The Application

Once again we'll use the building blocks from the Commanded Framework we set up in Part 1.

Thankfully for writing our own Event Handler in Commanded we don't need to add any other external library/dependency.

Note: application code can be found in this repository in the branch part-3

Feature

As stated above, we should issue a notification to the patient when another system actor asks it for consent.

Let's remember how asking for consent actually works:

  • A system actor issues a request asking for consent —
    POST /patients/:patient_id/consents/ask
  • An AskConsent command is dispatched reaching the Patient aggregate
  • After business requirements validation the aggregate emits the ConsentAsked event to the event store

From the description above we can see that all we have to do is listen for the ConsentAsked event and use the data form the event to issue a notification to the patient.

Note: We won't actually notify the user — email/sms/push notification. We'll simulate it by just logging the message.
Also we won't fetch any user information required for notification — name/phone/email/etc — , we'll pretend we're calling another service that actually has this information.

Event Handler

Implementing an Event Handler in Commanded is pretty straight forward.
We only need two things:

  • The event handler module: implements the Commanded.Event.Handler behaviour.
  • Start the handler — preferably under a supervision tree — : we'll use the application's supervisor.
# lib/consent/handlers/notify_patient.exdefmodule Consent.EventHandlers.NotifyPatient do
use Commanded.Event.Handler,
name: __MODULE__,
start_from: :current
alias Consent.Events.ConsentAsked def handle(
%ConsentAsked{
by_entity: entity,
by_id: id,
patient_id: patient_id,
target: permission
},
_meta
) do
with {:ok, patient} <- get_patient_contact(patient_id),
{:ok, practitioner} <- get_practitioner_contact(entity, id),
message <- build_message(patient, practitioner, permission),
:ok <- send_message(patient.phone, message) do
:ok
end
end
# private functions omitted for brevityend

As you can see in our event handler all we have to do is react to an emitted event in the handle function.

@spec handle(event, meta) ::
:ok | {:error, :already_seen_event} | {:error, reason :: any()}

We pattern match on the event we're interested and use the data from the event to execute the steps needed in order to notify the patient. If all goes well we return :ok atom as required by Commanded to indicate success.

Conclusion

Using event handlers to react to domain events and execute side-effects proved easy. I believe this is one of the aspects where the architecture patterns truly shine. In part 1 we had already reacted to domain events to build our query model but we could easily build another handler to take care of a completely different aspect of the application.

By having this separation of concerns between the command model and query model and by emitting events instead of mutating shared state gives a lot of flexibility for evolving the application. This was made clearer by how easy it was to implement a feature by simply building and plugging an event handler.

Comments

  • Instead of notifying the user directly from the consent api we probably should just stream the event over to a notification service.
  • Even though we're just pretending to call a remote service and not actually doing it, the code for doing that should be in separate modules. I put it as private functions of the handler itself just for brevity.
  • Once again, thank you again for staying up until this point.

--

--