Getting started
Let’s start building our Conduit web application. We’ll be using the latest version of the Phoenix Web framework, currently 1.3.
Before proceeding you will need to have Elixir v1.5 or later installed. Please follow the official installation guide to get Elixir running on your operating system. There are instructions for Windows, Mac OS X, Linux, Docker, and Raspberry Pi.
Installing Phoenix
Install the latest version of Phoenix using the mix command:
$ mix archive.install https://github.com/phoenixframework/archives/raw/master/phx_new.ez
Generating a Phoenix project
Once installed, you use the mix phx.new command to create a new Phoenix 1.3 project:
$ mix phx.new conduit --module Conduit --app conduit --no-brunch --no-html
A project in the conduit directory will be created.
The application name and module name have been specified using the --app and --module flags respectively. The Conduit frontend will be provided by one of the existing frameworks so we omit Phoenix’s HTML and static asset support, provided by Brunch, by appending --no-brunch --no-html to the generator command.
We will temporarily comment out the Ecto repo, Conduit.Repo, in the main Conduit application, to allow the server to be started without a database. We can also take the opportunity to remove Phoenix’s pub/sub dependency and channels/sockets as they will not be used for our RESTful API.
Starting the Phoenix server
Fetch mix dependencies and compile:
$ mix do deps.get, compile
Run the Phoenix server:
$ mix phx.server
Visit http://localhost:4000/ in a browser to check the server is running. An error is shown as no routes have been defined yet.
[info] GET /
[debug] ** (Phoenix.Router.NoRouteError) no route found for GET / (Conduit.Web.Router)
(conduit) lib/conduit_web/router.ex:1: Conduit.Web.Router.__match_route__/4
(conduit) lib/phoenix/router.ex:303: Conduit.Web.Router.call/2
(conduit) lib/conduit_web/endpoint.ex:1: Conduit.Web.Endpoint.plug_builder_call/2
(conduit) lib/plug/debugger.ex:123: Conduit.Web.Endpoint."call (overridable 3)"/2
(conduit) lib/conduit_web/endpoint.ex:1: Conduit.Web.Endpoint.call/2
(plug) lib/plug/adapters/cowboy/handler.ex:15: Plug.Adapters.Cowboy.Handler.upgrade/4
(cowboy) /Users/ben/src/conduit/deps/cowboy/src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4
Commanded facilitates CQRS/ES in Elixir
We will use Commanded6 to build our Elixir application following the CQRS/ES pattern. Commanded is an open source library that contains the building blocks required to implement CQRS/ES in Elixir.
It provides support for:
- Command registration and dispatch.
- Hosting and delegation to aggregates.
- Event handling.
- Long running process managers.
You can use Commanded with one of the following event stores for persistence:
- EventStore Elixir library, using PostgreSQL for persistence.
- Greg Young’s Event Store.
Your choice of event store has no affect on how you build your application.
For Conduit we will use the PostgreSQL based Elixir EventStore as we will also be using PostgreSQL for our read model store and the Ecto database query library.
Write and read model stores
Applications applying the CQRS pattern have a logical separation between the write and read models. You can choose to make these physically separated by using an alternative database, schema, or storage mechanism for each.
In Conduit, we will be using event sourcing to persist domain events created by our write model. These events are the canonical source of truth for our application, they are used by both the aggregates to rebuild their state and are projected into the read model store for querying. Since the read model is a projection built from all domain events in the event store, we can rebuild it from scratch at any time. To rebuild a read store, the database is recreated and then populated by projecting all of the domain events.
We will use two databases for Conduit: one for the event store; another for the read model.
The database naming convention is to suffix the storage type (event store or read store) and environment name to the application name (conduit):
| Environment | Event store database | Read store database |
|---|---|---|
| dev | conduit_eventstore_dev |
conduit_readstore_dev |
| test | conduit_eventstore_test |
conduit_readstore_test |
| prod | conduit_eventstore_prod |
conduit_readstore_prod |
Installing and configuring Commanded
We’ll be using the following open source libraries, published to Hex, to help build Conduit:
-
commanded- used to build Elixir applications following the CQRS/ES pattern. -
eventstore- an Elixir event store using PostgreSQL as the underlying storage engine. -
commanded_eventstore_adapter- adapter to use EventStore with Commanded.
The Commanded README details the steps required to install and configure the library.
- Add
commandedandcommanded_eventstore_adapterto the list of dependencies in mix.exs:defpdepsdo[{:commanded,"~> 0.15"},{:commanded_eventstore_adapter,"~> 0.3"},# ...]end - Include
:eventstorein the list of extra applications inmix.exs:defapplicationdo[extra_applications:[:logger,:eventstore,],# ...]end - Configure Commanded to use the EventStore adapter in the mix config file (
config/config.exs):config:commanded,event_store_adapter:Commanded.EventStore.Adapters.EventStore - Configure the event store database in each environment’s mix config file (e.g.
config/dev.exs):# Configure the event store databaseconfig:eventstore,EventStore.Storage,serializer:Commanded.Serialization.JsonSerializer,username:"postgres",password:"postgres",database:"conduit_eventstore_dev",hostname:"localhost",pool_size:10 - Fetch and compile the dependencies:
$mixdodeps.get, deps.compile - Create the event store database and tables using the mix task:
$mixdoevent_store.create, event_store.init
Configuring the read model store
Ecto will be used for building and querying the read model. The Phoenix generator will have already included the phoenix_ecto dependency, which includes ecto, and generated config settings for each environment.
- Configure the
Conduit.RepoEcto repository database in each environment’s mix config file (e.g.config/dev.exs):# Configure the read store databaseconfig:conduit,Conduit.Repo,adapter:Ecto.Adapters.Postgres,username:"postgres",password:"postgres",database:"conduit_readstore_dev",hostname:"localhost",pool_size:10 - Create the read store database:
$mix ecto.create
There are now two databases in our development environment:
- Read model store,
conduit_readstore_dev, containing only the Ecto schema migrations table. - An event store,
conduit_eventstore_dev, containing the default event store tables.