Let's say I have three aggregates in the same bounded context:
PhoneAggregate
ServiceCenterAggregate
ServiceWorkAggregate
ServiceWorkAggregate references both PhoneAggregate and ServiceCenterAggregate by id.
Now when new ServiceWorkAggregate is created, a POST request is sent:
{
"phone_id": uuid,
"servicecenter_id": uuid
}
I can't just create a new aggregate using those IDs, I need to perform some checks: at least confirm that those UUIDs are valid entity IDs, maybe also perform some business logic checks involving current state of selected PhoneAggregate and ServiceCenterAggregate. What is the best way to do this?
I could make ServiceWorkAggregate application service or domain service include repositories for all three aggregates.
if I go this route, should application service just pass IDs into domain service and domain service would load aggregates and do all the checks, since there could be business login involved.
or should application service fetch aggregates and pass them into domain service?
I could treat referenced aggregates as value objects and instead of phone_id and servicecenter_id fields inside ServiceWorkAggregate use value objects Repairable and ServiceLocation, which would be immutable copies of those aggregates, fetched from the same table etc. Then I would need only one repository in my servicework application service.
Is there a better way?
A lot of developpers think that the domain layer of an application must be designed as a single normalized data model, like an RDBMS schema. This is not true, and is the cause of many suffering.
The fact that you have 3 different contexts does not mean that the ServiceWork context is not able to manipulate data from the other two concepts. It only means that this context is not able to authoritatively change these concepts states. Put in other words, ServiceWorkRepository can read data about the Phone context, but it cannot create, delete or change a phone's state.
Also, you don't need the same information regarding a phone's state when you are changing a phone's context, or when you are changing a service work's context. This allows you to have two different models for the same concept, depending on the current context. Let's call them ServiceWorkContext.PhoneEntity and PhoneContext.PhoneEntity. These entities can map to the same table/columns in the database, but they can have different level of details: flattened relationships, fewer columns read, and do not allow changing the state.
This concept is called polysemic domain modeling.
In other words, you have the following classes:
MyApp.Domain.PhoneContext.PhoneAggregate
MyApp.Domain.PhoneContext.PhoneRepository
MyApp.Domain.ServiceCenterContext.ServiceCenterAggregate
MyApp.Domain.ServiceCenterContext.ServiceCenterRepository
MyApp.Domain.ServiceWorkContext.PhoneEntity
MyApp.Domain.ServiceWorkContext.ServiceCenterEntity
MyApp.Domain.ServiceWorkContext.ServiceWorkAggregate
MyApp.Domain.ServiceWorkContext.ServiceWorkRepository
MyApp.Domain.PhoneContext.PhoneAggregate represents the phone's state when you want to create or update a phone, and validates business rules regarding these use casses.
MyApp.Domain.ServiceWorkContext.PhoneEntity represents a simplified readonly copy of the phone's state, used when trying to create / update a service work. For instance it can contain a single Country property (which maps to Phone->Owner->Address->Country->Name) to be compared with the ServiceCenter's country, if you have a business rule stating that the phone's country must match the service center's. In that case, you may not need to read Phone.Number from the database for instance.
Related
type Resource struct {
id string
tenantID string
FieldValues map[string]interface{}
created time.Time
updated time.Time
}
type ResourceField struct {
id string
tenantID string
name string
fieldType string
created time.Time
updated time.Time
}
I'm new on Domain driven Design and i need some help to model this in DDD with event sourcing. In this scenario the ResourceField is a global concept, i.e. is the same for all the Resource instances.
I try to model both of them as AR: ok but when i have to delete a ResourceField i have to update all the Resource instances removing from the FieldValues map the correct key. So when a ResourceField Delete command is received and performed, then an event is emitted. Now Resource listen to this event and ... the problem: i have to update all the Resources with that field ID. I have to load all the Resources, update each of them, and save the new events. But if i have thousand of thousand of Resources?
What you are trying to do is the #1 antipattern in DDD which is trying to create a single unified model with all data in memory. This would require a disproportionate amount of computing power.
The purpose of DDD is to model your business in a dedicated layer called domain layer. The purpose of that layer is to provide your application with code that can validate your business rules, preventing rules violation by the rest of the application. Rules violation cannot happen during query operations, as queries, by definition, do not alter the system's state. It is useful only in command operations. Also, most commands only alter a small part of the domain model, called a context, and you don't need to use code from other contexts when trying to validate an operation in a given use case. This is why it is paramount to model your domain layer based on command use cases, and not as a single relational model which is what you do at the persistence layer level (or equivalent).
Each context uses a model to validate rules integrity. This model is either a single entity or a structure of multiple objects, called an aggregate, among wich one is an entity and called the aggregate root. By modeling both Resource and ResourceField as aggregate roots, you place them in different contexts. This requires that your business allows the existence of each independantly: you can create a Resource without a ResourceField, and you can create a ResourceField without a Resource. But of course, DDD allows reference between two contexts, as long as you only reference data from another context to the identity property of the aggregate root entity of the other context. This means Resource.FieldValues can only reference ResourceField.id.
Now your question considers a saga which sequences two use cases:
Delete a ResourceField
Read a Resource which previously referenced that ResourceField
When implementing the domain layer for the first use case, you should not consider how the relationship reference gets updated. This is the purpose of the persistance layer, and the domain layer is persistence agnostic. The purpose of the domain layer is to validate that the command does not violate the business rules, so it should focus on cases that would prevent users from deleting the ResourceField. How this is implemented depends on your business rules but common examples would be to deny deleting objects with a specific state, objects with creation date older than a specific delay, etc ...
Then implementation of the reference update is delegated to the persistence layer. Depending on the architecture of your solution, both the Resource and the ResourceField contexts may share the same persistence backend (aka single-database) or not (aka database-per-service).
In a single-database approach, when using a relational DBMS with foreign key constraints, you can delegate that reference update to the databse engine, through usage of the ON DELETE clause. If using a non-foreign key reference, or a non-relational database, it is up to your persistence layer to look for and update references in other tables.
In a database-per-service approach, you cannot rely on the persistence layer to update the reference, because the reference is stored in the persistence store of the other context, which you do not have access to. In that situation, you need to raise an event in the ResourceField context, which the Resource context will register to. Then it is up to the Resource persistence layer to update references in the persistence store.
Once the reference is updated in the persistence layer, the Resource context will be able to query the updated state transparently.
I'm having some difficulties figuring out how a reconstitution factory works when paired with DDD.
From what I understand, this factory would be owned by the repository layer (or rather, not the domain) and is simplified, as it expects all entities stored to be valid already.
Where I get confused, is how could a factory that lives in a layer outside the domain, know how to create a domain object while essentially bypassing any invariant checks? I only open specific methods on the aggregate to allow creation/updating of that entity. For example, a Survey has a list of people on it and I expose a method to add a person to the Survey (domain checks to make sure you're not adding 1 person multiple times). I have no other way of adding people to that Survey aggregate.
If it helps, the reason I'm considering this option is because after I send a Survey, I want to differentiate people who have received the Survey and those who haven't when adding them to the Survey aggregate. That way when I send to new people, I know who should receive it vs those who have already received it. I could just allow a nullable time parameter when adding a person, but I felt like that did not adequately communicate intent.
Thanks for reading.
How does a reconstitution factory bypass invariants?
The usual answer for a factory is that it doesn't use the domain methods to initialize the aggregate, but instead copies information into the aggregate via constructors.
In other words, the responsibility of the factory is to construct the object graph, and nothing else. It takes information out your data model, copies it into value objects, initializes entities, and ensures that your new in memory representation is in the same state as when the aggregate was stored.
My team is building a new microservice leveraging techniques from Domain-Driven Design and Event Sourcing. This service has to integrate with a handful of external bounded contexts (BCs) in the form of other legacy services. We've identified our core domain model, and its clear that we need some sort of Anti-corruption layer (ACL) between the external BCs and our internal domain model. However, we're getting hung up on some of the technical details of how best to accomplish this.
Let's say our domain has an Asset aggregate which can represent some piece of equipment from several of these external BCs. Once of these BCs, let's say BC-A, sends out EquipmentUpdate events on a message broker that our application can subscribe to. These events carry no intent - they merely reflect that the state of the external entity has changed in some way and it's up to us to determine what actually changed. The ID in the events is also from the external BC, not our domain.
So our ACL has to do the following tasks:
Map the external identifier from BC-A to our internal aggregate
Perform a diff of the new event and current/previous state to figure out what actually changed
Do this in a way that is resilient against out-of-order and duplicate event messages
Option 1 - Query directly from repository
First option is to use the external identifier directly in our repository to fetch the matching Asset. This seems like the simplest option, however it feels wrong since it's leaking concepts from external BCs into our repository API.
Furthermore, it forces us to to store external identifiers and event version directly on our aggregate which also feels like it defeats the purpose of the ACL.
interface AssetRepository {
Optional<Asset> findAssetByExternalIdentifier(String externalId);
}
Option 2 - Dedicated mapping
Second option is to expose a dedicated query that uses the external identifier to query the matching internal identifier, as well as the latest version that was processed.
This feels cleaner, but requires an additional read from the database where option 1 was a single read.
data class AssetMappingQueryResult {
String AssetId;
Long LatestVersion;
}
interface AssetMappingQuery {
Optional<AssetMappingQueryResult> resolveFromExternalIdentifier(String externalId);
}
How are other teams doing this?
I would tend to treat the EquipmentUpdate message as a signal that something might have changed. On receipt of such a message, the ACL queries BC-A for the latest state for the associated IDs, compares that state with the state it received the last time, and emits commands corresponding to the state changes which are of interest to the bounded context you're developing. In the case of duplicate messages (where the second event conveys no state change), this approach is idempotent. The confluence of "select current" likewise makes out-of-order not a concern. The ACL may want to guard against concurrent modifications involving the same ID, though viewing its output as commands against your BC's write model might make that unnecessary (especially depending on the chance that one of the concurrent modifications might be slow). The specific techniques for that vary, my personal preference coming from the Akka world would be to have responsibility for a given ID assigned to an actor.
An ACL is by its nature somewhat outside of any bounded context: it's analogous to the space between customs/immigration outposts on the border between countries. One could say it's partially in both (it's also, from a CQRS standpoint, a read-model for the "other" bounded context, though it may be a "read-model once removed", given that it should probably query a read-model in the source bounded context) bounded contexts. Alternatively, one could call an ACL a miniature bounded context which incorporates knowledge of parts of two other BCs: this may even extend to having its own aggregates, repositories, etc.
Which layer should be responsible for checking existence of some entity in the database?
Let's say I have an order as aggregate and that order can contain multiple items. Logic implies that I can add only existing items to order.
Should I write it like this in the application service:
var item = ItemRepository.GetByID(id);
//throws exception if the item is null
order.AddItem(item);
or
//validate item existence inside aggregate function
order.AddItem(item, IItemRepository repo);
Neither, really.
Entities don't cross aggregate boundaries. Either the entity is part of the aggregate, in which case the aggregate manages its own life cycle, or the item is part of some other aggregate, in which case you don't share the entity, you share a reference.
order.AddItem(id)
Part of the definition of aggregate is that changes in different aggregates can happen independently of each other. In other words, there's no way that this aggregate can know what is happening in that aggregate "now".
In other words, you can't ensure transactional consistency across an aggregate boundary.
The right answer, if you are willing to accept a data race, is to use a domain service to query the state outside the boundary.
interface InventoryService{
boolean currentlyInStock(Item id);
}
// ...
order.addItem(id, inventoryService);
A few points:
Use a domain service rather than passing in the other repository, because it better communicates what is going on. The domain service serves as a description of the contract that the aggregate actually needs. Furthermore, by refusing to pass the repository, you exclude any possibility that the order aggregate attempts to write to the item repository.
(The trivial implementation of this domain service is to just forward the call to the repository, but the order aggregate doesn't need to know that).
The domain service, in this case, should not be "helping" by choosing an action based on the availability of the inventory -- maybe the aggregate should throw, maybe the aggregate should throw for low volume orders/low priority purchasers, but use different rules when the order is over a million dollars. It's the order's job to figure that out, the domain service just provides the data.
Given the data race, some false positives can slip through; detection and mitigation is a good idea.
If you aren't willing to accept the data race (are you sure? Amazon accepts order for out of stock items all the time...), then you need to rethink the design of your model, and where you have set your aggregate boundaries.
The null design of a model is to capture all of the business state into a single aggregate; its own state is internally consistent, but it might not be consistent with external state. When you start carving up the model into separate aggregates, you are making the same assertion -- the aggregate needs to be internally consistent, but it might not be consistent with state outside the aggregate.
If that's not acceptable, then you turn Mother's picture to the wall, and implement your business rules directly in the book of record (ie, constraints in your RDBMS).
Quotes are from DDD: Tackling Complexity in the Heart of Software ( pg. 150 )
a)
global search access to a VALUE is often meaningles, because finding a
VALUE by its properties would be equivalent to creating a new instance
with those properties. There are exceptions. For example, when I am
planning travel online, I sometimes save a few prospective itineraries
and return later to select one to book. Those itineraries are VALUES
(if there were two made up of the same flights, I would not care which
was which), but they have been associated with my user name and
retrieved for me intact.
I don't understand author's reasoning as for why it would be more appropriate to make Itinierary Value Object globally accessible instead of clients having to globally search for Customer root entity and then traverse from it to this Itinierary object?
b)
A subset of persistent objects must be globaly accessible through a
search based on object attributes ... They are usualy ENTITIES,
sometimes VALUE OBJECTS with complex internal structure ...
Why is it more common for Values Objects with complex internal structure to be globally accesible rather than simpler Value Objects?
c) Anyways, are there some general guidelines on how to determine whether a particular Value Object should be made globally accessible?
UPDATE:
a)
There is no domain reason to make an itinerary traverse-able through
the customer entity. Why load the customer entity if it isn't needed
for any behavior? Queries are usually best handled without
complicating the behavioral domain.
I'm probably wrong about this, but isn't it common that when user ( Ie Customer root entity ) logs in, domain model retrieves user's Customer Aggregate?
And if users have an option to book flights, then it would also be common for them to check from time to time the Itineraries ( though English isn't my first language so the term Itinerary may actually mean something a bit different than I think it means ) they have selected or booked.
And since Customer Aggregate is already retrieved from the DB, why issue another global search for Itinerary ( which will probably search for it in DB ) when it was already retrieved together with Customer Aggregate?
c)
The rule is quite simple IMO - if there is a need for it. It doesn't
depend on the structure of the VO itself but on whether an instance of
a particular VO is needed for a use case.
But this VO instance has to be related to some entity ( ie Itinerary is related to particular Customer ), else as the author pointed out, instead of searching for VO by its properties, we could simply create a new VO instance with those properties?
SECOND UPDATE:
a) From your link:
Another method for expressing relationships is with a repository.
When relationship is expressed via repository, do you implement a SalesOrder.LineItems property ( which I doubt, since you advise against entities calling repositories directly ), which in turns calls a repository, or do you implement something like SalesOrder.MyLineItems(IOrderRepository repo)? If the latter, then I assume there is no need for SalesOrder.LineItems property?
b)
The important thing to remember is that aggregates aren't meant to be
used for displaying data.
True that domain model doesn't care what upper layers will do with the data, but if not using DTO's between Application and UI layers, then I'd assume UI will extract the data to display from an aggregate ( assuming we sent to UI whole aggregate and not just some entity residing within it )?
Thank you
a) There is no domain reason to make an itinerary traverse-able through the customer entity. Why load the customer entity if it isn't needed for any behavior? Queries are usually best handled without complicating the behavioral domain.
b) I assume that his reasoning is that complex value objects are those that you want to query since you can't easily recreate them. This issue and all query related issues can be addressed with the read-model pattern.
c) The rule is quite simple IMO - if there is a need for it. It doesn't depend on the structure of the VO itself but on whether an instance of a particular VO is needed for a use case.
UPDATE
a) It is unlikely that a customer aggregate would have references to the customer's itineraries. The reason is that I don't see how an itinerary would be related to behaviors that would exist in the customer aggregate. It is also unnecessary to load the customer aggregate at all if all that is needed is some data to display. However, if you do load the aggregate and it does contain reference data that you need you may as well display it. The important thing to remember is that aggregates aren't meant to be used for displaying data.
c) The relationship between customer and itinerary could be expressed by a shared ID - each itinerary would have a customerId. This would allow lookup as required. However, just because these two things are related it does not mean that you need to traverse customer to get to the related entities or value objects for viewing purposes. More generally, associations can be implemented either as direct references or via repository search. There are trade-offs either way.
UPDATE 2
a) If implemented with a repository, there is no LineItems property - no direct references. Instead, to obtain a list of line items a repository is called.
b) Or you can create a DTO-like object, a read-model, which would be returned directly from the repository. The repository can in turn execute a simple SQL query to get all required data. This allows you to get to data that isn't part of the aggregate but is related. If an aggregate does have all the data needed for a view, then use that aggregate. But as soon as you have a need for more data that doesn't concern the aggregate, switch to a read-model.