ddd - How to separate bounded contexts and share events?
Asked Answered
C

3

6

I am actually reading a book called "DDD in PHP", to help me understand the Domain Driven Design. So far everything is good, but I struggle to understand how to implement one specific topic without coupling Bounded Contexts: Domain Events

Let's say I have to BCs :

  • Payments: Handles generation of invoices, sending them to the customers, etc
  • Orders: Handles the creation of orders, their state, etc.

When an Order is placed, an OrderCreated Event is dispatched. The Payments BC catches this event with a subscriber, and creates the invoice.

The problem is, If I want to perfectly separate both BCs, where should the OrderPlaced Event live, since it's used by both BCs ? Should it live outside both BCs ? In both of them ? What if I want to deploy the Invoices module as a standalone, without having access to the Orders module, and its OrderPlaced event definition, would it cause some fatal errors ?

Thank you in advance for the answers !

Cerulean answered 5/7, 2016 at 8:12 Comment(0)
A
3

The problem is, If I want to perfectly separate both BCs, where should the OrderPlaced Event live, since it's used by both BCs ? Should it live outside both BCs ? In both of them ?

I would store the event in the context that owns it, i.e. the Orders context. How do you intend on separating the contexts? is it a physical / network boundary separation, or just conceptual?

What if I want to deploy the Invoices module as a standalone, without having access to the Orders module, and its OrderPlaced event definition, would it cause some fatal errors ?

It depends on what you are doing with OrderPlaced. If you are subscribing to it from some sort of event stream, then reacting to it inside InvoicesBC by way of converting it to an internal-to-invoices concept, then you'll probably be fine as you could just not deploy the subscriber. If your code in InvoicesBC can run without having to know about OrderPlaced then you should be fine

In general, there are a few ways to deal with this problem:

  1. Share a common definition. In C#, (not sure what the equivalent would be in PHP) in the past I've had a separate class library in the BC for the events that another BC might need (i.e. a MyContext.Contracts dll) that can then be referenced by other BC. These were published as internal nuget feeds (package manager stuff) so other contexts could keep themselves up to date.
  2. Weak serialization on the subscribing end. If you are subscribing to an event stream, you can deal with the raw representation it is stored in (i.e. JSON) rather than have some library automatically deserialize it into an object. If you go down this route, I'd suggest some "contract tests" in the publishing side that mimic what a subscriber would do. This will protect you from breaking your contracts with the outside world.
Abase answered 5/7, 2016 at 8:36 Comment(9)
The boundary between contexts could be either physical or conceptual, that is the problem. I am wondering if I should create a Library just for events, would it be a good practice ?Cerulean
I wouldn't create a single library for all your events. I'd create one per BC and have external BC import the ones they need. This way it is clear which BC owns the eventAbase
The alternative is option 2 - when code is within the same solution, you can just reference the events directly, you don't need a separate library. For physically separated BCs you can use weak serializationAbase
This would still make the BCs coupled to a part of others BCs right ? Payments would be couple to Orders.Events, which isn't really good, or is it ?Cerulean
There's a degree of coupling however you approach this. In this instance, your events would be part of your Published Language - you're explicitly accepting that other contexts want to know about something you are responsible for. Each BC could have events that other BC need to interact with. I think it's OK to have this level of coupling, as it's just dumb objects, you're not exposing behaviour or business rules - you're isolating what you expose to the outside worldAbase
Okay so I will assume that this is fine, thanks. However just for my own knowledge, is it possible to achieve a zero-level coupling ?Cerulean
Well... conceptually no. If you're saying BC1 publishes an event that BC2 needs to handle, then that's coupling. There's no way of getting around this. However, you can use weak serialization to prevent coupling at the implementation level, but this really only works if you've got a network boundary between BCs, i.e. reading from a stream somewhere. Option 1 is just a way of limiting the implementation of the couplingAbase
So is this a bad design then ? Does it mean that Orders and Payments are not separate BCs, but are one and one only ?Cerulean
No, i think it's fine. Things in the real world are coupled to other things. For example, when you withdraw money from a cash machine and then spend it in the shop, it doesn't mean the cash machine and shop are the same BC - they just share the contract of money, which wouldn't be "owned" by either of them - it would be owned by another BC (i.e. Government or Bank Of England etc!)Abase
Z
2

Although an answer has been accepted I would still like to add my opinion :)

They way your current BCs integrate is by subscribing directly to the messages published: Payments subscribes to OrderCreated. This style of interaction is called choreography. It isn't necessarily bad but you have to weigh the pros and cons. For instance, when you need to add a step in-between then you may have the Address BC subscribe to to the OrderCreated so that it can validate the delivery address. Now the Payments BC has to subscribe to the AdressValidated event. But then again, this event is published quite a bit since we also use it when registering customers. mmm...

Another option is to use orchestration where you explicitly store the state of the process in question. You my have an OrderProcess and a CustomerOnboardingProcess. You could then have your current BCs handle only messages within their own BC. Another process BC would then co-ordinate the messages and keep track of the state.

Two other issues one may run into with choreography are:

  • Having to wait for human intervention
  • Parallel processing where two tasks are running for the same process and one needs to bring the results back together again.

I hope that makes sense.

Zero answered 5/7, 2016 at 10:29 Comment(3)
Actually I find a bit odd to validate the address after the creation of the order. It should be a process of validation that is fired before OrderCreated, since OrderCreated means "I have the items, I have the price (unit and total), the client's shipping and billing address"Cerulean
It is a totally bogus, hypothetical, example :)Zero
Good reference about the choreography vs orchestration :)Apheliotropic
L
0

The question is if the Payments BC must create the invoice, because as the name suggests, it's concern should be payments and not orders or invoices.

Maybe something like this:

Orders -> 'order created' -> Payments -> 'payment done' -> Orders -> 'invoice created'
Labyrinthodont answered 5/7, 2016 at 8:43 Comment(6)
Maybe it was a bad example, but the logic stays the same, where do I put an event definition that is used across many BCs?Cerulean
Well if a BC needs to listen to an event it also needs the object that fires it. So I'm not sure if you need to worry too much about modularity because you will always include the object that fires the event. But I'm not sure what you mean with 'used across'. Do you mean that the BC's must be able to fire the event or to listen to it?Labyrinthodont
I mean that the OrdersBC must fire the OrderCreated event, and the PaymentsBC must listen to it, so they are indeed coupled because of this event. As stated by tomliversidge the idea of creating a separate library for OrdersBC.Events could be good, but I'm still worried of the modularity. I would like to deploy OrdersBC on an infrastructure, and maybe PaymentsBC somewhere else, or not at allCerulean
But then you should ask yourself: how would the PaymentsBC even know about an OrderCreated event if it doesn't even know the context? Maybe you should be looking at 2 separate projects and communicate via sockets for example.Labyrinthodont
Again, not sure about PHP, but in C# world, you can still deploy something if you've imported a reference to a dll from a separate library. It's just the same as if you imported a library to help parse JSON. You'd just deploy the specific BC code, plus a tiny bit of compiled code (SomeOtherBC.Contracts) alongside itAbase
It should not know the context in which the Order was created. What if it was created using an web app ? or an iOS app ? It doesn't matter, what does matter is the information sent. @Abase as said in your answer, I think I will go with this approachCerulean

© 2022 - 2024 — McMap. All rights reserved.