Reputation: 20314
I am trying to learn DDD
. I am modeling a property management domain and I think I have two contexts (subdomains?): a property management context and a resident context.
Let's say I have an aggregate root Apartment
, that holds Floorplan
s and Unit
s. Each Apartment
can have many Floorplans
, and each Floorplan
can have many Unit
s.
public class Apartment : IAggregateRoot // for clarity
{
public int Id { get; }
public Address Address { get; set; }
public ICollection<Floorplan> Floorplans { get; set; }
}
public class Floorplan
{
public int Id { get; }
public int ApartmentId { get; set; }
public string Name { get; set; }
public int Bedrooms { get; set; }
public int Bathrooms { get; set; }
public ICollection<Unit> Units { get; set; }
}
public class Unit
{
public int Id { get; }
public int FloorplanId { get; set; }
public string Number { get; set; }
}
Let's say in the property management context I now introduce a Resident
who gets assigned to a Unit
. My Unit
and Resident
class now looks like this:
public class Unit
{
public int Id { get; }
public int FloorplanId { get; set; }
public string Number { get; set; }
public ICollection<Resident> Residents { get; set; }
}
public class Resident // in the property management context
{
public int Id { get; }
public string FirstName { get; set; }
public string LastName { get; set; }
public void UpdateBalance(...);
}
My question is now if I introduce a Resident
in the resident context (that can PayRent()
or UpdateProfile()
, etc) they must have a 1:1 relationship with Resident
in the property management context, but I thought I cannot reference a non-aggregate root entity without going all the way through Apartment
because a Resident
cannot exist without an Apartment
.
Is my understanding of aggregate roots incorrect? Is Resident
an aggregate root in both contexts? I'm not sure how that would be modeled.
Upvotes: 1
Views: 569
Reputation: 1244
Is
Resident
an aggregate root in both contexts?
No, it is only the aggregate root in the Resident
aggregate, in the Resident
context. When you remove an Apartment
entity it causes deleting all the other entities in the aggregate inclusive the Resident
entity (only in the Property Management
context). But when a Resident
leaves a unit in the apartment it doesn't require to delete any other entity within the Apartment
aggregate.
Is my understanding of aggregate roots incorrect?
No, you are right saying
I cannot reference a non-aggregate root entity without going all the way through
But not only an aggregate root of one aggregate can refer to the root of another aggregate. A non-root entity from one aggregate can reference the aggregate root from another aggregate. Please take a look at the image below.
This image is taken from the Pluralsight course Domain-Driven Design in Practice
For identifying the root entity in aggregate use this principle that is already described in my answer above (the quote is taken from the book Applied Akka Patterns):
Aggregates, and their associated roots, are a tricky concept. It can be difficult to determine what is an aggregate in your system, or more importantly, what is the right aggregate root. Generally, aggregate roots are the top-level pieces of a system. All of your interactions with the system will in one way or another interface with an aggregate root (with a few exceptions). So how do you determine what your aggregate roots are? One simple rule is to consider deletion. If you pick a specific Entity in the system and delete it, does that delete other Entities in the system? If your system consists of people who have addresses, and you delete an address, does it delete other parts of the system? In this case, probably not. On the other hand, if you delete a person from the system, there is a good chance that you don’t need to keep that person’s address around anymore, so in this case, a person might aggregate an address.
Now your question about how it should be modeled:
My question is now if I introduce a
Resident
in theResident
context (that can PayRent() or UpdateProfile(), etc) they must have a 1:1 relationship withResident
in theProperty Management
context, but I thought I cannot reference a non-aggregate root entity without going all the way throughApartment
because aResident
cannot exist without anApartment
.
In my opinion, Resident
in the Property Management
context should be presented as an entity since it has an ID (e.g. passport, driving license) and the entity must have only those properties from the entity Resident
in the Resident
context that will be used in the Property Management
context.
I would suggest reconsidering the name Resident
for the context Resident
. When you keep information about a person but this person is not currently living in an apartment he\she is not technically Resident
of this apartment, it is just a person that is planning\planned to live, currently lives, or lived in the apartment. I think, the better name for what you call the Resident
context is Person
.
For communication between the contexts use integration events (don't confuse them with domain events):
Domain events versus integration events
Semantically, domain and integration events are the same thing: notifications about something that just happened. However, their implementation must be different. Domain events are just messages pushed to a domain event dispatcher, which could be implemented as an in-memory mediator based on an IoC container or any other method.
On the other hand, the purpose of integration events is to propagate committed transactions and updates to additional subsystems, whether they are other microservices, Bounded Contexts or even external applications. Hence, they should occur only if the entity is successfully persisted, otherwise it's as if the entire operation never happened.
As mentioned before, integration events must be based on asynchronous communication between multiple microservices (other Bounded Contexts) or even external systems/applications.
Thus, the event bus interface needs some infrastructure that allows inter-process and distributed communication between potentially remote services. It can be based on a commercial service bus, queues, a shared database used as a mailbox, or any other distributed and ideally push based messaging system.
I have noticed that this part of your question:
I am trying to learn DDD. I am modeling a property management domain and I think I have two contexts (subdomains?)
sounds like you are not sure about the meaning of terms context and subdomain. Please take a look at the image below; it is quite self-explanatory:
This image is taken from the Pluralsight course Modern Software Architecture: Domain Models, CQRS, and Event Sourcing
A sub-domain represents a part of a problem, whereas a bounded context represents a part of a solution that solves the problem. Each sub-domain usually covered by one bounded context. In some cases, one sub-domain may correspond to more than one bounded context, e.g. when you have a sub-domain with old requirements and a bounded context that already implemented these requirements, and later you have new requirements and you don't want to change the existing bounded context for some reason, then you may introduce the new bounded context for these new requirements. This new context should be separated via anti-corruption layer from the old one.
Upvotes: 1
Reputation: 1241
My question is now if I introduce a Resident in the resident context (that can PayRent() or UpdateProfile(), etc) they must have a 1:1 relationship with Resident in the property management context, but I thought I cannot reference a non-aggregate root entity without going all the way through Apartment because a Resident cannot exist without an Apartment.
It depends on your architecture. For the most part, it makes sense to me to have an event outside of your domains that'll be handled by both contexts (flowing through the application service down to the aggregates) and will react by "creating" the appropriate actors (a resident and property managent client?). Ensuring 1:1 here would imply ensuring that all events are handled and handling failures graciously (This sounds like a fun excercise).
All contexts will have a boundary. The boundary will serve as the contexts "interface" to consumers. Any communication between contexts should go through this "interface". As an example: If your contexts is implemented as a microservice (a REST API), you should only communicate to it by sending HTTP REST commands. This isolation keeps the contexts clean. That is the purpose of having contexts.
Upvotes: 1