My Journey into CQRS and Event Sourcing

Using Elixir — Part 2

Rodrigo Botti
Nexa Digital
5 min readSep 26, 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 3 >

Recap

Last time we started by explaining the theoretical basis of the architectural patterns and started building an Elixir application (revolving around medical consent):

  • higher level api with a command/write side and a query/read side
  • command side: dispatches commands to aggregates producing domain events
  • query side: event handlers build projections into a relational database optimized for the specific business case requirements

Introduction

Now that we have a working application it's time we tackle some items from the todo list left at the end of the previous article, more specifically exposing the application as a HTTP/REST api.

There are several ways of doing that, this time I'll be using the Phoenix Framework — "A productive web framework that does not compromise speed or maintainability."
Why phoenix? It's pretty much the de-facto web framework for Elixir. It is mature and rich in its feature set. It might be an overkill for a simple application such as this, but it was so easy to configure and integrate it into the project that this was not a problem.

Phoenix follows a pretty "standard" MVC — Model-View-Controller — pattern. If you are familiar with the pattern and/or other MVC web frameworks such as ruby on rails or django you'll be right at home.

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

The Application

Phoenix comes with its own CLI productivity tool for helping with scaffolding a brand new application and creating framework components (such as Controllers, Views, etc). We'll take advantage of some of these features to add a web-tier functionality to our existing application.

Dependencies, Structure and Configuration

Note: I'll be honest, I sort of cheated a little bit when incorporating phoenix into the application. Since it's CLI does such a great job at creating a fully functioning project scaffold, I decided to take advantage of it: instead of adding it to the existing project I created a brand new phoenix project and added the code produced previously (copy and paste) and did some git trickery in order to keep the history and remote origin.

# for scaffolding a new project:mix phx.new consent_api --module Consent --database postgres --no-webpack --no-html# code copy and git trickery omitted ...

We'll have five new phoenix dependencies added to our mix.exs file:

{:phoenix, "~> 1.4.10"},
{:phoenix_pubsub, "~> 1.1"},
{:phoenix_ecto, "~> 4.0"},
{:plug_cowboy, "~> 2.0"},

We'll also have a brand new configuration in our config/config.exs and config/dev.exs scripts:

# config.exsconfig :consent, ConsentWeb.Endpoint,
url: [host: "localhost"],
secret_key_base: "*** omitted ***",
render_errors: [view: ConsentWeb.ErrorView, accepts: ~w(json)],
pubsub: [name: Consent.PubSub, adapter: Phoenix.PubSub.PG2]
# dev.exs
config :consent, ConsentWeb.Endpoint,
http: [port: 4000],
debug_errors: true,
code_reloader: true,
check_origin: false,
watchers: []

This configures phoenix's Endpoint. More on that later…

Endpoint

"[…] a supervised process. Each request begins and ends its lifecycle inside your application in an endpoint. The endpoint handles starting the web server and transforming requests through several defined plugs […]" — Official Docs

In a phoenix application we start the application's endpoint as a supervised process in the application's supervision tree.

Router

Routers are responsible for parsing and matching incoming requests and dispatching them to the correct handler — they very similar to routers in NodeJS web libraries such as express in that they allow you to configure path+verb matchings and middleware components.

We'll configure our router to dispatch incoming requests to an application controller while also parsing json encoded request bodies and serializing responses bodies as json encoded strings:

defmodule ConsentWeb.Router do
use ConsentWeb, :router
# plug pipeline -- "middleware"
pipeline :api do
plug :accepts, ["json"]
end
# request matching and dispatch
# we'll dispatch to a specific controller function
scope "/api", ConsentWeb do
pipe_through :api
scope "/api", ConsentWeb do
pipe_through :api
# dispatch to ConsentController.ask_consent/2
post "/patients/:patient_id/consents/ask", ConsentController, :ask_consent
post "/patients/:patient_id/consents/grant", ConsentController, :grant_consent
post "/patients/:patient_id/consents/revoke", ConsentController, :revoke_consent
get "/patients/:patient_id/consents", ConsentController, :list_consents
get "/patients/:patient_id/consents/filter", ConsentController, :has_consent?
end
end

Controller

Now we have to implement the controller module's functions that we rely on in the previously defined router.

Controllers are modules that provide functions to handle the requests.
These request handling functions — also called actions — follow the pattern:

@spec action(Plug.Conn, Map) :: Plug.Conn

The first parameter is always conn, a struct which holds information about the request such as the host, path elements, port, query string, and much more. conn, comes to Phoenix via Elixir’s Plug middleware framework. More detailed info about conn can be found in plug’s documentation.

The second parameter is params. Not surprisingly, this is a map which holds any parameters passed along in the HTTP request — body, path and query. It is a good practice to pattern match against params in the function signature to provide data in a simple package we can pass on to rendering.

Now let's implement our controller: upon receiving a request we'll extract the parameters, forward them to the higher level api — built previously — handle it's result and render a json response accordingly. Let's show this by implementing one command side request and one query side request:

defmodule ConsentWeb.ConsentController do
use ConsentWeb, :controller
# fallback actions: not discussed
action_fallback ConsentWeb.FallbackController
alias Consent.Api, as: ConsentApi # Command side request
def grant_consent(conn, %{
"patient_id" => patient_id,
"to_entity" => to_entity,
"to_id" => to_id,
"target" => target
}) do
with :ok <- ConsentApi.grant_consent(patient_id, to_entity, to_id, target) do
conn
|> put_status(:ok)
|> json(%{patient_id: patient_id, action: :grant_consent})
end
end
# Query side request
def list_consents(conn, %{"patient_id" => patient_id}) do
list = ConsentApi.consents(patient_id)
conn
|> put_status(:ok)
|> json(%{data: list})
end
end

Note that the functions follow the action spec and that the names match those we configured in the router.

Note: For brevity I skipped on showing the V part of the MVC i.e Views. Even though we are simply serializing data as json we could forward the rendering logic to a view — this is how it is implemented in the actual codebase. It can be achieved by calling render(conn, name, params) instead of json(conn, data).

Conclusion

Since last time we made the bulk of our application and had already exposed it through a higher level api it became pretty straight forward to expose it as an HTTP service.
All we had to do was: have a web server to receive requests and send responses and implement a very thin logic of parsing request parameters, calling the higher level api, handle its results and serializing a response accordingly.
With phoenix this meant: configuring and starting the application endpoint — supervised — , configuring request matching and forwarding rules in a router and implement a controller.

I chose to use Phoenix but we could have achieved similar results using other means — cowboy + Plug for example.

Comments

Unfortunately this part of this series of articles doesn't have much in terms of actual CQRS and Event Sourcing, it's just about building on top of our previously defined application.
But don't worry, there's much on my todo list to learn about these patterns, so stay tuned for more.
Thanks for staying up until this point. This started as way for me to learn about these topics but I hope it is also helping some of you out there that are also starting to learn about them or about Elixir in general.

--

--