artem

artem

Commanded and non-commanded part of the system: how to organize databases and testing

I am learning Commanded by doing a simple project that’s just yet another wrapper for a ChatGPT conversation + buttons to tune the conversation. I’ve got to the point where something works: I can emit commands, events are generated and projections happen (via ecto projections).. and got issues with testing it properly which are possibly due to not so ideal database organization in the first place.

**Context and origin of the problem**
Not all of the system state is in CQRS. Personal information is not good to keep in a system from which it’s pretty hard to remove anything (because GDPR and general respect to people wishes for self-deletion). And then there’s a bunch of “standard” public web service functionality: authentication/login/accounts, password recovery and similar stuff that I wouldn’t be excited to implement in CQRS from scratch.

These “non-core” part of the system I grabbed from https://livesaaskit.com , it is more or less traditional LiveView Phoenix app starter with PostgreSQL-Ecto ad a bunch of tests. Commanded part I consider a “core” part of the system and it lives quite a standalone life except that its Ecto projections are displayed in Live View app and Live View app emits commands into this “core”.

Thus I ended up with three conceptual databases stored in two PostgreSQL schemas:

  1. Original LiveView app repository
  2. Separate schema for EventStore. I was not sure how it should happen, documentation and how mix tasks worked seemed [to me] to indicate that it’s a good idea to have it as a separate database, so main source of truth could have some special separate care
  3. Projections living in the same schema as original LiveView app and designed to be accessed by that same LiveView app via Ecto

**The problem**
Problems started when I wrote my first commanded tests following Commanded wiki and conduit app steps. Tests of original LiveView app and commanded seem to be using quite incompatible approaches.

Traditional LiveView app uses Ecto’s SQL Sandbox for testing. If I get it correctly, every test is running within an own transaction which makes it easy to clean the state (transaction is just rolled back) and also allows for running tests in parallel (as separate transactions don’t see effects of each other).

Commanded tests judging from commanded wiki and conduit run on the live [test] databases and just clean everything via SQL’s TRUNCATE at the start (or stop) of each test.

Naturally it is quite difficult to run both kinds of tests in parallel. After all non-commanded test may go over the pages/functionality that touches Commanded that “might just be deleted by the commanded test”.

**How to organize things properly**
While my problem at hand is specifically about testing, I’d love to use opportunity to learn and figure how things should be organized the proper way (somehow learning seems more efficient after you step into issues :slight_smile: )

It would be great to get advice on the following things:

  1. Does the whole idea of having both Commanded and non-commanded CRUD architectures in the same not-too-big service make sense in the first place? Is it how other people do it or do you implement all the logins/signups/emails from scratch in CQRS?
  2. Does it make sense to separate data repositories as I did it (CRUD parts to live in the same repo as Projections, EventStore - separately) or shall it be somehow different like e.g. 3 completely different schemas or just one to rule them all?
  3. How do you (or would you) organize tests to for both CRUD and Commanded parts of the system?
    • Shall I just stop running just mix tests and have a batch script to call the different test bunches sequentially (e.g. as mix test test/crud and mix test test/core)?
    • Or would you get rid of SQL Sandbox and just use the Commanded way for “reset everything” for both kinds of tests (maybe with --max-cases 1 so tests do not have DB purged in the middle of running)?
    • Or would it make more sense the other way around to give each CRUD test case an individual clean EventStore (probably in memory as one real DB schema per test case probably would be too difficult)? And just for the sake of test make commanded projections use separate test database?
    • Or something completely different?
  4. Given that commanded tests (per wiki and conduit approaches) wipe everything at the start (or end of each test), how are they executed in parallel? Or is the usual commanded tests way to run them via mix test --mac-cases 1?

Most Liked

qhwa

qhwa

Just come to this thread after a Google search. I can share some practice with resetting the read store.

The recommended testing approach by Commanded community conflicts with Phoenix’s sandbox one.

I believe sandbox is the better way because of concurrent testing, so I also spent some time trying to make the read store repo run in sandbox mode. It turns out that it’s easier than I thought since it already is. What I did was to remove the manual resetting (truncates).

Here’s what testing support files look like:

# file: test/support/storage.ex
defmodule MyAp.Storage do
  @moduledoc """
  Clear the event store and read store databases
  """

  alias EventStore.Storage.Initializer

  def reset! do
    reset_eventstore()

    # For read store, it is expected to be automatically
    # rolled back by the transactional tests, as we are
    # using sandbox mode of the pool.
  end

  defp reset_eventstore do
    config = Investracker.EventStore.config()

    {:ok, conn} = Postgrex.start_link(config)

    Initializer.reset!(conn, config)
  end
end

# file: test/support/data_case.ex
defmodule MyApp.DataCase do
  @moduledoc """
  Test case template with data cleaning up.
  """

  ...

  using do
    quote do
      import Ecto
      import Ecto.Changeset
      import Ecto.Query
      import Commanded.Assertions.EventAssertions
      import unquote(__MODULE__)
    end
  end

  setup tags do
    {:ok, _} = Application.ensure_all_started(:my_app)

    setup_sandbox(tags)

    :ok
  end

  @doc """
  Sets up the sandbox based on the test tags.
  """
  def setup_sandbox(tags) do
    pid =
      Ecto.Adapters.SQL.Sandbox.start_owner!(
        Investracker.Repo,
        shared: not tags[:async]
      )

    on_exit(fn ->
      :ok = Application.stop(:my_app)

      # clean event store
      MyApp.Storage.reset!()

      # clean read store with the help of sandbox rollback
      Ecto.Adapters.SQL.Sandbox.stop_owner(pid)
    end)
  end
end

Ideally we can also employ sandbox to reset event store, too, but I haven’t run into any issue yet, so I leave it as it is.

After a reset of the read store with MIX_ENV=test mix ecto.reset, everything seems working now.

Where Next?

Popular in Questions Top

beno
I will often find my self writing things similar to: case some_value do nil -> something() "" -> something() _ -> somethi...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
tduccuong
Hi, is there any work on GUI with Elixir, that is similar to Electron/Javascript? My idea is to bundle Phoenix and BEAM into a single se...
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New

Other popular topics Top

axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 48342 226
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 43757 214
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
klo
Got a question about when to concat vs. prepending items to list then reversing to achieve appending. So i know lists boil down to [1 | ...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New

Latest on Elixir Forum

Elixir Forum

We're in Beta

About us Mission Statement