ElixirConf

ElixirConf

ElixirConf 2023 - German Velasco - Using DDD concepts to create better Phoenix Contexts

ElixirConf: ElixirConf 2023 - German Velasco - Using DDD concepts to create better Phoenix Contexts

Comments welcome! View the elixirconf tag for more ElixirConf talks!

Most Liked

tcoopman

tcoopman

I’m going to try to explain my understanding as best as possible.

  1. The context in which you work matters. There’s a difference between a small company with 1 to a few dev teams, vs bigger companies that have many dev teams. From a DDD perspective it doesn’t matter much if you have a monolith or use different services.
    You can apply DDD principles/practices in both kinds of architectures.
    Often from a team organisation and deployment perspective, a monolith vs multiple services does matter.

  2. If you have a monolith, keeping referential integretity is not that hard, given that you have a single database.
    When you have multiple services, you have to rethink referential integretity anyway. (I make the assumption that a service has a separate database and you don’t want to deal with distributed transactions)

  3. There is a difference between a (sub)domain and a bounded context. Domains are part of the problem space, they are how the business see themselves, how the business is organised,.. Domains often have fuzzy boundaries, fuzzy language, are not super well defined.
    Bounded contexts on the other hand are solution space. We (dev) design them. Often they take inspiration from the domains, but they don’t have to perfectly align.
    The definition I use for bounded context is: in some context we have a consistent ubiquitous language and consistent model with an explicit boundary around it.
    So this means that we design different contexts (with inspiration from the domain) and build a ubuiquitous language and model and clearly state what is inside this BC and what is outside.
    The most important thing here IMO is that we design bounded contexts. This means that often we’ll have multiple heuristics to try to define the BC. Some examples of these heuristics are:

    • things that change for the same reason, belong together,
    • things that change for different reasons, don’t belong together,
    • look at the language - similar language → together,
    • natural domain boundaries,
    • look at business value → high value vs low value might be splitted up

One of the heuristics might be keep referential consistency.
As you can imagine, often some of the heuristics conflict - we call these competing heuristics - and thats what make software design hard, we’ll have to make decissions based on tradeoffs. There are no perfect solutions.

  1. In DDD we try to capture essential complexity and listen to domain experts to design our systems. I’ve seen cases where developers try to add consistency on top of a concept where domain experts say there is no such consistency. In this case developers remove essential domain complexity making some features hard or impossible to implement.
    (of course the opposite also happens where there’s no consistency and there should be)
    I just want to say that there is no 1 correct design.

Given all this, here is an example from a client that I consulted with that worked with warehouses/products/…
They had a pretty complex domain and software to match (and quite a bit of legacy).
For them adding consistency rule like a product needs a price would be a mistake, the price setting of products was very complicated and often only happened after customers already had products in their basket.
Some products have a price, other prices would be calculated based on the warehouse with most stock, or maybe calculated based on which warehouses we own vs which are partners of us vs how much of these products we already sold from our warehouses…
A very complex price setting.
Do we have an item in stock was also much more complicated than yes/no. Maybe we have it in a warehouse in the same country as the customers, maybe we have it in a different country. Some products are ordered and build on demand,…

All of this is to say that if business requires referential consistency, then please also implement it that way. But if business doens’t require it, find out why they don’t require it and make a decision based on that information.

I’m going to end with one last comment on design. There are a lot of businesses where a product is as simple as just a product. Maybe a Product is in draft DraftProduct, or it’s orderable OrderableProduct or it’s archived ArchivedProduct.
These can be 3 different entities in your software and maybe they even live in different bounded contexts. There’s going to be different consistency rules on a DraftProduct vs OrderableProduct (must be in stock, must have a price,…).
In that case you can have consistency rules for each different product type and guard the process on how these products move from one type to the other.

As for the question on example software, I’m don’t have examples in elixir. And the best examples are things that are closed source from clients. We do teach courses on tactical DDD patterns but not in Elixir (Domain-Driven Design in Typescript · Domain-Driven Design Courses)

Hopefully this helps a bit, I can talk a lot more on this, but don’t have the time right now.

tcoopman

tcoopman

I’m quite pragmatic about these kinds of things. And I definitely don’t like the word forbidden. Discouraged is can agree with.
But if something is discouraged we also need to know why it’s discouraged, and not just because someone or some practice says so.

Expanding on the discussion of yesterday

So let’s first look at a simple, imaginary example, but with some inspiration from real world situation that I’ve seen.

Imagine after a discussion with 1 domain expert, we came up with the following design:

(so a product has exactly 1 warehouse)

We implemented this and have a database with some constraint that a product must have a warehouse id.

Now a bit later we go talk to an other domain expert who’s main expertise is adding new products in the system.

We show them this picture and explain what it means

Sidenode on technical diagrams

The above picture is trivial to explain, but often showing technical UML diagrams to domain experts is not the best way to engage with them, because they’re often not familiar with this notation. In DDD we have some other techniques like EventStorming or Domain Story Telling to discover the domain, talk with domain experts, …

An example of the same knowledge but captured with EventStorming might look like this:

(blue = command or action, pink = constraint, orange = event or decision)

This notation is really easy for anyone to understand, we can read it like a sentence: when we add a new product, we check that the product has exactly one warehouse and if that’s true, the product will be added in the system.

Back on track,

So we discuss this with the domain expert and they immediately say, that’s not true. They explain

Often when we need to add new products, we don’t know in which warehouse we’ll store it. For some products we do, but for others we don’t know that yet.

“That’s interesting, but I guess that these products can’t be sold on the website yet.” I reply.

Sometimes these products will be shown on the website. People can’t buy them yet, but they can pre-order them.

The conversation continues for a bit and we go back to our design.

As we’re not doing any DDD and we feel that a refactor of the database constraints would be hard we propose the following solution:

If we don't know the warehouse, we'll add 9999 as warehouse_id

A small conclusion

This is how we start to add accidental complexity in our code base. Sure if this is the only thing that we do, maybe it’s ok.
But if later on the rules change again (we now have products that will never have a warehouse as they’re being sold by 3rd parties - we add 9998 as warehouse_id in that case), you can see that our system can quickly start adding a lot of accidental complexity, because we didn’t refactor to the actual business needs.

Is this forbidden?
No, but I’d discourage a design like that for a couple of reasons.
First, the upside of this design: it’s really easy to add it in at right now.

But, the downsides long term are huge.
These kinds of “hacks” accumulate quickly and the more you add in, the harder they are to refactor out. I’ve seen a lot of legacy systems that have columns with magic values like that. Figuring out what they do as someone new is often hard work (hopefully the meaning of a magic value doesn’t depend on an other magic value in a different column :anguished: )

Even for people who’re experts in the software, this design significantly increases the cognitive load for them, because everywhere they use the warehouse_id they need to take these things into account.

Refactor towards deeper insight

The above design is obviously flawed and it’s a great way to start building systems that after a while no one dares to touch anymore.

In DDD when we encounter such flaws in our model, we want to refactor towards deeper insights. The downside of this is that it takes time, but the upsides are that we now should have a model that aligns much closer with the actual business needs. Thus being able to serve the business better and faster in the future.

Back to the original questions: BC communication

Communication patterns between bounded contexts is something that’s discussed with Bounded Context maps and bounded context patterns. (a great talk on this topic is: https://www.youtube.com/watch?v=k5i4sP9q2Lk)

So in DDD I’d say we don’t forbid or discourage things necessarily, but we try to find out what patterns would work best.

We often try to limit the amount of knowledge 2 bounded contexts need to have about each other (both domain and technical knowledge).

How much knowledge depends on the business requirements (something we don’t have a lot of influence on) and how we design the system (something we can influence hugely).
Bounded context patterns define what kind of coupling relationship there are.

The hardest type of coupling is called a “shared kernel” in DDD, that’s for example 2 bounded context sharing a database, or sharing a domain software module.

It’s often said that this pattern is an anti-pattern in DDD, but it’s still a pattern that’s widely used. For me it’s important to that when you pick a pattern like that you know why you do it, and what the upsides and downsides are.

If you’re 1 team working on 2 bounded contexts that have a shared kernel, you’re not going to have a lot of downsides.
Once you split the 2 bounded contexts across 2 teams, you’ll need a lot of communication between the 2 teams to make sure that you don’t break each others code by changing stuff that impact the shared kernel. For example, you share the database I talked about earlier, if 1 team decides that warehouse_id 9999 means no warehouse, the other team needs to be informed up front about that because who knows what they rely on.
And it’s like that for a lot of choices you make.

Something else to take into account is how fast or slow is your software changing. It’s easy to keep a shared kernel in a slow changing part of the software, but a lot harder if it’s part of your core domain that you’re working on all the time.

And finally there are differences even when talking about a shared kernel. Sharing the full database scheme is obviously tighter coupled than just sharing 1 table for communication.

So, is a shared kernel bad? It depends, and it depends on some of the things I just described.

In your example:

Doing the upsert is an example of the shared kernel. They share a database.
Changing it to an oban worker triggering an api call (or public module function call) would reduce the coupling.

Sending notifications via pubsub, depending on how it’s implemented I would also call that a shared kernel. But promotions could also expose a public interface that you need to call when you want to notify someone, and that reduces the coupling again.

(there’s a lot more nuance, but hopefully you can see what I mean)

Like I just explained, yes it can, we’d call this a shared kernel. It certainly has upsides (like the simplicity of the single transaction), but there are downsides as well. It’s up to you to decide how they balance out.

To conclude. Nothing is forbidden, but some things are discouraged because we know that there are patterns that will make your software hard to maintain and to evolve over time.

If you’re building software as a small team, reducing coupling, splitting into BCs or services, also has a cost. It’s up to you to evaluate what’s important and how to design the software.

LostKobrakai

LostKobrakai

There’s already a lot of interesting discussion here.

In regards to the initial question of “why does DDD promote the a lack of referencial integrity”:

I think this starts at the wrong end. A more useful exploration would be: Does the business you’re working with have the need for referencial integrity between the contexts in question? Does the real world work with shared data or does information in the real world flow from one set of information to another set of information.

Consider a car company. There’s engineering designing cars and there’s sales selling cars. The “cars” those two departments think about are completely separate things. That’s what bounded contexts are about – separating engineerings “cars” from sales’ “cars”. Sure at some point e.g. an engineering sample might become a car being sold. But that’s generally better understood by “oh this is no longer a car engineering works with, but it’s now a car being sold” – a deletion in the engineering context and a creating in the sales context.

I often feel like people try to apply DDD at a scale where this kind of issue doesn’t come up in the first place. If a webshop can be dealt with in a single db with all the referencial integrity possible that’s fine. That however doesn’t mean that model scales to a company with many different departments all dealing with kind of the same core product, but all interacting with it in vastly different ways, contexts, lifecycles, …

Where Next?

Popular in Talks Top

JuanjoA
Hi, I don’t know if I have the post in the right place. I put here in case you might be interested, I am not the author, I just found it...
New
axelson
ElixirConf 2017 - Working with legacy databases in Ecto - @geo Often when starting an Elixir or Phoenix proj...
New
axelson
ElixirConf 2017 - Keep an Eye on the Sky with Nerves and Phoenix - @electricshaman As part of the next gener...
New
axelson
ElixirConf US 2018 – We’re Just Getting Started - Our Three Years with Elixir – Maciej Kaszubowski (@mkaszubowski) ...
New
zporter
ElixirConf US 2018 – Breaking Down the User Monolith – Zach Porter Coming from Ruby on Rails, the convention is to have a...
New
axelson
ElixirConf 2017 - Managing Tables With Elixir and OTP - Robert Beene We’ve all waited for a table at a resta...
New
axelson
ElixirConf US 2018 – Simple is Beautiful: Building an SLA Monitoring Tool Using Elixir/OTP at PagerDuty – Aish Dahal ...
New
Lawrence_elixir
Tonci Galic - Building a GameBoy emulator with Elixir and Scenic - ElixirConfEU 2019 Tonći Galić - Automating my role, mak...
New
axelson
ElixirConf 2017 - Elixir The Toyota Way - Powell Kinney Toyota has a century-long legacy of innovation in ma...
New
axelson
ElixirConf 2017 - Phoenix after 100000 lines - Renan Ranelli Given phoenix’s rise in popularity, the interne...
New

Other popular topics Top

malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
marius95
Hello everyone, I try to use an Javascript Event Handler in my root.html.leex file. Therefore I created a function in the app.js file: ...
New
Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
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
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
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

Latest on Elixir Forum

Elixir Forum

We're in Beta

About us Mission Statement