Support multiple usages/instance of a library that uses named processes

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