Arc for Elixir is an opinionated CQRS application framework for Phoenix and Plug on Cratis Chronicle, with model-bound commands and queries, validation, authorization, observable queries and opt-in TypeScript proxies for React.

Hex.pm Hex Docs License: MIT Discord

Source preview. The package is built and tested here, but the API may still change, and nothing is claimed beyond what Limits and gaps says is verified.

Overview

You write a command or a query as a plain module. Arc binds, validates, authorizes and runs it, records the events it returns in Chronicle, and serves it to React, LiveView or any HTTP client.

defmodule Library.Authors.Registration.RegisterAuthor do
  @moduledoc "Registers a new author."

  use Arc.Command

  schema do
    field :id, Library.Authors.AuthorId, key: true, required: true
    field :name, :string, required: true, min_length: 2, max_length: 50
  end

  def handle(%__MODULE__{name: name}, _ctx),
    do: {:append, %Library.Authors.AuthorRegistered{name: name}}
end
  • use Arc.Command / use Arc.ReadModel — declare commands and queries; fields, rules and authorization are declarations, handle/2 is the logic
  • The Arc HTTP contract on Plug — @cratis/arc, @cratis/arc.react and Cratis Components work against it unchanged
  • Phoenix and LiveView — the same commands and queries, in process, with forms that show pipeline errors on the right field
  • Observable queries — re-run when Chronicle (or MongoDB) changes a read model, pushed over the multiplexed WebSocket or Server-Sent Events hub, in full or delta mode
  • Tenancy — header, query string, claim or subdomain resolvers select the tenant, which scopes the Chronicle namespace and the MongoDB database
  • MongoDB — cratis_arc_mongodb: tenant-aware databases, naming and BSON conventions, connection resolution and change stream observation
  • Cross-cutting filters — authorize a whole namespace of commands and queries in one place
  • Opt-in TypeScript proxies — mix arc.gen.proxies generates typed commands and queries, with the same validation as the server, only if you add the compiler; they can sit beside the Elixir they come from, as vertical slices
  • Identity — /.cratis/me, typed identity details, development users for Lens, signed forwarded identity and on-behalf-of, as Arc on .NET
  • The exact result envelopes — CommandResult and QueryResult match the Arc contract field for field, pinned by specs
  • Chronicle underneath — returned events are appended with the caller's identity and correlation; constraint rejections reach the client as validation results
  • Arc.Test — the real pipeline against Chronicle's in-memory kernel

Arc for Elixir works with Chronicle only. It is part of the Cratis family of Arc implementations for .NET, Kotlin and Java, Go and TypeScript, and follows the same HTTP contract.

Install

defp deps do
  [
    {:cratis_arc, "~> 0.1"}
  ]
end

Arc needs Elixir 1.18 or later and brings cratis_chronicle with it. Phoenix, Phoenix.HTML and Phoenix LiveView are optional: the LiveView support compiles only when phoenix_live_view is a dependency. CI builds and tests with Elixir 1.19.5 on Erlang/OTP 28.5.

Add import_deps: [:cratis_arc] to .formatter.exs so field :name, :string is not rewritten with parentheses.

Prerequisite: Chronicle running

docker pull cratis/chronicle:latest-development
docker run -d --name chronicle \
  -p 127.0.0.1:35000:35000 \
  -p 127.0.0.1:27017:27017 \
  cratis/chronicle:latest-development

The 127.0.0.1: prefixes keep both ports on your machine: the development kernel accepts well-known credentials and its MongoDB has no authentication.

Getting started

Get started builds an authors feature end to end: events, a command, a query, the HTTP API, a LiveView page and a test.

Documentation

Samples

SampleShows
Samples/plugThe smallest Arc application: Bandit and a Plug router
Samples/phoenixThe HTTP contract, LiveView and a React app served from one Phoenix endpoint
Samples/frontendA React app on the generated proxies

Samples/run-tests.sh builds and tests all three.

Contributing

cd Source/arc
mix deps.get
mix format --check-formatted
MIX_ENV=test mix compile --warnings-as-errors
mix test

cd ../..
Samples/run-tests.sh
ContractTests/run.sh

Pull requests need exactly one release label (major, minor, patch or no-release) and a description written as a release note; Build and release explains both. Design decisions are recorded in decisions. Questions and ideas are welcome on Discord; report confirmed bugs as issues.

License

MIT. See LICENSE.