Related
Let me preface this by apologising if this was asked before (I could not find a similar question when doing a quick search).
The DDD pattern is centered around defining and isolating aggregates to split the business complexity into a more manageable chunks, and it is strictly forbidden for an aggregate to hold any kind of relation to other aggregates. The references to the other aggregates are instead to be stored as ids that can then be used to fetch the other aggregates on demand. Therefore a single aggregate would only contain its properties, value objects and references to other aggregates.
To use a common example (User, Order) it would look as such:
public class User {
private Long id;
private List<Long> orders;
}
and
public class Order {
private Long id;
private Long userId;
}
However I have seen multiple sources use another layer of encapsulation for the aggregate references, turning them from the property types (as defined in the example above) into value objects like shown below:
public class User {
private Long id;
private List<OrderId> orders;
}
and
public class Order {
private Long id;
private UserId userId;
}
I am rather new to DDD so I want to understand the benefit of doing so when working with non-composite ids.
On the first glance I see a lot of pretty obvious drawbacks (or such they seem to me!), like explosion in quantity of common base types, serialization issues, extra complexity when working with the code and accessing the values stored within these holders, however I am sure that it would not be done so without a very good reason that I am overlooking somewhere.
Any comments, thoughts or feedback would be very welcome!
This is a domain abstraction
private UserId userId;
This is a data structure
private Long userId;
Your domain logic (probably) doesn't care, or need to care, about the underlying data structure that supports representations of UserId, or how they are stored in the database, or any of that nonsense.
The broad term is "information hiding" -- creating firewalls around decisions such that the decision can be changed without that change cascading into the rest of the system. See Parnas 1971.
There are some mistake detection benefits as well. Consider
todays_order.userId + yesterdays_order.userId
That's utter nonsense code; adding two identifiers together doesn't do anything useful. But adding to Long values together is a perfectly normal thing to do in other contexts, and the compiler isn't going to catch this mistake.
recindOrder(orderId, userId)
Did you catch the bug? I've got the arguments in the wrong order! When the method signature is
recindOrder(Long userId, Long orderId)
The machine can't help me catch the problem, because I haven't given it the hints that it needs to look beyond the data structures.
There is also a theory that by providing an explicit representation of the domain value, that code attracts other related functions that otherwise might not find a home -- in effect, it improves the coherence of your design.
(In my experience, that's less true of semantically opaque types like identifiers than it is for numerical abstractions like money. However, if you have some identifiers that are reserved, then the identifier type becomes a convenient place to document the reservation.)
In the red book (Implementing Domain-Driven Design) Vernon Vaughn shows an aggregate example for a Scrum Core Domain. Unfortunately the code samples are just fragments.
For the second attempt the model is split in multiple aggregates instead of using a single large aggregate. As a result the method contract for the planBacklogItem method changes from
public class Product ... {
...
public void planBacklogItem(
String aSummary, String aCategory,
BacklogItemType aType, StoryPoints aStoryPoints) {
...
}
...
}
to
public class Product ... {
...
public BacklogItem planBacklogItem(
String aSummary, String aCategory,
BacklogItemType aType, StoryPoints aStoryPoints) {
...
}
}
The application service looks then like this:
public class ProductBacklogItemService ... {
...
#Transactional
public void planProductBacklogItem(
String aTenantId, String aProductId,
String aSummary, String aCategory,
String aBacklogItemType, String aStoryPoints) {
Product product =
productRepository.productOfId(
new TenantId(aTenantId),
new ProductId(aProductId));
BacklogItem plannedBacklogItem =
product.planBacklogItem(
aSummary,
aCategory,
BacklogItemType.valueOf(aBacklogItemType),
StoryPoints.valueOf(aStoryPoints));
backlogItemRepository.add(plannedBacklogItem);
}
...
}
I understand the idea of aggregates and the sample code, but I wonder why planBacklogItem is not declared as static in the multiple aggregate version. The method does not access any instance data. Separate aggregates were used to achieve better concurrent access. Therefore I don't understand why the application service first pulls a full product from the repository, while none of its data is needed.
Since the BacklogItem uses Ids it can be created without reading the product from the repository. In case the product aggregate has lots of data and child aggregates are accessed frequently, the implementation can cause performance issues.
The only explanation I could come up with is that it should ensure the existence of the product. But why would you not use a method like productRepository.existsProduct(productId) instead?
I'm working with C# and don't understand all the magic of #Transactional. Vaughn did not say anything about the isolation level. Race conditions between reading the product and writing the BacklogItem could occur. Does #Transactional create a serializable transaction? I doubt it since not all storages support it. If the isolation level of the transaction is not serialized or the product is not locked during read it could be deleted before the BackLogItem was written. (It might not be a business case for the scrum examples, but it is a general concern).
I'm afraid that I miss something major. Thanks for any help.
The method does not access any instance data.
Not sure if the book examples are incorrect, but it does use instance data on GitHub samples.
public BacklogItem planBacklogItem(
BacklogItemId aNewBacklogItemId,
String aSummary,
String aCategory,
BacklogItemType aType,
StoryPoints aStoryPoints) {
BacklogItem backlogItem =
new BacklogItem(
this.tenantId(), // <--
this.productId(), // <--
aNewBacklogItemId,
If the isolation level of the transaction is not serialized or the product is not locked during read it could be deleted before the BackLogItem was written.
Indeed, there could be a race condition (assuming we can remove products). TBH I don't know LevelDB enough and didint' dug into the actual implementation details, but a traditionnal foreing key constraint could prevent orphans in a relationnal DB.
However, that wouldn't really work for logical deletes (e.g. archiving) so in these cases I guess there's those choices:
Ignore the problem. Perhaps it doesn't actually matter that a backlog item gets planned a millisecond after a product has been archived? Race Conditions Don't exist?
Lock the Product AR. That could be done in numerous ways, such as forcing an optimistic locking version bump.
Apply an eventually consistent compensating action, such as unplanning a planned backlog item.
I was wondering whether it would be considered bad practice to use an aggregate identifier across a service in another (extensipn) aggregate which shares that they are both revolving about the same identifiable entity.
The problem I am currently having is that we want to split some logic (bounded context if you so will) into a different service as the one originally creating the aggregate.
In general, this seems to work, as when I send a Command within the second service, it is picked up and updates its state. As I can use EventSourcingHandler to also use Events created in the other service to manipulate its state, I get state information from a source applied by the first services aggregate.
I was worried that the snapshot mechanism would work against me, but apparently it is smart enough to store snapshots separately as long as I make sure the aggregate "type" name is not the same.
So far, so good, the only thing that's a smell for me is that the second aggregate does not have (needs) an initial constructor CommandHandler, as the creation is done in the first aggregate.
So, am I going against the way axon framework intends aggregates to be used, or is this a viable use case?
#Aggregate
#Getter
#NoArgsConstructor
public class Foo {
#AggregateIdentifier
private String fooIdentifier;
#CommandHandler
public Foo(CreateFooCommand command) {
apply(FooCreatedEvent.builder()
.fooIdentifier(command.getFooIdentifier())
.build());
}
#EventSourcingHandler
public void on(FooCreatedEvent event) {
this.fooIdentifier = event.getFooIdentifier();
}
}
#Aggregate
#Getter
#NoArgsConstructor
public class Bar {
#AggregateIdentifier
private String fooIdentifier;
private String barProperty;
#CommandHandler
public void on(UpdateBarCommand command) {
apply(BarUpdatedEvent.builder()
.fooIdentifier(this.fooIdentifier)
.barProperty(command.getBarProperty())
.build());
}
#EventSourcingHandler
public void on(FooCreatedEvent event) {
this.fooIdentifier = event.getFooIdentifier();
}
#EventSourcingHandler
public void on(BarUpdatedEvent event) {
this.barProperty = event.getBarProperty();
}
}
The case for why I tried to split is that we wanted to separate the base logic (creation of the aggregate, in this case a vehicle) from the logic that happens and is handled in a different bounded context and separate microservice (transfers from and to a construction site). Since I cannot publish a creation event (CommandHandler in the constructor, sequence 0) for the same aggregate identifier but different aggregate type twice, I could not separate the two states completely.
So my only options right now would be what I presented above, or use the creation of the second aggregate to set a different aggregateId, but also add internally the aggregateId of the first aggregate to allow for events to be published with the aggregateId information of the first as a reference Id. To make this work I would have to keep a projection to map back and forth between the two identifiers, which also does not look too good.
Thanks in advance,
Lars Karschen
Very interesting solution you've come up with Lars. Cannot say I have ever split the Aggregate logic in such a manor that one service creates it and another loads the same events to recreate that state in it's own form.
So, am I going against the way axon framework intends aggregates to be used, or is this a viable use case?
To be honest, I don't think this would be the intended usage. Not so much because of Axon, but more because of the term Bounded Context you are using. Between contexts, you should share very consciously, as terms (the ubiquitous language) differs per context. Your events are essentially part of that languages, so sharing the entirety of an aggregate's stream with another service would not be something I'd suggest normally.
Whether these services you are talking about truly belong to distinct Bounded Contexts is not something I can deduce right now, as I am not your domain expert. If they do belong to the same context, sharing the events is perfectly fine. Then still I wouldn't recreate a different aggregate based on the same events. So, let me add another concept which might help.
What I take from your description, is that you have something called a Vehicle aggregate which transitions different states. Wouldn't a Polymorphic Aggregate be the solution you are looking for? That way you can have a parent Vehicle aggregate with all the basics, and more specific implementations when necessary? Still, this might not fit your solution completely, something I am uncertain about given your description.
So, I am going to add a third pointer which I think is valuable to highlight:
Since I cannot publish a creation event (CommandHandler in the constructor, sequence 0) for the same aggregate identifier but different aggregate type twice, I could not separate the two states completely.
This line suggests you want to reuse the Aggregate Identifier between different Aggregates, something which comes back in the question's title too. As you've noted, [aggregate identifier , sequence number] pairs need to be unique. Hence, reusing an aggregate identifier for a different type of aggregate is not an option. Know however that Axon will use the toString method of your aggregate identifier class to fill in the aggregate identifier field. If you would thus adjust the toString() method to include the aggregate type, you'd be able to keep the uniqueness requirement and still reuse your aggregate identifier.
For example, the toString method of a VehicleId class containing a UUID would normally output this:
684ec9f4-b9f8-11ea-b3de-0242ac130004
But if you change the toString to include the aggregate type, you would get this:
VehichleId[684ec9f4-b9f8-11ea-b3de-0242ac130004]
Concluding, I think there are three main points I'd like to share:
Axon Framework did not intent to reuse Aggregate Streams to recreate distinct Aggregate types.
Polymoprhic Aggregates might be a means to resolve the scenario you have.
The [aggregateId, seqNo] uniqueness requirement can reuse an aggregateId as long is the toString method would append/prepend the aggregate type to the result.
I hope this helps you on your journey Lars. Please let me know of you feel something is missing or if I didn't grasp your question correctly.
I am practicing DDD, and I have a very simple example, which looks like this currently:
Polling
getEventBus() -> Bus
getEventStorage() -> Storage
getMemberRepository() -> MemberRepository
getCategoryRepository() -> CategoryRepository
getBrandRepository() -> BrandRepository
getModelRepository() -> ModelRepository
getVoteRepository() -> VoteRepository
MemberRepository
MemberRepository(eventBus, eventStorage)
registerMember(id, uri)
-> MemberRegistered(id, uri, date)
-> MemberRegistrationFailed //when id or uri is not unique
isMemberWithIdRegistered(id)
isMemberWithUriRegistered(uri)
CategoryRepository
CategoryRepository(eventBus, eventStorage) {
addCategory(id, name)
-> CategoryAdded(id, name, date)
-> CategoryAdditionFailed //when id or name is not unique
isCategoryWithIdAdded(id)
isCategoryWithNameAdded(name)
};
BrandRepository
CategoryRepository(eventBus, eventStorage) {
addBrand(id, name)
-> BrandAdded(id, name, date)
-> BrandAdditionFailed //when id or name is not unique
isBrandWithIdAdded(id)
isBrandWithNameAdded(name)
};
ModelRepository
ModelRepository(eventBus, eventStorage)
addModel(id, name, categoryId, brandId)
-> ModelAdded(id, name, categoryId, brandId, date)
-> ModelAdditionFailed //when id or name is not unique and when category or brand is not recognized
isModelWithIdAdded(id)
isModelWithNameAdded(name)
VoteRepository
VoteRepository(eventBus, eventStorage)
addVote(memberId, modelId, vote, uri)
-> MemberVoted(memberId, modelId, vote, uri, date)
-> VoteFailed //when the member already voted on the actual model and when memberId or modelId is not recognized
I'd like to develop here a polling system, so I think we could call this the polling domain. We have members, categories, brands, models and votes. Each member can vote on a model only once and each model have a brand and a category. For example inf3rno can vote on the Shoe: Mizuno - Wave Rider 19 with 10, because he really likes it.
My problem is with the
addModel(id, name, categoryId, brandId)
-> ModelAdded(id, name, categoryId, brandId, date)
-> ModelAdditionFailed //when id or name is not unique and when category or brand is not recognized
and the
addVote(memberId, modelId, vote, uri)
-> MemberVoted(memberId, modelId, vote, uri, date)
-> VoteFailed //when the member already voted on the actual model and when memberId or modelId is not recognized
parts. Let's stick with the ModelAddtion.
If I want to check whether the categoryId and brandId are valid, I have to call the CategoryRepository.isCategoryWithIdAdded(categoryId) and the BrandRepository.isBrandWithIdAdded(brandId) methods. Is it allowed to access these methods from the ModelRepository? Should I inject the container and use the getCategoryRepository() -> CategoryRepository and getBrandRepository() -> BrandRepository methods? How to solve this properly by DDD?
update:
How would you solve this validation in the domain if you'd really need the foreign key constraint and your db engine would not have this feature?
There are 2 hard problems in computer science: cache invalidation, naming things, off by one errors, and attributing quotes.... I'll come in again.
Repository, as used in the ubiquitous language of DDD itself, doesn't normally mean what you are trying to express here.
Eric Evans wrote (the Blue Book, chapter 6).
Another transition that exposes technical complexity that can swamp the domain design is the transition to and from storage. This transition is the responsibility of another domain design construct, the REPOSITORY
The idea is to hide all the inner workings from the client, so that client code will be the same whether the data is stored in an object database, stored in a relational database, or simply held in memory.
In other words, the interface of a repository defines a contract to be implemented by the persistence component.
MemberRepository
MemberRepository(eventBus, eventStorage)
registerMember(id, uri)
-> MemberRegistered(id, uri, date)
-> MemberRegistrationFailed //when id or uri is not unique
This, on the other hand, looks like a modification to your domain model. "registerUser" has the semantics of a command, MemberRegistered, MemberRegistrationFailed look like domain events, which strongly implies that this thing is an aggregate, which is to say an entity that protects specific invariants within the domain.
Naming one of your aggregates "Repository" is going to confuse everybody. The names of aggregates should really be taken from the ubiquitous language of the bounded context, not from the pattern language we use to describe the implementation.
If I want to check whether the categoryId and brandId are valid, I have to call the CategoryRepository.isCategoryWithIdAdded(categoryId) and the BrandRepository.isBrandWithIdAdded(brandId) methods. Is it allowed to access these methods from the ModelRepository?
Assuming, as above, that CategoryRepository, BrandRepository and ModelRepository are all aggregates, the answer is no, no, and no.
No: If your have modeled your domain correctly, then all of the state needed to ensure that a change is consistent with the business invariant should be included within the boundary of the aggregate that is changing. Consider, for example, what it would mean to be adding a model in this thread, while the brand that the model needs is being removed in that thread. These are separate transactions, which means that the model can't maintain the consistency invariant.
No: if the motivation for the check it to reduce the incidence of errors by sanitizing the inputs, that logic really belongs within the application component, not the domain model. It's the responsibility of the domain model to ensure that the parameters of the command induce a valid change to the state of the model; it's the responsibility of the application to ensure that the correct parameters are being passed. The sanity check belongs outside the domain model
That said
No: aggregates in the domain model shouldn't access each other directly; instead of passing in an aggregate, pass in a domain service that represents the query that the domain model needs to run.
Model.addModel(brandId, brandLookupService) {
if (brandLookupService.isValid(brandId)) {
// ...
}
}
This extra bit of indirection removes any ambiguity about which aggregate is being changed within a given transaction. The BrandLookupService itself, under the covers, could well be loading a read only representation of a Brand from the BrandRepository.
Of course, it still doesn't address the concern that the brands could be changing even as the model is referencing the brand. In other words, there's a potential data race in this design because of where the transactions boundaries are drawn.
How would you solve this validation in the domain if you'd really need the foreign key constraint and your db engine would not have this feature?
Two options:
1) Redraw the aggregate boundaries.
If you need the foreign key constraint enforced by the domain model, then its not a "foreign" key; its a local key for an aggregate that contains both bits of state.
2) Change the requirements
Udi Dahan, I think in this talk, pointed out that sometimes the way that the business (currently) runs simply doesn't scale properly, and the business itself may need to change to get the results that they want.
I am not sure what the aggregates are here.
Let's try this a different way - how do we implement this?
For example inf3rno can vote on the Shoe: Mizuno - Wave Rider 19 with 10, because he really likes it.
In your design above, you used a VoteRepository to do this. We don't want to use "repository", because that noun isn't taken from the ubiquitous language. You called this the polling domain earlier, so let's try Poll as the entity. The Poll entity is going to be responsible for enforcing the "one man, one vote" invariant.
So it's going to look something like
class Poll {
private PollId id;
private Map<MemberId,Vote> recordedVotes;
public void recordVote(MemberId memberId, Vote vote) {
if (recordedVotes.containsKey(memberId)) {
throw VoteFailed("This member already voted. No backsies!");
}
recordedVotes.put(memberId, vote);
}
}
And the code to record the vote is going to look something like
// Vote is just a value type, we can create one whenever we need to
Vote vote = Vote.create(10);
// entity ids are also value types that we can create whenever
// we want. In a real program, we've probably done both of these
// lookups already; Poll and Member are entities, which implies that
// their identity is immutable - we don't need to worry that
// MemberId 3a7fdc5e-36d4-45e2-b21c-942a4f68e35d has been assigned
// to a different member.
PollId pollId = PollId.for("Mizuno - WaveRider 19")
MemberId memberId = MemberId.for("inf3rno");
Poll thePoll = pollRepository.get(pollId);
thePoll.recordVote(memberId, vote);
pollRepository.save(thePoll);
From a puristic view, you shouldn't need to access 2 repositories. I say puristic because it might take a while to understand what missing bits of the domain would simplify this.
From the top of my head, I would question myself the following:
Do you need to ensure that those entities exist? (isCategoryWithIdAdded and isBrandWithIdAdded). Depending on your storage engine, can you enforce this (e.g. required foreign key). I think this would be my approach, as it's also faster from a performance point of view.
Can you ensure those entities exist somewhere else? Some DDD implementations assume that data is correct once an Application Service is called.
And last (this might be a bit questionable) can this feature link 2 things even if they don't exist? (what would be the 'damage').
And just a comment... having something in your domain called Model is so confusing, as it's part of the vocabulary of DDD. =D
Which of the following would you go with?
And based on object oriented programming which one is the best practice?
A
Class Note
{
//Some properties, etc
public static Note getNoteFromServer();
public void UpdateNoteOnServer();
}
B
Class Note
{
//Some properties, etc
}
Class NoteManager
{
public static Note getNoteFromServer();
public static UpdateNoteOnServer(Note);
}
I would say option B. In that way you separate concerns: you have a Note that can be reused anywhere (and not necessarily on a networked application), and you have a manager class that only cares with server communication.
You may also think on implement logic for multiple servers. For example, you may want to comunicate with data formats like JSON or XML. You may implement an interface (example, interface INoteManager) and then implement two classes with servers for each of the data types I mentioned (example, NoteManagerXml and NoteManagerJson).
The main point on this question is sepration of concerns. Hope I've helped! :)
To take a different viewpoint from my other answer, I'd suggest that your division into Note/NoteManager is the wrong one - not because Note has anything wrong with it, but because the term Manager is a bit of a code smell because it's very generic, inviting the use of the class as a general dumping ground.
If the class is responsible for note persistence, call it NoteRepository.
If it's responsible for validating the content of a single note, move the logic onto the Note.
If it's responsible for creating notes, providing a number of convenience methods for easily creating notes, call it NoteFactory.
And if it's responsible for all of the above, split it into separate pieces because it's doing too much.
That's a pretty opinion based question you're asking there.
You're essentially asking (if I understand correctly) whether it is better to have a Class which contains only properties and another class to manage that object (Example B) or to have a class which does everything (Example A).
It really depends. If we're planning on using a MVC kind of framework, Example B would fit better, with Note being your Model, and NoteManager being the controller.
Personally, I would go with a hybrid of A and B, where NoteManager is handling controller actions, but the Model still has methods of its own to do things like managing a singleton instance. So maybe something like this?
Class Note
{
//Some properties, etc
public static Note getInstance(noteIdentifier);
public void saveNote();
}
Class NoteManager
{
// This handles view validation and calls Note.saveNote();
public static UpdateNoteOnServer(Note);
}
I think A is better, for 1 reason:
It implements the Object Oriented
paradigm to the letter.
The problem i see with B is that a static method that receives an instance of the same class sounds redundant to me because, why would you use a static method to apply behaviour to an instance of the same class? The whole idea behind classes and instances is that Classes are the frame and instances cookies, if you need different cookies, modify your frame and get new ones.
It seems to depend on how its going to be used in your program. If Note is the only class or is the parent class for derived classes then there is no point and having a "Manager", Keep It Simple Stupid (KISS). However if the Manager has to deal with other classes via Interfaces then I can see having a seperate class.
As per my experience best practice is , as long as things are separated DRY is best practice. you can extends note to notemanager
Class Note
{
//Some properties, etc
}
Class NoteManager
{
public static Note getNoteFromServer();
public static UpdateNoteOnServer(Note);
}
I'd choose B, unless you want to end up like poor ol' PHP:
get_note_from_server_and_print_the_response($note, 'PHP, why must you be so disorganized?')
But seriously, it may seem intuitive to do A at the moment, but you'll eventually split A up, as those server operations will require more and more related functions, until you have a mammoth Note class which contains every function in your program...
"It Depends"
One of the things it depends upon is the language of implementation.
If you are working in C# or Java, then you'll likely want to go with the Note/NoteManager approach as this gives you the most flexiblity of implementation - because static members in those languages a kind of second class citizens.
To illustrate, in Delphi's original Object Pascal lanaguage, methods and properties that could be accessed without an instance were known as class members, not static members, and they could be virtual, and therefore overridden in descendent classes.
If you're working with a language that provides features like "virtual class (static) members" and a few others, then you might want to merge Note/NoteManager together.
I would go with "B"
Reason why is that you may require "Note" to be used with another type of Controller class, like what you have done for NoteManager.
Also gives you the ability to dissociate your Data Objects or DTO's or Model away from your actual controller classes.
C
Class Note
{
//Some properties, etc
public static Note LoadFrom(Whatever);
public void SaveTo(Whatever);
}