CQS is a well-known concept from the early age of programming introduced by Bertrand Meyer in the Eiffel language.

It says that every action should be either a command (that performs an action) or a query (that returns some data) but not both at the same time.

It was devised to draw a clear line between performing mutations and collecting results.

Meyer, Bertrand. “Eiffel: a language for software engineering”

The problem which appears from time to time in many languages is related to changing the state or causing side effects while asking for and returning results. It’s basically a mixed responsibility of one action and should be treated like an anti-pattern.

Consider the Rails first_or_create example:

User

.where(email: "kamil@lelonek.me")

.first_or_create whereemail"kamil@lelonek.me"

Finds the first record with the given attributes, or creates a record with the attributes if one is not found.

Moreover:

Please note this method is not atomic, it runs first a SELECT, and if there are no results an INSERT is attempted. If there are other threads or processes there is a race condition between both calls and it could be the case that you end up with two similar records.

Do you see the possible pitfalls? I’m sure I don’t have to explain here things that may go wrong. You probably already understand why this is considered as a bad practice and how it makes harder to reason about the code.

Mutators & Loaders

I’d like to present the Elixir approach I’ve been using for over a couple of years already. It helps me to maintain a clear separation between reading and writing. It makes me sure that actions I call are safe and do nothing more than I would expect. It’s basically a reusable framework for CQS handling in a functional way with Elixir.

Use case

We will be managing (storing and reading) recent media (e.g. posts, videos, pictures) for a particular social media platform (e.g. Facebook, YouTube, Instagram) and an influencer (e.g. beyonce, elonmusk, cristiano).

We will have to take a look at the following components:

Migration

Schema

Changeset

Mutator

Query

Loader

The first 3 ones represent the infrastructural part so, unless you are a beginner, you can jump straight to the Mutator definition. Otherwise, let’s start from the simplest one.

Migration

Ecto.Migration is used to modify your database schema over time.

➜ cqs git:(master) mix ecto.gen.migration CreateRecentMedia

* creating priv/repo/migrations

* creating priv/repo/migrations/20180126093645_create_recent_media.exs

Here is our particular implementation:

defmodule Cqs.Repo.Migrations.CreateRecentMedia do

use Ecto.Migration def change do

create table(:recent_media) do

add :influencer_handle, :citext, null: false

add :platform_name, :citext, null: false

add :media, {:array, :map}, null: false

end

end

end

In the recent_media table we store influencer_handle as a case-insensitive character string type, the same for platform_name while media as a list of maps which represents content of what we fetched.

➜ cqs git:(master) ✗ mix ecto.create

The database for Cqs.Repo has been created ➜ cqs git:(master) ✗ mix ecto.migrate

10:37:19.250 [info] == Running CQS.Repo.Migrations.CreateRecentMedia.change/0 forward 10:37:19.250 [info] create table recent_media 10:37:19.309 [info] == Migrated in 0.0s

The created table definition looks as follows:

cqs_repo_dev=# \d+ recent_media Table "public.recent_media" Column | Type | Nullable |

-------------------+---------+----------+

id | bigint | not null |

influencer_handle | citext | not null |

platform_name | citext | not null |

media | jsonb[] | not null | Indexes:

"recent_media_pkey" PRIMARY KEY, btree (id)

Under the hood, media column has a decomposed binary format type— JSON array in PostgreSQL.

Schema

In the simplest case, an Ecto.Schema is used to map data coming from a table into Elixir structs.

Let’s see our example:

defmodule Cqs.RecentMedia do

use Ecto.Schema schema "recent_media" do

field :influencer_handle, :string

field :platform_name, :string

field :media, {:array, :map}

end

end

It represents exactly what we defined in the previous step. Here media will be casted to a list of maps in Elixir while both influencer_handle and platform_name are gonna be treated as strings.

Changeset

Ecto.Changeset allows casting and validation when manipulating structs. Both validations and constraints are ultimately turned into errors in case something goes wrong. Some of them (like in our case) can be executed without a need to interact with a database.

Our Changeset looks as follows:

defmodule Cqs.RecentMedia.Changeset do

import Ecto.Changeset alias Cqs.RecentMedia @params_required ~w(influencer_handle platform_name media)a

@params_optional ~w()a def build(schema \\ %RecentMedia{}, params) do

schema

|> cast(params, @params_required ++ @params_optional)

|> validate_required(@params_required)

end

end

We are just focusing on two things here: validating if all the params exist and have the correct types according to our Schema definition.

Mutator

Finally, our special Mutator component comes. However, to be honest, there’s nothing special about it in reality. It just leverages previously defined Changeset and operates on Repo to insert a record into a database.

There are no read operations. Only write, and, in our case, only one (but can be more of course).

defmodule Cqs.RecentMedia.Mutator do

alias Cqs.{Repo, RecentMedia.Changeset} def create(params) do

params

|> Changeset.build()

|> Repo.insert()

end

end

Query

Query is not a self-sufficient module actually. It’s an auxiliary component for Loader (explained further) similar to what Changeset for Mutator is.

defmodule Cqs.RecentMedia.Queries do

import Ecto.Query alias Cqs.RecentMedia def all,

do: RecentMedia def one(platform_name, influencer_handle) do

from rm in all(),

where: rm.influencer_handle == ^influencer_handle,

where: rm.platform_name == ^platform_name

end

end

It defines a bunch of helper functions representing database queries in terms of our RecentMedia definition.

all/0 is jut a wrapper over our Schema and one/2 allows us to fetch RecentMedia by platform_name and influencer_handle there.

Loader

This module is leveraging previously defined Queries and applies them on Repo .

There are no write operations. Only read, and, in our case, just two of them (but can be more of course).

defmodule Cqs.RecentMedia.Loader do

alias Cqs.{Repo, RecentMedia.Queries} def all,

do: Repo.all(Queries.all) def one(platform_name, influencer_handle) do

platform_name

|> Queries.one(influencer_handle)

|> Repo.one()

end

end

The functions correspond to these ones defined in Queries and they are there to apply on Repo .

Subscribe to get the latest content immediately

https://tinyletter.com/KamilLelonek

Usage

Now, with a set of well-designed components, you are able to manipulate RecentMedia in our system with clear boundaries and separation.

Firstly, let’s construct RecentMedia directly:

iex(1)> %Cqs.RecentMedia{}

%Cqs.RecentMedia{

__meta__: #Ecto.Schema.Metadata<:built, "recent_media">,

id: nil,

influencer_handle: nil,

media: nil,

platform_name: nil

}

Then, create one using Mutator :

iex(2)> Cqs.RecentMedia.Mutator.create(%{platform_name: "x", influencer_handle: "y", media: []}) 13:35:05.032 [debug] QUERY OK db=5.0ms

INSERT INTO "recent_media" ("influencer_handle","media","platform_name") VALUES ($1,$2,$3) RETURNING "id" ["x", [], "y"]

{:ok,

%Cqs.RecentMedia{

__meta__: #Ecto.Schema.Metadata<:loaded, "recent_media">,

id: 1,

influencer_handle: "y",

media: [],

platform_name: "x"

}}

And finally, read it from the database:

iex(3)> Cqs.RecentMedia.Loader.one("x", "y") 13:35:32.002 [debug] QUERY OK source="recent_media" db=4.4ms decode=2.7ms

SELECT r0."id", r0."influencer_handle", r0."platform_name", r0."media" FROM "recent_media" AS r0 WHERE (r0."influencer_handle" = $1) AND (r0."platform_name" = $2) ["x", "y"]

%Cqs.RecentMedia{

__meta__: #Ecto.Schema.Metadata<:loaded, "recent_media">,

id: 1,

influencer_handle: "y",

media: [],

platform_name: "x"

}

Summary

As you can see, with a couple of simple modules, we were able to introduce quite a complex solution into our application design. By using presented components you can keep your code clean and business logic separated.

It’s easy to describe how they are related to one another and what exactly they use. Schema is used by both parts the same as Repo . The other components are divided and clearly belong to separate contexts.

Now, I hope you will be using this pattern in your projects to take the most from functional approach and advanced system architecture.

Acknowledgments

Finally, I’d like to thank the following guys who took a significant role in developing, testing and supporting the presented concepts here:

Michał Muskała for developing these components

Mariusz Żak for tuning up some of the ideas

Michał Załęcki for testing and validating the concept

GitHub

If you are interested in an example repository, you can find it here: https://github.com/KamilLelonek/cqs-in-elixir