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)

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, …

tcoopman

tcoopman

I haven’t seen the presentation yet. I will watch it later.

From your explanation I guess that domains and bounded contexts are used interchangeably in this talk?
I’ll use bounded contexts (BC) in my further explanation.

A BC is a context where you define a ubiquitous language and consistent model.

By that definition you should not have transactions across differenent BCs. Because by definition if you need a transaction to keep things consistent across 2 BCs, you can’t keep the model consistent in 1 BC on it’s own.

That means if you need transactions to keep things consistent across BCs, you probably should think if you need to remodel your BCs, for example that the things that you want to keep in 1 transaction should be part of 1 BC, or maybe remodel so that you have a business process that does first 1 thing and then the second thing.

(This is just regular DDD advice without having seen the talk, I will watch the talk at some point and maybe add some more insights after :-))

sodapopcan

sodapopcan

I am by no means a DDD expert but have a bunch of experience with some peoples’ idea of it as well as my own idea of it. The whole notion of DDD is quite flexible. It’s not something like SCRUM where the author says if you aren’t following it to the letter then you aren’t really doing it. The DDD book is very large but it’s peppered with bold paragraphs and the intro claims that it’s fine to only read those paragraphs, learn the concepts that appeal to you, and you’ll be “doing DDD”. I imagine “fancy names for existing concepts” isn’t too far off, especially if you are someone coming from XP (though I don’t know because I wasn’t doing this level of development in the 90s) but I find a lot of that language useful. For example, you could just call an Anti-Corruption Layer what it essentially is: a big ol’ wrapper! But “anti-corruption layer” clearly communicates the intent about why that wrapper is necessary and DDD gives good advice on how to implement and maintain them (especially when it comes to wrapping junk code that’s part of your codebase).

So, my brain is often pretty cluttered and I miss the nuance in these discussions sometimes, but in my experience, database referential integrity is a non-issue. Bounded Contexts are not microservices, so it’s perfectly legit—and I would say ideal—that tables still point to each other across subdomains. In fact, it’s perfectly legit for subdomains to share tables with each subdomain owning different slices of the table—of course any shared columns should probably only have one writer. The intro of this talk says it pretty well with “Stop doing table-driven design” and “you can’t express every entity globally throughout your app.” Ecto makes this feel really natural by not pushing you into a 1:1 relationship of schema-to-table. That 1:1 relationship is pretty ingrained into the soul of most web developers, though.

Is that even what you mean? If I’m way off base then could you share an example of when referential transparency is a problem?

Where Next?

Popular in Talks Top

New
axelson
ElixirConf 2017 - Well Behaved Native Implemented Functions for Elixir - @potatosalad Native Implemented Fun...
New
axelson
ElixirConf US 2018 – You Can Never Debug the Code You Run, But You Can View the Code the Debugger is Running – Luke Imhoff (@KronicDeth) ...
New
axelson
ElixirConf US 2018 – Making a GraphQL Server with Absinthe & Dataloader – Aaron Votre (@shamshirz) The GraphQL query...
New
axelson
by @takasehideki Can you imagine that your Elixir code will be executed as a hardware circuit on an FPGA? The FPGA (Field-Programmable...
New
axelson
Hey folks, beginning to post today’s round of ElixirConf videos! ElixirConf US 2018 – Elixir at a Walking Pace – Lance Halvorsen (@lance...
New
axelson
ElixirConf US 2018 – Docker and OTP: Friends or Foes? – Daniel Azuma (@dazuma) Docker is the hot deployment technology a...
New
CodeSync
Introducing Phoenix Sync - James Arthur | ElixirConf EU 2025 | ElixirConf EU 2025 Comments welcome! View the <span class...
New
axelson
ElixirConf 2017 - Keep an Eye on the Sky with Nerves and Phoenix - @electricshaman As part of the next gener...
New
New

Other popular topics Top

Tee
can someone please explain to me how Enum.reduce works with maps
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
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
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
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
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod -- where is this set? Thanks.
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

We're in Beta

About us Mission Statement