Silviu Rosu
Silviu Rosu
Let's make programming easier

Domain Driven Design with Elixir

Im my previous article I wrote how to decouple your application from Phoenix and Elixir.

In this article I will talk more about separating application in different domains based on the relation between models. So we can further separate db umbrella app from previous post further in more domains.

Domain driven design

I suggest using DDD to separate domain models in smaller umbrella applications. Usually in an application you can separate some island of entities that are tied to each other and have meaning together. This can become you domain.

As an example let’s presume that we have an online ordering system for restaurants. We can separate the database entities in 3 different domain models like this:

DB separation in domains

In our application restaurant, user and shopping can become 3 different applications extracted as umbrella apps for example. I created a small github project to demonstrate my approach. I separated the app in more umbrella projects like this:

├── apps
│   ├── api
│   ├── restaurants_db
│   ├── restaurants_db_behaviour
│   ├── service
│   ├── shopping_db
│   ├── shopping_db_behaviour
│   ├── users_db
│   └── users_db_behaviour
  • api is the web interface (Phoenix) that talks with the service
  • service is the business logic for the application. It contains mostly all the logic. It uses domain models (restaurant, shopping, user)

  • restaurants_db_behaviour is the restaurants database behavior specifications that need to be implemented. It also contains the structs that will be used to pass and return the data.
  • restaurants_db is the database gateway implementation for restaurant domain model
  • shopping_db_behaviour is the restaurants database behavior specifications that need to be implemented. It also contains the structs that will be used to pass and return the data
  • shopping_db is the database gateway implementation for shopping domain model

I have 2 applications for each domain. One is the behaviors specifications and the struct (data) that will be pass during communication. I extracted them in a different app because each implementation needs to include this package and implement it. So all the implementation will be plugins that implement this app.

Also I suggest using a single interface file to be used. Do not let users access any model path from your app. For example in restaurants_db we will have:

├── restaurants_db
│   ├── lib
│   │    ├── restaurants_db.ex
│   │    ├── load_restaurant
│   │    │          ├── db_gateway.ex
│   │    ├── search_restaurant
│   │    │          ├── db_gateway.ex
│   │    ├── load_restaurant_menu
│   │    │          ├── db_gateway.ex

The only model used from applications that talk to restaurants_db application will be RestaurantsDB. Inside this file we can delegate the calls to the corresponding models like this:

defmodule RestaurantsDB do
  @moduledoc """
   External interface methods for restaurants domain model
  @behaviour RestaurantsDBBehaviour

  alias RestaurantsDB.LoadRestaurant
  alias RestaurantsDB.SearchRestaurant
  alias RestaurantsDB.LoadRestaurantMenu

  @impl true
  defdelegate load_restaurant(restaurant_id), to: LoadRestaurant.DbGateway, as: :load

  @impl true
  defdelegate search_restaurant(name), to: SearchRestaurant.DbGateway, as: :search

  @impl true
  defdelegate search_menu(restaurant_id), to: LoadRestaurantMenu.DbGateway, as: :load

This way we can keep flexible the internal structure on the application without affecting consuming apps.

Having this separation into domains bring a lot of flexibility and advantages like:

  • separating database in multiple smaller ones. Each domain can have it’s own separate db
  • splitting responsibility between team or team members. Each team can be responsible of a separate domain
  • domains can be easily be reused in other applications. Either by git submodules, either by extracting them to separate hex packages. I tend to prefer the git submodule approach.