An Interest In:
Web News this Week
- April 2, 2024
- April 1, 2024
- March 31, 2024
- March 30, 2024
- March 29, 2024
- March 28, 2024
- March 27, 2024
Using CQRS in Phoenix Contexts
Recently I read this article Naming Phoenix context functions. And I think using naming conventions is good and all, but maybe we could make a step futher and apply CQRS inside such modules.
Command Query Responsability Segregation is the notion that you can use a different model to update information than the model you use to read information...
--- Martin Fowler
I made a small system to store gameplays results of dancing machines such a Pump it Up.
For example this is the cards.ex file.
defmodule Rankmode.Cards.Queries do import Ecto.Query, warn: false alias Rankmode.Repo alias Rankmode.Cards.Card def all() do Repo.all(Card) |> Repo.preload([:user, :game, :mix]) end def for(user: user_id) do from(c in Card, where: c.user_id == ^user_id, order_by: [desc: c.activated_at], preload: [:user, :game, :mix, :profile]) |> Repo.all() end def get!(uid: uid) do from(c in Card, where: c.uid == ^uid, preload: [:user, :game, :mix, :profile] ) |> Repo.one!() end def get(uid: uid) do from(c in Card, where: c.uid == ^uid, preload: [:user, :game, :mix, :profile] ) |> Repo.one() end def get(id: card_id, user: user_id) do from(c in Card, where: c.id == ^card_id and c.user_id == ^user_id, preload: [:user, :game, :mix, :profile] ) |> Repo.one() end def get(:notactivated, uid: uid) do from(c in Card, where: c.uid == ^uid and is_nil(c.activated_at) and is_nil(c.user_id), preload: [:user, :game, :mix] ) |> Repo.one() endenddefmodule Rankmode.Cards.Changesets do alias Rankmode.Cards.Card def new(attrs) do %Card{} |> Card.changeset(attrs) end def empty() do new(%{}) end def activate(id, attrs) do %Card{id: id} |> Card.changeset_activate(attrs) end def update(id, attrs) do %Card{id: id} |> Card.changeset(attrs) endenddefmodule Rankmode.Cards.Commands do import Ecto.Query, warn: false alias Rankmode.Repo alias Rankmode.Cards.Changesets def create(attrs) do Changesets.new(attrs) |> Repo.insert() end def activate(id, attrs) do Changesets.activate(id, attrs) |> Repo.update() end def update(id, attrs) do Changesets.update(id, attrs) |> Repo.update() endenddefmodule Rankmode.Cards doend
And the corresponding Schema file card.ex
defmodule Rankmode.Cards.Card do use Ecto.Schema import Ecto.Changeset schema "cards" do field :uid, :string field :checksum, :string field :activated_at, :naive_datetime belongs_to :mix, Rankmode.Mixes.Mix belongs_to :game, Rankmode.Games.Game belongs_to :user, Rankmode.Accounts.User has_one :profile, Rankmode.Profiles.Profile timestamps() end @optional [:activated_at, :user_id, :mix_id, :game_id] @required [:uid, :checksum] def changeset(model, attrs) do model |> cast(transform(attrs), @optional ++ @required) |> validate_required(@required) |> validate_length(:uid, min: 3, max: 255) |> unique_constraint(:uid, name: :cards_uid_checksum_index) |> unique_constraint(:checksum, name: :cards_uid_checksum_index) end def changeset_activate(model, attrs) do changeset(model, activate(attrs)) end defp checksum(attrs) do Map.merge(attrs, %{checksum: Base.encode16(:crypto.hash(:sha256, Map.get(attrs, :uid, "")))}) end defp activate(attrs) do Map.merge(attrs, %{activated_at: NaiveDateTime.utc_now()}) end defp transform(attrs) do checksum(attrs) endend
My approach is separating the concerns in 4 modules.
Base Module
A base module, that could store helper functions or any other function that does not fit in the other modules.
defmodule Rankmode.Cards doend
Queries Module
A module for mostly SELECT
type functions
defmodule Rankmode.Cards.Queries doend
Commands Module
A module for mostly INSERT
, UPDATE
, DELETE
type functions.
defmodule Rankmode.Cards.Commands doend
Changesets Module
A module for storing the changesets used both in Queries and Commands.
defmodule Rankmode.Cards.Changesets doend
File structure
I prefer storing these in a single file. But if it becomes messy overtime, an structure like this can be used.
cards/ card.ex cards.ex changesets.ex commands.ex queries.ex
Original Link: https://dev.to/clsource/using-cqrs-in-phoenix-contexts-51oa
Dev To
An online community for sharing and discovering great ideas, having debates, and making friendsMore About this Source Visit Dev To