I've read that the reason to split the application in different bounded contexts is because of the ubiquitous Language and also to split big entities. For example, I can have a Product entity with logic for Support and Sales and I can split the model into two Product models in different bounded contexts - Support and Sales bounded contexts, but I can also just create two different entities SupportProduct and SalesProduct and achieve the same result? So why also do I need different bounded contexts?
Bounded contexts don't really have anything to do with the application or code, they're for problem decomposition and a means of recording the fact that sales domain experts and support domain experts mean different things when they talk about a product entails.
So SupportProduct and SalesProduct are valid encodings of the fact that "product" exists in two different bounded contexts; you might choose this encoding if you're aiming for a more monolithic implementation in a language which doesn't support modules/packages/namespaces (in a language which does have such support you might have something like sales.Product and support.Product).
Bounded contexts don't imply anything about code structure, or deployment into different (possibly micro-)services, although that decomposition does suggest some natural seams with which to structure code/deployment.
Related
I have a subdomain which involves tracking user financial data across different financial account types.
For example, users can input data for their:
bank accounts,
credit cards,
loans,
lines of credit,
real estate,
and more...
Now within each individual type, there are more subtypes.
For instance, under loans:
personal loans,
business loans,
mortgages,
car loans,
and more...
They would each have their own particular invariants, with some unique properties and functionality, and some shared properties and functionality.
I've been approaching this using composition, creating an aggregate for each subtype, and using interfaces and helper interface implementations to share similar logic between aggregates.
However, it appears as though I'm going to end up with dozens of different aggregates when modelling all these different account types. This doesn't feel right.
Alternatives I've considered:
have a type property on the loan aggregate, and conditional logic based off the type.
create different bounded contexts for each of these types: This feels like overkill, I believe this is all part of the same business subdomain.
create aggregates based off shared functionality - eg SecuredLoan and UnsecuredLoan aggregates
creating subclasses in the general aggregates to hold the subtype's unique functionality. get some encapsulation of subtype specific logic, with some conditional logic still (eg conditional properties on the aggregate). Not really sure the difference between this and just creating a separate aggregate for each subtype
Tradeoffs seem to be, the more general the implementation, there will end up being a ton of conditional logic, and conditional properties based off the subtype.
Versus building specific aggregates for each subtype, the logic per aggregate is simplified, but there ends up being hundreds of commands in the application layer, a lot of them which are basically the same thing but to a different subtype. Additionally, there end up being dozens of repositories.
It feels like I either get an explosion of conditional logic complexity in a general aggregate, or an explosion of the number of aggregates (or contexts) if building one per subtype.
Question - is there a known pattern for dealing with this type of modelling problem? Or is it really just dealing with the above tradeoffs, and finding something which fits best? In that case, is there some precedent I can apply to the decision-making process, as I'm struggling to decide between the above approaches. And is it problematic if there end up being many dozens of aggregates within a given context?
Rather than starting with the data at rest to get your aggregates, consider instead what operations/changes ("commands" one might say) will be performed and how the results of those operations affect future operations is what leads to what the aggregates want to be. Event storming style approaches can be helpful for figuring out these relationships between state changes.
For instance, each of these kinds of loans might have AccrueInterest, DrawPrincipal, and RecordPayment commands which operate on the balances identically (given perhaps configurable rate parameters etc.) and which don't affect and aren't affected by other commands. In that scenario, you can have a Loan aggregate which models the idea that there's a loan with interest and principal balances on which interest accrues and payments are made. An AutoLoan aggregate might then just be managing the collateralization of Loan ABC123 with VIN 1G1234567890.
Sorry for the simple starting answer, but …
Build your aggregates based on your actual use cases.
Aggregate A - Scenario A
Aggregate B - Scenario B
…
Avoid building aggregates for general conditions, DDD and ubiquitous language is about developing language, aggregates and systems around the use case not general purpose.
General purpose has its use cases and isn’t necessary anti DDD; but the focus is on creating the specifics then abstracting to generality.
We try to split up our domain into bounded contexts with the goal to have a modular application design/architecture.
We did an enlightening EventSorming session which helped us a lot to identify bounded contexts and its aggregates. After the workshop every participant agreed on the bounded contexts we identified.
Nevertheless we feel uncomfortable as we fear our bounded contexts are still too large. EventStomring focusses on the domain events / process and that's the major building block we used to identify our bounded contexts.
We also identified aggregates like "Contract". Every contract nearly follows the same process, but the amount of data these contracts contain can differ massively. There are very simple contract types and contract types which include a lot of additional data and attachments.
Is it meaningful to declare another bounded context just because the aggregate's data is more complex?
Both approaches have their drawbacks:
Implementing all contract types in one bounded context might lead to a lot of if-Statements in the code in order to handle the differing data.
Extracting a new bounded context might lead to a lot of duplicate code just because some data differs.
Any suggestions / best practices how to handle this?
...domain events / process and that's the major building block we used
to identify our bounded contexts
BCs are not identified by processes, BCs are related to the language. Each BC has its own ubiquitous language (UL). A BC is the context in which a concept has meaning. Anyway BCs belong to the solution space. First of all you should explore the domain (problem space) and split it in subdomains, distilling the core domain. Then you model each subdomain. A BC is the context where a model lives. Ideally the relationship between subdomains and BCs is 1:1.
The process of discovering subdomains is iterative, and you will find them as you know the domain better, talking to experts. When you find a word that may have different meanings, or when two different words have the same meaning, then it means that you are crossing a boundary between BCs.
So, subdomains identification is not about processes, but about concepts and UL.
Is it meaningful to declare another bounded context just because the
aggregate's data is more complex?
No, you shouldn't create BCs arbitrary just because aggregates are complex. BCs are based on the UL.
Any suggestions / best practices how to handle this?
You should learn more about the domain (contract, types, etc) by talking to domain experts, and study your aggregate (transactional consistency)... Anyway, if you split your aggregate into anothers, it doesn't mean that they belong to different BCs, they still can belong to the same BC. A BC can have more than one aggreagate. It all depends on your concrete domain.
Bounded contexts have little to do with if-statements, so I'm not sure what you mean.
Bounded contexts are a semantically closed set of business functionalities. Basically your bounded context is well defined if it can execute its functions in complete isolation, even if the other contexts are not available.
Other than that, you can have any design inside of the context. I feel the amount of if-statements depends more on your class/code-design, like whether you use polymorphism correctly, interfaces, things like that.
But, to your point: You don't need to have everything perfect the first time. If you identified some valid contexts, you already did the hard part. If any context can be further divided, that could happen later anytime with little impact on others (since contexts are more or less isolated).
No specific business teams for different kinds of contracts
No dedicated dev team for specific kinds of contracts
Same ubiquitous language is used for all contracts
Every contract nearly follows the same process
These to me are signs that all contracts belong to the same business subdomain and should ideally be in the same Bounded Context - unless legacy or third party systems force you otherwise.
How do you plan and divide your application into bounded contexts? On the one hand it's very handy having all this decoupling, but on the other hand too much granularity can lead to a very cumbersome development. Where is this thin line? What factors do you take under consideration when designing your bounded contexts and context maps? Are they technical, strategic maybe?
In a lot of examples you see e-commerce app as a set of bounded contexts: catalog, shopping, invoicing, delivery etc.
Where are those coming from?
Technical, definitely not...
Did you heard about Event Storming ? I think it is a good way to found your boundaries...
Bounded context is about language, it is not technical or architectural think, but more about your domain and the words used by your Domain Expert : Greg Young explain-it well in the beginning of this talk
When you are modelling your Domain Model, you have to define your boundaries (for eCommerce, Product is not the same concept with the same attributes in different contexts : catalog, shopping... The same thing about customer... And you don't have to use the same entity for each context, so every context have its specific Ubiguitous language), think about this activity iteratively (sometime Bounded Contexts emerge and are not clearly visible first)
As soon as you use both same words to define distinct concepts, you are faced to both bounded contexts.
Suppose an application dealing with movies rental.
If your team (business/developers) talk about "Users" to define users in terms of identity and access, and "Users" too to in terms of Renters, then you would have both bounded context:
IdentityAndAccessContext (managing authentication, roles etc.. technical)
MovieRentalContext (unaware of Users, but Renters)
Bounded context is the reflection of the chosen ubiquitous language.
For modeling domain from scratch I think is good decision to start from one bounded context and one module. Than concepts start to conflict or look strange keeped in one module, it's time to separate some concepts into distinct module (or reorganize between modules).
Same approach for separating modules into distinct bounded contexts.
I've been reading Eric Evans' DDD: Tackling Complexity in the Heart of the Software and in the section in context maps, Evans cited an example of 2 bounded contexts (Booking context and Network Traversal Service) using a translator to integrate them.
If I understand correctly, when we create a model, we put everything into bounded contexts creating conceptual boundaries for the domain. My questions are:
If everything should be in a bounded context, where should the translator be located? In Evans' sample diagram, the translator is outside (in between) both bounded contexts.
Say we have a single team working on a ERP. Should the ERP be put in several bounded contexts or a single one only. Based on Evans' sample, the bounded contexts were devised so that multiple teams could work on their own model. But since this is a single team, wouldn't they benefit on a single model so integration wouldn't be an issue? Or did I understand this wrong?
In question 2's case, if multiple bounded contexts, what if in implementation, we need a class from Accounting to be used in Payroll? I don't think we need a translator here but I'm not sure how to share the required class. Will just referencing the needed class from another bounded context be fine?
And lastly, how is a module in DDD related to a bounded context?
In an Infrastructure layer (outside the Domain), it's a technical detail.
A bounded context(BC) emerges from the Domain, that's why we identify them and not defining them. Once identified, if there are many BCs and there are developers available then the workload can be split so that the app can be developed faster. A single model is not advisable, you want a single domain model per BC and also a simplified read model for querying (CQRS).
I don't know but for me Payroll is part of Accounting, there aren't really 2 BC. Account is a BC, however other BC might need a Payroll but that definition is specific to the BC. And probably the definition is just really some data (a read model). Payroll behaviour should stay in Accounting. So you need to have clearly defined what each BC understands by "Payroll". Usually you'll have a domain service who will use concepts from both BC and which will use the 'translator'.
It isn't. A module just groups things together from a technical point of view. A BC is pretty virtual, you might choose to have a project or a module corresponding to one BC but that's your decision how to organize things.
If everything should be in a bounded context, where should the translator be located? In Evans' sample diagram, the translator is
outside (in between) both bounded contexts.
I think this question has no sense. Each BC is a boundary in wich a domain model exist, and the boundary is determined by the UL, each BC has its own UL. If you just create one model, you don't need translation. Instead of spliting a big model into several smaller ones, you are joining them.
Say we have a single team working on a ERP. Should the ERP be put in several bounded contexts or a single one only. Based on Evans'
sample, the bounded contexts were devised so that multiple teams could
work on their own model. But since this is a single team, wouldn't
they benefit on a single model so integration wouldn't be an issue? Or
did I understand this wrong?
I think you understood wrong. You split a single model into several BCs according to the UL, not to the available teams. Then if you don't have enough teams for the number of BCs created, a team will have to develop more than one BC. By the way, the opposite is not desirable, i.e., a BC shouldn't be developped by more than one team.
In question 2's case, if multiple bounded contexts, what if in implementation, we need a class from Accounting to be used in Payroll?
I don't think we need a translator here but I'm not sure how to share
the required class. Will just referencing the needed class from
another bounded context be fine?
You shouldn't reference a domain object of another BC. That BC should protect its domain model, that's the application layer for. The application layer expose DTOs, plain objects, it shouldn't expose the domain model to the outside world. What you are asking for is a shared kernel (a model shared by other BC).
And lastly, how is a module in DDD related to a bounded context?
A module is just a java package, useful for keeping together things that a related to each other. So you split the source code of a BC in modules. Do it according to the UL, not to technical aspects. For instance, a module for each aggregate, not a module for entities, another one for repositories, etc.
In my current project (e-commerce website), we have different Bounded Context like: billing, delivery or payment in our checkout process.
On top of this, depending on what the customer will buy, the checkout process will be different. So depending on the content of her cart the number of steps in the checkout process can be different, or we won't/will ask her for certain informations.
So should one create a different bounded context for each different type of checkout process ?
For example, the Order aggregate root will be different depending on the checkout process
EticketsOrder (in this context we don't need a delivery address so we won't ask one to the user)
Ticket BillingAddress
ClothesOrder (in this context we need a delivery address and there will be an additional step in the checkout process to get this)
Clothes BillingAddress DeliveryAddress
This separation will imply to create two different domain entities even thought they have similar properties.
What's the best way to model this kind of problem ? How to find the context boundary ?
A bounded context is chiefly a linguistic boundary. A quote from the blue book (highlighted key part):
A BOUNDED CONTEXT delimits the applicability of a particular model so
that team members have a clear and shared understanding of what has
to be consistent and how it relates to other CONTEXTS. Within that
CONTEXT, work to keep the model logically unified, but do not worry
about applicability outside those bounds. In other CONTEXTS, other
models apply, with differences in terminology, in concepts and rules,
and in dialects of the UBIQUITOUS LANGUAGE.
A question to ask is whether the different types of orders created are entirely distinct aggregates, or are they all order aggregates with different values. Is there a need to consider order as a whole regardless of how they were created? I've build and worked with ecommerce systems where different types of orders were all modeled as instances of the same aggregate, just with different settings and there were no linguistic issues. On the other hand, the orders in your domain may be different enough to warrant distinct contexts.
I often consider BC boundaries from the perspective of functional cohesion. If you segregate orders into two BCs will there be a high degree of coupling between them? If so, that may be a sign that they should be combined into one BC. On the other hand, if the only place that the BCs interact is for reporting purposes, there is no need to combined them.
It appears as though you may have missed a bounded context. When this happens one tends to try and fit the functionality into an existing BC. The same thing happens to aggregate roots. If something seems clumsy or it doesn't make sense try to see whether you haven't missed something.
In your example I would suggest a Shopping BC (or whatever name makes sense). You are trying to fit your checkout process into your Order BC. Your Shopping BC would be responsible for gathering all the data and then shuttling it along to the relevant parts.
The product type selected will determine whether a physical delivery is required.
Hope that helps.