I’ve built an Elixir CQRS Event Store library (eventstore).
It currently only supports single instance usage. You configure the PostgreSQL connection in your mix environment config (e.g. config/dev.exs
).
config :eventstore, EventStore.Storage,
username: "postgres",
password: "postgres",
database: "eventstore_dev",
hostname: "localhost",
pool_size: 10
Then you use the EventStore
module to append/read events to/from the database.
{:ok, events} = EventStore.read_stream_forward(stream_uuid)
Internally, it uses supervised named processes, such as a stream registry, event publisher, and storage pool. I’d like to allow consumers to configure multiple instances of an event store. So you could configure one per Elixir umbrella app (microservice), or multiple event stores in a single app.
What is the best practice in Elixir to achieve this? I’m currently thinking of following the pattern used by Ecto. Where you use Ecto.Repo
in your own module and specify the name of the configuration to use via opt_app
.
defmodule Sample.Repo do
use Ecto.Repo,
otp_app: :my_app
end
How do you handle named processes with this approach?
The downside of this approach is that it obfuscates the public API from the consumer. Using the Ecto example above, there is no visibility of the public API functions available in Sample.Repo
that are included by the using macro. Is there an alternative approach?
2 Likes
The approach you mentioned with use
is the most common and clean approach I have seen. It used by many libraries such as Ecto
with Ecto.Repo
(as you already pointed out), by Gettext with use Gettext
, by Fluxter with use Fluxter
and many others.
The approach you can take to handle named processes is prefix everything with the module that calls use ...
. For example, if you have
defmodule MyApp.EventStore do
use EventStore
end
You could register processes as MyApp.EventStore.StreamSupervisor
and similar.
The downside you mentioned about “hiding” the API is somewhat true but is usually solved via behaviours. Basically, you make the module that you want to use
(say EventStore
) a behaviour as well and then have @behaviour EventStore
in the __using__/1
macro. This way, every module that calls use EventStore
uses that behaviour. With this setup, you can proceed to document the behaviour itself and then the modules that call use EventStore
are somehow documented as well. This is the approach that Ecto, Gettext, and Fluxter all take.
Hope that was clarifying :).
3 Likes
@whatyouhide Thanks for the reply. That sounds like the ideal solution. Glad to see that I was heading in the right direction as the typical way of solving the problem.
@whatyouhide Do you have an example of prefixing process names with a module name (e.g. MyApp.EventStore.StreamSupervisor
) you mentioned?
@slashdotdash This bit of code in Fluxter generates some worker processes specs and uses the name of the module that called use Fluxter
to register those processes.
2 Likes