DDD structure example
Asked Answered
T

1

6

I am trying to structure an application using DDD and onion/hexagonal/clean architecture (using Java and Spring). I find it easier to find guidance on the concepts themselves than actually how to implement them. DDD in particular seems rather tricky to find examples that are instructive because each problem is unique. I have seen numerous examples on SO that have been helpful but I still have questions. I wonder whether going through my example would help me and anyone else.

I hope you can forgive me asking more than one question here. The example seems too big for it to make sense me repeating it in multiple questions.

Context:

We have an application that should display information about soccer stats and has the following concepts (for simplicity I have not included all attributes):

  • Team, which has many Players.
  • Player.
  • Fixture, which has 2 Teams and 2 Halves.
  • Half, which has 2 FormationsPlayed and many Combinations.
  • FormationPlayed, which has many PositionsPlayed.
  • PositionPlayed, which has 1 Player and a position value object.
  • Combination, which can be of 2 types, and has many Moves.
  • Move, which can be of 2 types, has 1 Player and an event value object.

As you can imagine, trying to work out which things are aggregate roots here is tricky.

  • Team can exist independently so is an AR.
  • Player can exist independently so is an AR.
  • Fixture, when deleted, must also delete its Halves, so is an AR.
  • Half must be an entity in Fixture.
  • FormationPlayed must be deleted when a half is deleted, so perhaps this should be an entity in Half.
  • PositionPlayed must be deleted when a Formation is deleted, so believe this should be an entity in FormationPlayed.
  • Combination in a sense can exist independently, though is tied to a particular game half. Perhaps this could be an AR tied by eventual consistency.
  • Move must be deleted when a Combination is deleted, so believe this should be an entity in Combination.

Questions:

  1. Do you see any errors in the above design? If so what would you change?
  2. The Fixture - Half - FormationPlayed - PositionPlayed aggregate seems too large, so I wonder whether you would agree that this could be split into Fixture - Half and FormationPlayed - PositionPlayed using eventual consistency. The thing I can't find an example of is how this is implemented in Java? If Fixture were deleted, would you fire a FixtureDeleted event that causes its corresponding FormationPlayed entities to also be deleted?
  3. I want to construct a domain model that has no understanding of the way that it will be persisted (as per onion architecture). My understanding is that domain entities here should not have surrogate keys because this relates to persistence. I also believe that entities should only reference entities in other aggregates by ids. How then, for example, would PositionPlayed reference Player in the domain model?
  4. Initially the aim is only to allow the client to get the data and display it. Ultimately I want clients to be able to perform CRUD themselves, and I want all invariants to be held together by the domain model when this happens. Would it simplify things (and can you show me or point me to example explaining how) to have two domain models, one simple for data retrieval and one rich for the operations to be performed later? Two BCs, as it were. The reason I ask is that a rich domain model seems rather time consuming to come up with when initially we only want to display stats in the database, but I also don't want to create trouble for myself down the line if it is better to create one rich domain model now in view of the usecases envisioned later. I wonder, if I were to create a simpler model for data retrieval only, which concepts in DDD could be ignored (would I still need to break up large aggregates, for example?)

I hope this all makes sense. Obviously happy to explain further if needed. Realise I'm asking a lot here and I may have confused some ideas. Any answers and wisdom you can give to this would be greatly appreciated !

Toughminded answered 24/1, 2017 at 9:33 Comment(5)
"Can exist independently" tends to be trumped by more complex domain invariants, transactional analysis and performance questions in real-life situations. Domain entities are not static objects, a lot of the domain logic has to do with finer details of how they can be transitioned from one state to another. Inter-entity synchronization is also a big question that has an impact on the design. I suggest reading/watching anything by Vaughn Vernon on the subject of aggregate design.Rhinoplasty
Thanks @guillaume31. Clearly you understand the subject well. Yes I have viewed a number of talks by Vaughn Vernon on the subject - which are brilliant - but because each problem is unique I still feel a little confused as to how to actually implement what he says. I have very little guidance on the subject apart from the books, talks, repositories and answers I can find online. What I mean by that is a generic talk or example found by searching can only go so far in addressing a specific problem.Toughminded
@Rhinoplasty Would your opinion be that a rich domain model is right in this scenario and do you know of any other specific links that might be helpful for this particular case?Toughminded
Gut feeling : the requirements suggest a simpler solution than Rich Domain Model / DDD tactical patterns (aggregate, entity, etc.) Martin Fowler has a list of Domain Logic Patterns that you could perhaps choose a better one from. You can still use DDD's strategic patterns, as explained here.Rhinoplasty
@Rhinoplasty very helpful thanksToughminded
D
4

Do you see any errors in the above design? If so what would you change?

There might be a big one: is your system the book of record? or is it just keeping track of events that happen in the "real world". In a sense, the point of aggregates is to ensure that the book of record is internally consistent, but if you aren't the book of record....

For an example of what I mean

If Fixture were deleted, would you fire a FixtureDeleted event that causes its corresponding FormationPlayed entities to also be deleted?

Udi Dahan wrote: Don't Delete, Just Don't. If an entity has a lifecycle, and that lifecycle has an end, then you mark it, but you don't remove the entity.

I want to construct a domain model that has no understanding of the way that it will be persisted (as per onion architecture)

Great! Be warned, a lot of the examples that you will find online don't get this part right -- for historical reasons, many demonstrations of model are tightly coupled to the side effects that they have on persistence.

My understanding is that domain entities here should not have surrogate keys because this relates to persistence. I also believe that entities should only reference entities in other aggregates by ids. How then, for example, would PositionPlayed reference Player in the domain model?

Ah -- OK, this one is fun. Don't confuse surrogate keys used in the persistence layer with identifiers in the domain model. For instance, when I look at my purchasing history on Amazon, each of my orders (presumably an aggregate) has an ORDER # associated with it. That would imply that the domain level knows about OrderNumber as a value type. The persistence solution in the back end might introduce surrogate keys when storing that data, but those keys are not used by the model.

Note that's I've chosen an example where the aggregate is clearly the authority -- the order only really exists within the model. When the real world is the book of record, you often don't have a unique identifier available (what is Lionel Messi's PlayerId?)

The reason I ask is that a rich domain model seems rather time consuming to come up with when initially we only want to display stats in the database

A couple of thoughts on this -- is usually saved for more complicated use cases (Greg Young: "is this where you get a competitive advantage?"). Most of the power of aggregates comes from the fact that they ensure the consistency of changes of state. When your real problem is data entry and reporting, it tends to be overkill.

Detection and remediation of inconsistencies is often easier/cheaper than trying to get prevention right; and may be satisfactory to the business, given the costs. Something to keep in mind.

The application is keeping track of events in the real world. At the moment, they are recorded manually in a database. Can you be explicit why you believe the distinction is important?

Very roughly -- events indicate things that have already happened. It's too late for the domain to veto them; the real world is outside of the domain's control. Furthermore, we have to keep in mind that, since the real world is the book of record, things may have happened in the real world that our domain model doesn't know about yet (the reporting of events may be delayed, lost, reordered, and so on).

Aggregates are supposed to be a source of truth. Which means that they can only govern entities in the digital world.

One kind of information resource that you could create is a report of Messi's goals in a season. So every time a goal is reported, you run a command to update the report aggregate. That's not anemic -- not exactly -- but it's not very interesting. It's really just a view (in CQRS terms, it's a read model) that you can recreate from the history of events. It doesn't have any intelligence in it.

The interest aggregates are those that make decisions for themselves, based on the information that they are given.

A contrived example of an aggregate would be one that, if a player scores more than 10 goals in a season, orders that players jersey for you. Notice that while "goals" are something already present in your event stream, the business rule doesn't. That's purely a domain model thing.

So the way that this would work is that each time a goal event appeared, you would load the JerseyPerchasing aggregate, and tell it about the goal. And that aggregate would make sure that this was a new goal (not one that had previously been reported), and determine if the number of goals called for ordering a shirt, check to see if the order for the shirt had already been placed.

Key idea here -- the goals are something that the aggregate is told about. The decision to purchase a jersey is made by the aggregate, and shared with the world.

Later, you realize that sometimes a player gets traded, and then scores a 10th goal. And you have to determine as a business whether that means you get one shirt (which?) or one shirt for each jersey, or maybe you only order jerseys if he scored 10 goals for a specific team in a season. All of this logic goes into the aggregate.

a domain model as per onion architecture that, can you point me to any good examples?

Best place to look, as weird as it sounds, is among the functional programming types. Mark Seemann's blog includes a lot of important ideas that will help here.

The main idea to keep in mind that the model sits at the bottom. The app passes state to the model, and gets state back (in CQS terminology, you query the model). The app is responsible for sharing the results obtained from the model with the persistence component.

do you believe the accepted view would be that an anaemic model should be adopted for a domain this size

In the case where you are just re-organizing information from the real world for easier consumption? Yeah - load document, update document, store document makes a lot more sense to me than going overboard with a bunch of aggregate modeling. But don't read too much into that -- I don't know more about your model than what you have written here. If there's real business complexity in how you evaluate the information from the real world, then the answer would be different.

Drubbing answered 24/1, 2017 at 14:7 Comment(5)
Really appreciate you taking the time to go through that. Thanks for your wisdom on this. If you could come back on the points you addressed that would really help further. The application is keeping track of events in the real world. At the moment, they are recorded manually in a database. Can you be explicit why you believe the distinction is important?Toughminded
Good point about deleting - I had read that article before. I guess my question was more about how a command would cascade and be handled within an aggregate that is broken into smaller pieces irrespective of the type of command. The main struggle in my head is breaking the aggregate into manageable chunks and so I am looking for some validation that the way I am thinking of doing this is right. If it were an update, would the use of domain events that I described be correct?Toughminded
Re a domain model as per onion architecture that, can you point me to any good examples? Often I find examples that either follow onion architecture but not ddd, follow ddd but not onion architecture or follow both but aren't instructive to the problem I'm trying to address. Re ids, that's really interesting. Would you create an id wherever an entity needs to be referenced by one within the domain model?Toughminded
Re whether this case does not warrant DDD, do you believe the accepted view would be that an anaemic model should be adopted for a domain this size, with objects only really present to extract relational data into object form? This isn't a criticism of your answer at all, just want to know what you think best and how best to do it. Thanks again.Toughminded
Thanks for all your time on this. The answer you just added is very helpfulToughminded

© 2022 - 2024 — McMap. All rights reserved.