I have a design problem in my project.
To simplify things, I'll take as an example Vaughn Vernon's forum project, which advocates the use of small aggregates rather than large ones. Because that is my problem.
Projet de Vaughn Vernon : https://github.com/VaughnVernon/IDDD_Samples/tree/master/iddd_collaboration/src/main/java/com/saasovation/collaboration
Vaughn Vernon forum model :
Forum AR that can be closed
Discussion AR
Post AR
So to create a Discussion on a Forum, Vaughn does that :
Forum {
startDiscussion(title) {
return new Discussion(this.id, title)
}
}
Then, to post into discussion, he does that :
Discussion {
post(message) {
return new Post(this.forumId, this.id, message)
}
}
I think that's really cool, it makes sense from a business point of view.
According to its code, Forum can be closed, however, you can still create discussions and post. I also find that strange...
If I want to avoid starting discussions in a closed forum, it would be easy :
Forum {
startDiscussion(title) {
if (this.isClosed) {
throw new Exception("Cannot start discussion: forum closed")
}
return new Discussion(this.id, title)
}
}
Now, if I want to avoid posting in a discussion whose forum is closed, how can I do ?
Apart from using a large aggregate, I don't see how to do..
I had thought of using a service :
Service {
Post = postToDiscussion(forumId, discussionId, messageToPost) {
forum = forumRepo.get(forumId)
if (forum.closed) {
// throw Exception
}
discussion = discussionRepo.get(discussionId)
return discussion.post(messageToPost)
}
}
As there is a business rule, this service cannot be an Application Service, right?. A Domain Service then?
I am very surprised that such basic problems can become so complicated to implement in DDD..
Thanks for enlightening me :)
You can use events. When a forum is closed, publish ForumClosedEvent. Then handle that event anywhere you need to react to it. You can identify this type of requirements because they are usually worded like "When X happens, then Y should happen", where X is a business operation that will be represented as an event. For example, "When a forum is closed then all related discussions should be closed".
Note that Forum and Discussions will be eventually consistent, as there'll be a length of time when the Forum is closed but the related Discussions are still open. Make sure that you handle this situation accordingly. Many times, you don't have to do anything, in other cases, you have to execute some compensating actions. Don't assume that everything has to always be strictly consistent, real life is not transactional anyway.
Instead of passing just forumId to postToDiscussion method, pass the whole Forum aggregate. It is ok to fetch and additional aggregate in the application service in order to fulfill the domain logic and check some business rules.
Related
I am reading Eric Evans' book about DDD. I really like the concepts in the book. I started to think how I can migrate an old project to this newly(for me) discovered concepts.
Of course some issues raised in my head. So I am asking for help.
Let me give you context. The project is small CMS which managing issues and their content.
The application should support following structure:
Publisher it is the root entity
Publication it is "child" of Publisher
Issue it is "child" of Publication
Section it is "child" of Issue
Page it is also "child" of Issue, think of it as image
Article it can be child of Issue or Section
Page and Article are the two types which hold the actual content.
So this is the structure which we defined with the business. The two most important functionalities are "Parsing" and "Publishing".
Parsing means to read data from external source for now XML file and create an Issue and the related content - articles and sections or pages.
Publishing it is a flag which shows whether the content should be displayed or not. The problem is this flag should be updated across all content starting from the issue.
My thoughts on this: All objects described above should be entities because they need to be individually findable so they need Id. Then I think they should be aggregates as well because they contains some side logic and in case of some objects they may contains other entities or value objects. So I will refer to each aggregate by some kind of identifier object which also will tell me the relation between the entities.
I have two problems with the main functionalities.
Parsing - I read that we should save/update only one aggregate per request. When we parse content we receive a list of aggregates, which holds issue and its content and I need to store it to the DB. Which means I have list of aggregates and I have to bend the rule. Not only that but imagine what will happen if I parse 60 or more articles for an issue and the application fire OnCreate event for every article, page or section. What will be your approach here.
Publishing - actually is pretty much the same problem I have to update 60 aggregates and the same case here with events.
There should have a way to model this with DDD concepts but I am still trying to find it and every time when start reading I can not stop thinking about this scenarios.
What will be you approach to this scenarios by using DDD?
Also if you see that this approach is to DB oriented please let me know what will be your ides?
The DDD book is awesome, but doesn't have much code in it. If you have some time, read Implementing Domain-Driven Design as it has a ton of code examples.
The concept you might be looking for is a saga or long running process. Sagas orchestrate changes to multiple aggegrates, depending how it's implemented, you could think of it as a workflow and in each stage a different aggregate is updated. It's important that sagas can be continued if the app goes down while running.
From a pragmatical approach, in the past I did update multiple aggregates inside one transaction as adding sagas can introduce a lot of complexity in a project. I think it's ok as long as all aggregates are modified from the same place (ideally one short method) and there are no interactions between the aggregates. For example, imagine you have to publish a bunch of resources, so the solution is to loop over multiple aggregates invoking a method that will publish them).
edit
Going a bit through memory lane, I found these articles wrote by Vaughn Vernon (the author of the book I mentioned above) which touches this subject: https://www.dddcommunity.org/library/vernon_2011/
I implemented event sourced entities ( in Domain driven design it's called aggregate). It's a good practice to create a rich domain model. Domain driven design (DDD) suggests putting all business related things when possible into core entities and value objects.
But there is an issue when using such an approach in combination with event sourcing. In comparison to traditional approaches in an event sourced systems events are stored first and later all events are applied when building the entity to execute some methods.
Based upon that, the big question is where to put the business logic. Usually, I would like to have a method like:
public void addNewAppointment(...)
In this case, I would expect that the method makes sure that no business rules are violated. If this is the case an exception would be thrown.
But when using event sourcing I would have to create an event:
Event event = new AppointmentAddedEvent(...);
event store.save(event);
Right now, I explored 2 approaches to check business rules before storing the event.
First, check business rules within the application layer. The application layer in DDD is a delegation layer. Actually, it should contain no business logic. It should only delegate things like getting core entities, calling methods and saving things back. In this example this rule would be violated:
List<Event> events = store.getEventsForConference(id);
// all events are applied to create the conference entity
Conference conf = factory.build(events);
if(conf.getState() == CANCELED) {
throw new ConferenceClosed()
}
Event event = new AppointmentAddedEvent(...);
event store.save(event);
Obviously, the business rule adding appointments to canceled conferences should not be possible leaked into the non-core component.
The second approach I know is to add process methods of commands to core entities:
class Conference {
// ...
public List<Event> process(AddAppointmentCommand command) {
if(this.state == CANCELED) {
throw new ConferenceClosed()
}
return Array.asList(new AppointmentAddedEvent(...));
}
// ...
}
In this case, the benefit is that the business rules are part of the core entity. But there is a violation of separation of concerns principle. Now, the entity is responsible for creating events that are stored in an event store. Besides that, it feels weird that an entity is responsible for creating events. I can argue for why it's natural that an entity can process events. But the creation of domain events for storing, not for natural publishing, feels wrong.
Did anyone of you experience similar issues? And how did you solve these?
For now, I will just go with the business rules within the application service solution. It is still one place and ok-ish but it violates some of the DDD principles.
I am looking forward to your ideas and experiences about DDD, event sourcing and the validation of incoming changes.
Thanks in advance
I love this question. When I first asked it, that was the break between just following the patterns and challenging myself to understand what is really going on.
the big question is where to put the business logic
The usual answer is "the same place you did before" -- in methods of the domain entities. Your "second approach" is the usual idea.
But there is a violation of separation of concerns principle.
It isn't really, but it certainly looks weird.
Consider what we normally do, when saving off current state. We run some query (usually via the repository) to get the original state out of the book of record. We use that state to create an entity. We then run the command, in which the entity creates new state. We then save the object in the repository, which replaces the original state with the new state in the book of record.
In code, it looks something like
state = store.get(id)
conf = ConferenceFactory.build(state)
conf.state.appointments.add(...)
store.save(id, conf.state)
What we are really doing in event sourcing is replacing a mutable state with a persistent collection of events
history = store.get(id)
conf = ConferenceFactory.build(history)
conf.history.add(AppointmentScheduled(...))
store.save(id, conf.history)
In mature business domains, like accounting or banking, the ubiquitous language include event histories: journal, ledger, transaction history,... that sort of thing. In those cases, event histories are an inherent part of the domain.
In other domains -- like calendar scheduling -- we don't (yet?) have analogous entities in the domain language, so it feels like we are doing something weird when we change to events. But the core pattern is the same -- we pull history out of the book of record, we manipulate that history, we save the updates to the book of record.
So the business logic happens in the same place that it always did.
Which is to say that yes, the domain logic knows about events.
An exercise that may help: let go of the "object oriented" constraint, and just think in terms of functions....
static final List<Event> scheduleAppointment(List<Event> history, AddAppointmentCommand addAppointment) {
var state = state(history)
if(state == CANCELED) {
throw new ConferenceClosed()
}
return Array.asList(new AppointmentAddedEvent(...));
}
private static final State state(List<Event> history) {...}
In Vaughn Vernon's Implementing Domain-Driven Design book, he described the use of factory method in an Aggregate Root. One example was that of a Forum aggregate root which had startDiscussion factory method which returned a Discussion aggregate root.
public class Forum extends Entity {
...
public Discussion startDiscussion(
DiscussionId aDiscussionId, Author anAuthor, String aSubject) {
if (this.isClosed()) {
throw new IllegalStateException("Forum is closed.");
}
Discussion discussion = new Discussion(
this.tenant(), this.forumId(), aDiscussionId, anAuthor, aSubject);
DomainEventPublisher.instance().publish(new DiscussionStarted(...));
return discussion;
}
How would one implement this factory pattern in an event sourcing system, specifically in Axon?
I believe conventionally, it may be implemented in this way:
StartDiscussionCommand -> DiscussionStartedEvent -> CreateDiscussionCommand -> DiscussionCreatedEvent
We fire a StartDiscussionCommand to be handled by the Forum, Forum then publishes a DiscussionStartedEvent. An external event handler would catch the DiscussionStartedEvent, convert it, and fire a CreateDiscussionCommand. Another handler would instantiate a Discussion using the CreateDiscussionCommand and Discussion would fire a DiscussionCreatedEvent.
Alternatively, can we instead have:
StartDiscussionCommand -> CreateDiscussionCommand -> DiscussionCreatedEvent
We fire StartDiscussionCommand, which would trigger a command handler and invoke Forum's startDiscussion() method that will return the CreateDiscussionCommand. The handler will then dispatch this CreateDiscussionCommand. Another handler receives the command and use this to instantiate Discussion. Discussion would then fire the DiscussionCreatedEvent.
The first practice involves 4 DTOs, whilst the second one involves only 3 DTOs.
Any thoughts on which practice should be preferred? Or is there another way to do this?
The best approach to a problem like this, is to consider your aggregates (in fact, the entire system) as a black box first. Just look at the API.
Given a Forum (that is not closed),
When I send a StartedDiscussionCommand for that forum,
A new Discussion is started.
But also
Given a Forum that was closed
When I send a CreateDiscussionCommand for that forum,
An exception is raised
Note that the API you suggested is too technical. In 'real life', you don't create a discussion, you start one.
This means state of the forum is involved in the creation of the discussion. So ideally (when looking into the black box), such a scenario would be implemented in the Forum aggregate, and apply an event which represents the creation event for a Discussion aggregate. This is under the assumption that other factors require the Forum and Discussion to be two distinct aggregates.
So you don't really want the Command handler to return/send a command, you want that handler to make a decision on whether to create an aggregate or not.
Unfortunately, Axon doesn't support this feature, yet. At the moment, Axon cannot apply an event that belongs to another aggregate, through its regular APIs.
However, there is a way to get it done. In Axon 3, you don't have to apply an Event, you can also publish one directly to the Event Bus (which in the case of Event Sourcing would be an Event Store implementation). So to implement this, you could directly publish a DomainEventMessage that contains a DiscussionCreatedEvent. The ID for the discussion can be any UUID and the sequence number of the event is 0, as it is the creation event of the discussion.
Any thoughts on which practice should be preferred?
The motivation for a command is to direct the application to update the book of record. A command that you don't expect to produce an event is pretty weird.
That is, if your flow is
Forum.startDiscussion -> []
Discussion.create -> [ DiscussionCreated ]
One is bound to ask why the Forum is involved at all?
if (this.isClosed()) {
throw new IllegalStateException("Forum is closed.");
}
This here is an illusion -- we're looking at the state of the Forum at some arbitrary point in the past to process the Discussion command. In other words, after this check, the state of Forum could change, and our processing in Discussion would not know. So it would be just as correct to make this check when validating the command, or by checking the read model from within Discussion.
(Everything we get from the book of record is a representation of the past; it has to be, in order to already be in the book of record for us to read. The only moment that we act in the present is that point where we update the book of record. More precisely, its at the moment of the write that we discover if the assumptions we've made about the past still hold. When we write the changes to Discussion, we are proving that Discussion hasn't changed since we read the data; but that tells us nothing of whether Forum has changed).
What command->command looks like is an api compatibility adapter; in the old API, we used a Forum.startDiscussion command. We changed the model, but continue to support the old command for backwards compatibility. It would all still by synchronous with the request.
That's a real thing (we want the design to support aggressive updates to the model without requiring that clients/consumers be constantly updating), but it's not a good fit for your process flow.
I am studying hard DDD now. After months of studying, I found a contradiction between modeling and coding. As you know, DDD has the principle of "Domain 1st, Tech 2nd". However, there are "definite" constraints when moving the actual model into the implementation, for example:
Original model:
With the domain concept, posts are associated with multiple comments, and comments are an ordinary model that depends on the post. Moving this into code ...
class Post{
Integer postId;
String title;
String content;
String writer;
Collection<Comment> comments;
}
class Comment{
String content;
}
Like this... But if there are hundreds of millions of comments, there will be performance problems. According to the solution I have investigated, finally I change the original model as follows....
Revised model:
Also the code will change...
class Post{
Integer postId;
String title;
String content;
String writer;
}
class Comment{
Integer postId;
String content;
}
This revised model works well for me and I am satisfied with the solution. But I feel something inconsistent. It seems that the model is distorting due to technical constraints. If we show the first and second models to the domain experts and talk about the diagram, he or she will better understand the first model.
Am I misunderstanding about DDD concept? If so, give me some advice. Thank you.
One of the changes in mindset that come with Domain Driven Design is that your domain will constantly evolve with changes to your understanding of the domain. It is not just that the domain will evolve, but your understanding and perception about it will evolve, as well.
What you are running into right now, in my opinion, is trying to rationalize the design based on the loading of one or more entities into an aggregate by looking at it from how it is persisted. This common parent and child relationship feels natural, because it is the way that most of us have done things in the past. Even from a DDD perspective, it is easy to get caught up in the "Comments can't exist without Posts" paradigm.
This comes down to the fact that you are modelling based upon data inside of the domain, instead of use cases. Look at it this way... Posts are not simply a collection of Comments. Instead, a Comment refers to a specific Post. The nuance sounds minor, but it has broad-reaching consequences. When modeled in this fashion, it matches your revised model - and it is completely and totally fine. The only thing you need to change is your mindset, being that Comment can be considered an Aggregate, just as a Post is. They both are going to have use cases where the other one must exist, but at the same time, you need to see that you are unlikely to do something where both are impacted as part of the same use case (outside of deletion of the Post).
To illustrate, consider this use case:
As a Writer, I want to be able to edit the Content of my Post.
Your new model actually effectively supports that. Instead of looking at the relationship, you are looking at how the domain is used. Should you load Comments for a Writer to edit? Without knowing your domain, I would still assume that you would not want to do so. There are use cases that will likely involve both Posts and Comments, and that is also fine. Since you have two Aggregates inside of a single Bounded Context, you will be able to support use cases that are solely based on Posts, solely based on Comments, or a combination of both.
In terms of the technical concerns, you mention "hundreds of millions of comments". I assume that you mean system-wide, and not for a single Post? Assume that you have an active post, and that it sees 20k comments in its lifetime. For a properly designed and optimized database, this should still not be an issue. When it becomes an issue, if it ever does, it can be further addressed by additional changes to the technology used, as opposed to changing the domain. You can look at things like caching strategies (outside of the domain, since that is an application concern and not a domain concern), external indexes, eventual consistency, etc.
Highly recommend taking a read through Vaughn Vernon's "Effective Aggregate Design" series of articles, if you have not already:
Effective Aggregate Design
Additionally, his excellent book "Implementing Domain-Driven Design", is a must-read, in my opinion. While the Evans material is essential, it is more like the theoretical concepts; whereas the Vernon material talks about how to put the concepts into practice and what the implications of those decisions are.
As an addendum, like plalx points out in the comments below. None of this is meant to be a generalization about Posts and Comments in general, but explicitly how they apply to your domain, as it has been described to us and what could be inferred from your models. Posts and Comments will likely behave differently for others, because others will have distinct use cases. Even within your own domain, should Posts and/or Comments exist in a different context, they could behave differently. Above all else, try and make sure that everything you add to your domain model (in general) is added because of a specific and direct use case. It is tempting to simply start data modeling, but you will start to find yourself trying to force use cases into the domain model. Domain modeling should not be a finite process, or a single step in the process. Be willing and able to adapt to changes in understanding or changes to the way that the business changes its overall operational strategies over time.
One pitfall I notice about people entering the DDD world is that they visualize their ARs as source of READ operations as well. So, many times they tend to "model" the ARs and its entities/value objects in a way that "appeals" a DTO that is meant to hydrate the UI.
In your case, I would make Post an AR and Comments another AR. Adding a comment shouldn't require to instantiate Post at all. Editing a Post shouldn't require to 'load' the comments at all.
Use an independent mechanism to project your post/comments into a POCO/DTO class that may make sense to have a collection of comments in your post.
Does it make sense?
I'm reading about the idea of Bounded Contexts in DDD, and I'm starting to realize that I don't have a clear understanding of exactly what a Model looks like in practice. (I might not even know exactly what a Domain means, either.)
Let's look at the popular e-commerce example: A customer browses products, adds to their cart, places an order. The order fulfillment people ship out the orders.
Is there one big e-commerce Domain with multiple Bounded Contexts (Product Catalog Context, Shopping Cart Context, Order Context, Fulfillment Context)? Does each Bounded Context contain a bunch of Models (So that the Product Catalog Context contains models for the products, the product images, the product reviews)?
How far off am I?
At least You are on right track. Classic mistake is to see patterns only.
Domain means problems You are dealing with (support for e-commerce, healthcare, accounting, etc.). Domain model is solution of those problems represented in code that follows our mental model as close as possible.
Let's look at the popular e-commerce example: A customer browses products, adds to their cart, places an order. The order fulfillment people ship out the orders.
In Your example, I would start with something like this:
class Product { }
class Customer
{
Cart Cart;
void PlaceAnOrder()
{
order = new Order(Cart.Products);
Orders.Add(order);
Cart.Empty(); //if needed
}
Orders Orders;
Orders UnfulfilledOrders()
{
Orders.Where(order => !order.IsFilled);
}
}
class Cart
{
void AddProduct(product)
{
Products.Add(product);
}
void Empty()
{
Products.Clear();
}
}
class Order
{
bool IsFilled;
void Order(products)
{
Products = products;
IsFilled = false;
}
void Fill()
{
IsFilled = true;
//TODO: obviously - more stuff needed here
}
Money TotalPrice()
{
return Products.Sum(x => x.Price);
}
}
class System
{
void Main()
{
SimulateCustomerPlacingAnOrder();
SimulateFulfillmentPeople();
}
void SimulateCustomerPlacingAnOrder()
{
customer = new Customer();
customer.Cart.AddProduct(allProducts.First());
allCustomers.Add(customer);
}
void SimulateFulfillmentPeople()
{
foreach (var customer in allCustomers)
{
foreach (var order in customer.UnfulfilledOrders())
order.Fill();
}
}
}
At start - this seems like a huge overkill. With procedural code - the same can be achieved with few collections and few for loops. But the idea of domain driven design is to solve really complex problems.
Object oriented programming fits nicely - with it You can abstract away things that doesn't matter when You advance forward. Also - it's important to name things accordingly so You (and Your domain experts (people that understands problem)) would be able to understand code even after years. And not only code but to talk in one ubiquitous language too.
Note that I don't know e-commerce domain and what kind of problems You might be trying to solve, therefore - it's highly likely that I just wrote complete nonsense according to Your mental model. That is one reason why teaching domain modeling is so confusing and hard. Also - it demands great skill in abstract thinking which according to my understanding ain't main requirement to get CS degree.
You are kind a right about bounded contexts. But You should remember that they add need for translation between them. They add complexity and as usual - complex solution is appropriate for complex problems only (this is true for ddd itself too). So - You should avoid spamming them as long as meaning of your domain entities don't overlap. Second reason (less "natural") would be strong need for decomposition.
P.s. Read Evans book. Twice... Until it makes sense... :)
I think your view is accurate. Contexts deal with different aspects of the same real-world concept. In your case, you might have an order represented as some kind of shopping cart abstraction for the user and at the same time in some form that is compatible with the used ERP.
Instead of trying to shoehorn the concept of an order into a single model, DDD's contexts basically say it's OK to represent some concept using two completely different models (in different contexts), providing explicit mappings between these models as the need arises. This prevents models from aggregating the cruft that usually creeps in if one tries to use the same model within a multitude of contexts.
If you are ok with example in java, this might be useful: http://dddsample.sourceforge.net/