Question:
- If I have to fetch an entity from another Bounded Context in order to map it to something in this Bounded Context, how would I go about doing that?
- Do I call the foreign Bounded Context's Application Layer? Perhaps I need a new "Query" Application Layer for such queries?
- Do I call foreign Bounded Context's repositories?
- Do I call foreign Bounded Context's Domain model directly?
Domain model (please note two separate Bounded Contexts):
CustomerContext.Customer
- this is the complete Customer model.ConsentContext.Person
- a scaled-down, heavily transformed version of multiple instances ofCustomerContext.Customer
. Rules guiding this transformation reside inConsentContext.Person
.Person
never references ̇Customer` directly, no dependency here.- Please note: these two entities most definitely belong to separate Bounded Contexts. Customer is what the business works with on a daily basis. Consent Person is heavily transformed due to legal and business requirements and has a different structure altogether. Customer is merely a data source, so Person can create itself using internal business logic.
Application Layer:
ConsentContext.ApplicationService
- implements use cases. As part of those use cases it fetchesConsentContext.Person
through somePersonRepository
.
Repos:
ConsentContext.PersonRepository
has to get in touch with theCustomerContext
and retrieve theCustomerContext.Customer
and map to a new Person. This is where I am coming up short - what do I call from here?CustomerContext
's Application Layer?CustomerContext
's repositories?CustomerContext
's Domain model directly?
Other:
- Both Bounded Contexts run in the same process, I am not using a REST API.
- I am targeting Customer/Supplier relationship.
2 Answers 2
I’m going to sidestep the discussion of whether there really are two bounded contexts, and address the question of how they can share data. Before doing that, however, I have to point out that you are doing yourself a disservice by having 2 bounded contexts in the same process. From Microsoft’s definition
Bounded contexts are autonomous components, with their own domain models and their own ubiquitous language. They should not have any dependencies on each other at run time and should be capable of running in isolation. However they are a part of the same overall system and do need to exchange data with one another.
A big advantage of bounded contexts is their independence from one another. If one goes down, the other should survive and be able to keep functioning. This also helps in scalability by allowing deployment on different servers if the app gets more popular than expected.
Note that 2 process spaces doesn’t have to mean 2 servers. They can both share the same hardware but should have as little coupling between them as possible, meaning no coupling in code.
Along the coupling idea, we have this from Eric Evans
Code reuse between BOUNDED CONTEXTS is a hazard to be avoided.
All of the options provided above violate this guidance.
So your question has to do with the final sentence of the Microsoft definition of a Bounded Context. These are your options in order of preference
Each context can cache the information in needs from the other contexts. This is done via an event queue or message queue that all contexts share, and as important aggregates change they inform other interested contexts. Julie Lerman has a good discussion of it here. If you want to stick with a single piece of hardware, the message queue can also run on that hardware, though that would introduce some scalability and durability concerns.
Second option is to add an API. I know you say you don’t have one, but APIs can be deployed as part of the context so they are little extra work. They can also be locked down to only accept requests from localhost using CORS rules.
Third option is a shared database, but now we’re coupling the contexts so this is a dangerous road. If you MUST do this, at least make the tables in separate schemas so the developer is aware they are breaking the wall between the contexts.
I would avoid all three of the options you have listed. It’s not hard to imagine requirements changing in one context that conflict with the rules in another. After all, that’s why you separated them into separate contexts to begin with.
-
Thank you for the detailed write up. Interestingly, so none of the options actually allow for fetching data directly via the other context's repositories as that would introduce coupling. I watched several videos on Pluralsight and the topic of communicating with other Bounded Contexts was never present, so I just improvised. I will give my comment on each of the options in comments below.robotron– robotron07/28/2018 10:23:01Commented Jul 28, 2018 at 10:23
-
1API: Let's forget the two Bounded Contexts I mention above for a moment and imagine I have some other two BCs. If I were to place both BCs behind API, each BC would still have an Application Layer sitting between the API and Domain Layer (as per Onion Arch.), through which the API call would have to go through. Application Layer implements various use cases. If I were to fetch some data via the API, perhaps I do not wish to execute complete use cases, perhaps I just want a Domain Entity/DTO, take the data and map/transform it to an entity in the other BC. How would I go about doing that?robotron– robotron07/28/2018 10:30:12Commented Jul 28, 2018 at 10:30
-
1Migrating to a BC is a much tougher task than greenfield. Shared database is a compromise that seems to make things easiest while fighting the battle of teasing apart the contexts. With 700K+ users, I would not be surprised if you start having performance issues soon, though, making the breakup of BCs into separate address spaces all the more urgent.Brad Irby– Brad Irby07/28/2018 10:31:10Commented Jul 28, 2018 at 10:31
-
1I completely agree the DB integration is a code smell, but when migrating sometimes you have to hold your nose. greenfield is about doing it right so you don't have to do it again. Migration is about moving the needle. It's rarely possible to get all the way to the target architecture in one stepBrad Irby– Brad Irby07/28/2018 10:36:57Commented Jul 28, 2018 at 10:36
-
1If you are just querying via the API there should be no business rules to execute. At most there will just be some table joins, unless you're doing CQRS (which I like) and the read model is already updated. You can wrap that query in an Interactor if you think necessary, but if you're just querying to fill a View Model, maybe you can use a shortcut.Brad Irby– Brad Irby07/28/2018 10:39:07Commented Jul 28, 2018 at 10:39
If Customer is a datasource for Person, then its fine to have a PersonRepository reference Customer and a CustomerRepository.
That doesn't break the bounded context because PersonRepository is a service rather than an Entity/Domain Object.
PersonRepository should contain the transformation rules rather than Person
-
'PersonRepository should contain the transformation rules rather than Person' - there are two transformations going on. 1) mapping Customer attributes to what the Person requires and 2) Legal and business logic pertaining to the Consent Bounded Context. But I do understand what you are aiming at: Anti-Corruption Layer and Context Mapping.robotron– robotron07/26/2018 09:12:13Commented Jul 26, 2018 at 9:12
-
I have listed three different ways for fetching entities from
CustomerContext
: application layer, repo, domain model. Would you say each of these is a valid way of fetching data, depending on the circumstances? Or are some definitely off-limits, like going for the domain model directly?robotron– robotron07/26/2018 09:15:17Commented Jul 26, 2018 at 9:15
Person
created? Generally, repositories are used to persist/load your model, not create it. IsPersonRepository.find( customerId )
not possible? These two entities seem like they may share an ID. That is, traditionally, how aggregates/entities are referenced between contexts.Person
anywhere. It is reconstituted every time anew, based on thecustomerId
andCustomer
. We do storePerson
in a dedicated database, however.Person
requires one or two separateCustomer
's to be created. There is a complex relationship between customers (typical example: two customers belong to the same account: one might be an account owner, but the other one might be relevant for Consent gathering, due to various legal reasons). So it is not that simple to mapCustomer
toPerson
- we mainly take both customersname
,id
,VAT
, whether they are business or residential and then use these to instatiate aPerson
(based on person internal logic, in ctor). Instantiation is done inPersonRepository
.