citerus/dddsample-core

Isn't it a problem that you add entities to an aggregate from a different aggregate?

Closed this issue · 9 comments

https://github.com/citerus/dddsample-core/blob/master/src/main/java/se/citerus/dddsample/application/impl/BookingServiceImpl.java#L38

    final TrackingId trackingId = cargoRepository.nextTrackingId();
    final Location origin = locationRepository.find(originUnLocode);
    final Location destination = locationRepository.find(destinationUnLocode);
    final RouteSpecification routeSpecification = new RouteSpecification(origin, destination, arrivalDeadline);

    final Cargo cargo = new Cargo(trackingId, routeSpecification);

    cargoRepository.store(cargo);

Here the Location is an entity, actually it looks like an aggregate root. Still you pass it as a parameter to the RouteSpecification. Afaik. aggregates cannot access each other directly. You should have added only a verified LocationId to the RouteSpecification, which is not necessarily the UnLocode you use for finding it. But correct me if I am wrong.

You are right. Things have happened in the DDD community the last 10 years. Now we favour passing IDs around rather than whole aggregates. This is something that could be improved in this sample application.

@daneidmark Any idea how to implement that? I mean it is obvious here, but in another line you use location methods: https://github.com/citerus/dddsample-core/blob/master/src/main/java/se/citerus/dddsample/domain/model/cargo/RouteSpecification.java#L61 For that you need location objects, but all you would have is ids. How do you get the data there?

Btw. it is commented here, that the upper code could be moved to a factory: https://github.com/citerus/dddsample-core/blob/master/src/main/java/se/citerus/dddsample/application/impl/BookingServiceImpl.java#L37 I think that is not necessary. We tend to use factories when there are different implementations for the same interface, so we can hide the implementation selection which use to be repeating if we keep it on the upper level. Here you have only one implementation for the Cargo and I doubt that will change...

As you mentioned earlier we typically want each aggregate to be self contained. I.e we ensure consistency of changes within an aggregate. Hence, if you are in a situation where, for example, one user action (command) is affecting multiple aggregates you need to think a bit. Can I remodel it? or can I use events to propagate changes.

An example could be if you are buying a book then you want to create an order and change the inventory. instead of doing these in the same transaction you could place the Order, listen to the corresponding OrderPlacedEvent and then update the Inventory.

In the route specification we are using the location aggregate root directly but we are still only comparing identities. Hence, we could solve it quite easily by just replacing the entity with its ID.

This is typically how we look at things when it comes to business rules and domain logic (on the write side). When it comes to reading data or present information. Then we are typically reading multiple aggregates to generate the appropriate view (sure this can also be solved by creating for example read models on the fly). To show the itinerary we typically want the pretty printed names on locations, not only the IDs.

I do not know where the comment originates from but I tend to agree with you. The only thing I can think of is that it might help with readability to encapsulate the creation of the object.

@daneidmark Right, it is possible to compare ids. I guess if we need more than that for writing then we have to move the code to an upper level, because we want to access multiple aggregates. Probably we'd need to move the code to an app service or to a domain service.

The more read about DDD the more questions I have. For example I am not sure I understand why we need the repository pattern. I read somewhere, that ordering should be coordinated by a domain service, because it does not belong to a specific entity, but the collection of entities. But the repository represents a collection more or less, so normally I would move there the ordering logic, which feels more like OOP and having domain services and repos which don't do much feels more like procedural programming.

Currently I am working on a project and try to practice DDD on it, but I am totally lost by one part of it. Maybe you can help, idk if you have time for these things. I asked it on many Q&A sites and I just got zero useful answers. What I do is updating a forecast data from an external service regularly. When I got a new planet wide forecast, then I update the personal forcast for each user, which depends on their locations and it might trigger notifications for these users depending on the user settings. For example if the forecast says that there will be tornado in the vicinity and the user wants tornado warnings, then they will be notified. All of this is clear, I know how to program that, I'll need an interval for regular updates, I need to pull the data for the external service and process it. What is not clear how to solve this with aggregates, repositories and domain services. I have no idea in which layer the interval should be. It appears to be a domain feature, at least it seems to be important, that I regularly update the forecast data from outside. But I can put the interval outside and call domain stuff from there. The forecast data itself is interesting too, because I don't want to store it, just keep temporarily in memory until I can get the next update. In theory I could make it an aggregate and call update on it, but since I don't save it, I don't think it should be an aggregate. Another way to model this update is creating a new forecast instance every time, instead of updating a single one. It is just listening to a different event. What is certain that the forecast is part of the domain or at least the local forecast, because the whole thing is about sending forecast notifications. Beyond that I have no idea. I tried to read about domain services, sagas, process managers, but the articles are contradictory. One claims that the saga is a domain service, while other claims that the saga is on the same level as the application services, listens to events and creates commands. Another claims that the domain service is stateless while the sage is stateful. Some the whole thing is a complete mess. What I know that I might need either a saga or a domain service or both. At least it looks like that. Can you give me some advice or maybe recommend a blog or article which is solves this kind of problems?

@Inf3rno My apologies, I didn't go through the whole comment, but there is one thing I read recently. It's that DDD is only good if the domain logic has complexity and not a good fit if the technology is complicated. It only works in case of domain complexity. Maybe that's something you can review, make sure you are not using incorrect approach.

Otherwise to get help on DDD, you can try reaching out at: DDD-CQRS-ES Slack. At the very least you can ask there for better community where you can ask this question. And anyways it's a good community to be a part of if you are interested in DDD.

@npathai Could you invite me to the DDD-CQRS-ES slack workspace,thanks?

@npathai Sure, thanks! In my experience one thing that is hard to guess is complexity. At least most of my projects had hidden complexity I discovered later. Ofc. I could use something simpler than DDD and refactor, redesign later. Tbh. I like modelling and maybe I use it as a golden hammer. :-)

I too have recently started on the path of learning about DDD. I am finding that this example is very confusing because it appears to break the rules that I have been reading about in Eric Evans's book and Vaughn Vernon's book.

For example, just looking at the cargo root aggregate, I see that it imports several entities from other root aggregates. Some of these are not even roots of their respective aggregates.

Here is a list of such cases that I have found :-

  • handling.HandlingHistory used in
    Cargo.java
    Delivery.java

  • handling.HandlingEvent used in
    Delivery.java
    Itinerary.java
    HandlingActivity.java

  • voyage.Voyage used in
    HandlingActivity.java
    Delivery.java
    Leg.java

  • location.Location used in
    Cargo.java
    Delivery.java
    HandlingActivity.java
    Itinerary.java
    Leg.java
    RouteSpecification.java

Is there something that I am missing that makes it OK for these classes to import classes (including non-root aggregate classes) from other aggregates?
It maybe that my understanding is wrong. In which case I would love to know how!

We have now created a ticket in our backlog to replace the linked entities with id's.