dgamidov

dgamidov

Domain Driven Design diagram for Umbrella app

Hi all!

I am working on umbrella app now - 4 apps in it.

Being impressed with Controller Control: Designing Domains for Web Applications - Gary Rennie and Episode @057: Educating in Elixir with Dave Thomas @pragdave I have started to think about my application design.

I have started to learn these books:
Martin Fowler - Patterns of Enterprise Application Architecture
InfoQ: Domain Driven Design Quickly

Also, @Gazler recommended:
ElixirConf 2016 - Building Umbrella Project by Wojtek Mach and
github:acme_bank by @wojtekmach

Based on it, I have made a diagram:

How can I improve it?

Most Liked

hlx

hlx

I’m trying out the following structure

umbrella
├── core (Elixir)
├── web (Phoenix)
└── api (GraphQL)
├── config
│   ├── config.exs
│   ├── dev.exs
│   ├── prod.exs
│   └── test.exs
├── lib
│   ├── core
│   │   ├── checkout
│   │   │   ├── (...)
│   │   ├── account
│   │   │   ├── authorizers
│   │   │   │   ├── organization_authorizer.ex
│   │   │   │   ├── sales_channel_authorizer.ex
│   │   │   │   ├── shop_authorizer.ex
│   │   │   │   └── user_authorizer.ex
│   │   │   ├── models
│   │   │   │   ├── domain.ex
│   │   │   │   ├── organization.ex
│   │   │   │   ├── sales_channel.ex
│   │   │   │   ├── shop.ex
│   │   │   │   ├── tax_setting.ex
│   │   │   │   ├── theme.ex
│   │   │   │   └── user.ex
│   │   │   ├── repositories
│   │   │   │   ├── organization_repository.ex
│   │   │   │   ├── sales_channel_repository.ex
│   │   │   │   ├── shop_repository.ex
│   │   │   │   ├── theme_repository.ex
│   │   │   │   └── user_repository.ex
│   │   │   ├── organization_service.ex
│   │   │   ├── sales_channel_service.ex
│   │   │   ├── shop_service.ex
│   │   │   ├── theme_service.ex
│   │   │   └── user_service.ex
│   │   ├── inventory
│   │   │   ├── authorizers
│   │   │   │   ├── collection_authorizer.ex
│   │   │   │   ├── permalink_authorizer.ex
│   │   │   │   ├── variant_authorizer.ex
│   │   │   │   └── product_authorizer.ex
│   │   │   ├── models
│   │   │   │   ├── collection.ex
│   │   │   │   ├── image.ex
│   │   │   │   ├── permalink.ex
│   │   │   │   ├── price.ex
│   │   │   │   ├── product.ex
│   │   │   │   ├── stock.ex
│   │   │   │   └── variant.ex
│   │   │   ├── repositories
│   │   │   │   ├── collection_repository.ex
│   │   │   │   ├── permalink_repository.ex
│   │   │   │   ├── variant_repository.ex
│   │   │   │   └── product_repository.ex
│   │   │   ├── uploaders
│   │   │   │   └── image_uploader.ex
│   │   │   ├── collection_service.ex
│   │   │   │── permalink_service.ex
│   │   │   │── variant_service.ex
│   │   │   └── product_service.ex
│   │   ├── relation
│   │   │   └── models
│   │   │       ├── collection_permalink.ex
│   │   │       ├── product_collection.ex
│   │   │       ├── product_image.ex
│   │   │       ├── product_permalink.ex
│   │   │       ├── product_price.ex
│   │   │       ├── product_shop.ex
│   │   │       ├── product_stock.ex
│   │   │       ├── shop_organization.ex
│   │   │       ├── user_organization.ex
│   │   │       ├── variant_image.ex
│   │   │       ├── variant_permalink.ex
│   │   │       ├── variant_price.ex
│   │   │       └── variant_stock.ex
│   │   ├── application.ex
│   │   ├── authorizer.ex
│   │   ├── definitions.ex
│   │   └── repo.ex
│   └── core.ex
├── priv
│   └── repo
│       ├── migrations
│       │   ├── (...)
│       └── seeds.exs
├── test
│   └── (...)
├── README.md
└── mix.exs

In web and api I use the core package like:

{:ok, shop} = MyApp.Core.Account.ShopService.get(1)
{:ok, shop} = MyApp.Core.Account.ShopService.update(shop, params)

defmodule MyApp.Core.Account.ShopService do
  alias MyApp.Core.Account.{ShopAuthorizer,ShopRepository,Shop}

  def update(%Shop{} = shop, params) when is_map(params) do
    with :ok <- ShopAuthorizer.authorize(:update, shop),
      do: ShopRepository.update(shop, params)
  end
end
12
Post #8
peerreynders

peerreynders

Pardon my ignorance but is there a technical reason for for adopting this type of organization:

inventory
        ├── authorizers
        │   ├── collection_authorizer.ex
        │   ├── permalink_authorizer.ex
        │   ├── variant_authorizer.ex
        │   └── product_authorizer.ex
        ├── models
        │   ├── collection.ex
        │   ├── image.ex
        │   ├── permalink.ex
        │   ├── price.ex
        │   ├── product.ex
        │   ├── stock.ex
        │   └── variant.ex
        ├── repositories
        │   ├── collection_repository.ex
        │   ├── permalink_repository.ex
        │   ├── variant_repository.ex
        │   └── product_repository.ex
        ├── uploaders
        │   └── image_uploader.ex
        ├── collection_service.ex
        │── permalink_service.ex
        │── variant_service.ex
        └── product_service.ex

rather than something like this

inventory
        ├── collection_service
        │   ├── collection.ex
        │   ├── collection_authorizer.ex
        │   ├── collection_repository.ex
        │   └── collection_service.ex
        │── permalink_service
        │   ├── permalink.ex
        │   ├── permalink_authorizer.ex
        │   ├── variant_repository.ex
        │   └── permalink_service.ex
        │── variant_service
        │   ├── variant.ex
        │   ├── variant_authorizer.ex
        │   ├── variant_repository.ex
        │   └── variant_service.ex
        └── product_service
            ├── product.ex
            ├── product_authorizer.ex
            ├── product_repository.ex
            └── product_service.ex

Seems the organizing principle is to put “like” things into the same place rather than “putting everything to deal with subject area X” into the same place. One of the starting points described by the article that I linked to earlier - Bring clarity to your monolith with Bounded Contexts - is to “Invert folder structures into a flat domain-oriented grouping”.

The organizing principle of “putting like things into the same place” that ultimately is responsible for “a Rails app always looking like a Rails app” (and not communicating the actual intent behind the web app) is what inspired Architecture: The Lost Years (Ruby Midwest 2011 Keynote). That type of organization is rarely helpful in revealing the emerging (context) boundaries as the application matures (Evans, DDD p.48: “…crucial discoveries always emerge during the design/implementation effort”).

FYI: for anyone interested: Eric Evans: DDD Reference (PDF 2011)

kelvinst

kelvinst

Well, I have a very close setup to yours, the only difference is the “infrastructure layer” you have and I don’t. In my case, the plugs stay on the application layer (because they have web specific logic, like session control and etc), and the service and the repo go to the domain layer, which does not have any kind of web logic, just business logic.

I’m happy with my structure for now, and it’s a pretty clear separation of concerns. My next step, I guess, is to separate the domain layer in more pieces, by resources and features and not by a specific layer itself. Something I learned with CBRA (Component Based Rails Applications) your application is a big box, and big boxes tend to become a mess if you don’t separate into mor boxes, and label them correctly.

peerreynders

peerreynders

Sounds like you are talking about bounded contexts. Identifying and maintaining the correct bounded contexts within a monolith is supposed to give some of the benefits of microservices without taking on the inherent overhead (though in the end more discipline is required). The interesting thing is that applying DRY across context boundaries can result in a “mess of coupling”.

David Dawson:

Also InfoQ: Don’t Share Code Between Microservices (2015-Jan-25).

krapans

krapans

Would be cool if you in the end shared this architecture as open source project. :wink:

Where Next?

Popular in Questions Top

gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list....
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
LegitStack
I’m trying to make a websocket server in Phoenix or raw Elixir. I heard about gun, I think I could use cowboy, but since I’m not that sma...
New
hpopp
To simplify some tasks at work, I wrote and published this package yesterday. It’s a simple macro that enables Access behaviour on struct...
New
Codball
Mix format works fine if run from the cmd. I’ve followed this to facilitate the implementation into VSC which involves downloading an ext...
New
Mooodi
Given a string, how can I get access to its character by index? Enum.at("my_string", 2) doesn't work. Or rather, not char, but a substr...
New
wernerlaude
In AR this is so simple @articles = current_user.articles How to do in Ecto? def index(conn, _params) do current_user = conn.assig...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New

Other popular topics Top

Qqwy
Update: How to use the Blogs &amp; Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3268 119930 1237
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
polypush135
As many of you may have realized by now (sorry for all the posts here) I’ve been working on a db problem where I’m trying to aggregate a ...
New
danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 27727 240
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
AstonJ
We’ve put together this wiki for Phoenix LiveView - please feel free to add any info you feel is worth including. What is Phoenix LiveV...
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

We're in Beta

About us Mission Statement