1

Let's say you are performing a PUT request on an endpoint for a resource that holds a list of entities. For example, a football team and it's players. It would be fair to say that the list of players is within the bounded context of its team?

Does this mean that the aggregate root should have the ability to change anything concerning the content of the list including details about the players themselves?

{
    "name": "The Fighting Mongooses",
    "city": "New New York",
    "players": [
        { "id": "1" }, { "id": "2" }
    ]
}

Submitting this payload to the endpoint should persist the name and city properties, and also ensure the association of the players with IDs 1 and 2 to the Team, all through logic contained in the Team aggregate.

But what if I add more properties to one of the player objects?

{ "id": "1", "firstname": "steve" }

Should the aggregate be allowed to change that property if it doesn't already equal "steve"?

What if I do not include an ID for one of the players?

{ "firstname": "jason", "lastname": "smith" }

Should the aggregate then be allowed to create a new player with those properties?

3 Answers 3

3

The domain model

In view of your example, I understand that players are well identified persons that exist independently of any specific team. In this case, Player is an entity independent of the Team aggregate. The nested players in the Team are in fact TeamPlayers, i.e. entities that each refer to a Player managed elsewhere. The model would look like:

Class diagram with a team aggregate and a player aggregate

In this regard, the Team aggregate would be allowed to change the player’s role or shirt number in the team, but not the Player's name or birthdate.

The API

If you apply sound separation of concerns, the PUT should only allow to set Team and TeamPlayer properties. So properties such as player name should be either ignored or trigger an error.

On the other hand you could decide to forward player specific requests to the Player endpoint as a convenience. But I fear that this would make access control very difficult, add unnecessary complexity, and could moreover easily lead to misunderstandings and accidental overwritings.

6
  • Very clear explanation! So, with your restrictions in place. Would it ever be acceptable to extend the Player aggregate to include the TeamPlayer entity, even though the Team aggregate root already includes it? Can the Team aggregate root have a function AddPlayer at the same time as the Player aggregate root having the function AssignToTeam? Or is that considered crossing boundaries.
    – apriestley
    Jun 12 at 14:22
  • @apriestley the aggregates are meant to enforce the invariants. If you’d move TeamPlayer into the Player aggregate, the Team would no longer be able to enforce its invariants by itself (e.g. number of players). Do, that’s not ok. If you have some operations that cannot clearly be included in the responsibilities of one aggregate (e.g. adding a new player and assign to team), the practice is to outsource them in a “service”: the service is outside the aggregates and can coordinate aggregate operations to perform smoothly things across borders.
    – Christophe
    Jun 12 at 15:54
  • I see, so now I am left with two aggregates (Team and Player) and a service (TeamMembershipService). So with that in place, both aggregates must defer to the service for any operations concerning the TeamPlayer entity such as Player.GetTeams, Team.GetPlayers, Team.AddPlayer, Player.AssignToTeam, and so on. Is that more or less the idea?
    – apriestley
    Jun 12 at 16:18
  • @apriestley this seems to be somewhat the idea, but I wonder if you don’t make it more complicated than needed. You’d use your player endpoint to create a new player, youd use your team end point to assign a player to a team, and optionally, you’d offer a teamplayer service to combine the two operations.
    – Christophe
    Jun 12 at 16:53
  • I would agree that in this hypothetical, having the service is overly complex, and that I would just start by giving the Team aggregate ownership of the concept of membership. The main purpose of the question (in my first comment) is to ascertain what do you do if that level of complexity is requested. However I don't know if I'd allow the service to create new players as that's what the player aggregate is there for
    – apriestley
    Jun 12 at 17:12
1

It depends on what the object is representing and what rules you want.

Team is a bad example, because each Player is an entity. If Player A is on two teams they still have the same first name.

But maybe you have TeamSubmissionForm instead, this must be filled in to apply to enter your team into a league It has to contain the names etc of all the team members. You fill it in and submit it. Now it makes sense to include the child objects. They are value objects and don't change if the player changes their name

Or maybe Team is an object in your football manager game and you want a player to only ever be on one team and its fine.

2
  • In the above example I'm thinking about players as being persisted as their own entities, since they have IDs. Why do you think it's a bad example? Team is an entity as well--it just has a relationship to player, which is another entity. I am doing a PUT request against the Teams ID. Regarding being on many teams, are you suggesting that team can only be the aggregate root containing a list of players if no other team can contain the same players?
    – apriestley
    Jun 10 at 19:11
  • if player is part of the team AR then if the player is on two teams you have the players data twice. If the players data changes, say they change their name, then you have to check all the teams
    – Ewan
    Jun 10 at 19:22
0

Let's clarify a couple of things:

  • Bounded Context defines the boundaries of a domain perspective, in which domain language has the same meaning. A bounded context contains all the model that makes sense from a specific domain point of view. In your example, team and player are concepts of the same BC (like team management)
  • An entity is a trait of a domain concept, not a model. A model usually involves one or more entities
  • The (greatly simplified) purpose of an aggregate is to define what data needs to be part of a unit of work. The aggregate is a model used to change the business state (data). You want each aggregate to be specific (unique) to each app use case. The aggregate encapsulates consistency rules and in practice is a group of value objects.

Your post assumes an entity is a model and we end up with a very common mistake: considering an aggregate a container of data structures or entities.

An aggregate is specific to an use case which itself is an app behaviour. You don't have an aggregate defined for itself, you have an aggregate which defines the write model of a behaviour.

In your example it seems that the use case is : "Assign players to team". The aggregate involves the concepts of Team and Player (entities). The important entity in this case is the Team and that serves as an aggregate root. The other entities are usually referred via their Id and you did that.

Now, I would leave things like that, because that's the relevant model to assign players to a team . That's the model which then can be used by the repository/persistence service to update the tables.

If you want to query things, skip the repos and aggregates (yes, CQRS), just query the tables and return a read model.

Remember that an aggregate is a relevant model to update data only (useless for reading data). In this use case you're concerned about team assignments, basically changing associations between a team and players according to domain rules.

2
  • Interesting. Is there any authoritative quote to support the claim "An aggregate is specific to an use case which itself is an app behaviour. You don't have an aggregate defined for itself, you have an aggregate which defines the write model of a behaviour.” ? I went through Evans’ book and I didn’t find anything in that direction. He seemed quite often to define aggregates based on structural relation and invariants.
    – Christophe
    Jun 15 at 16:56
  • Evans' book is 22 years old. Things change. DDD is now more than an OOP based design technique. My 14 years experience learning and applying DDD lead me to this. When you need to design for scalability, the more granular and somewhat stricter approach i.e one specific aggregate per command use case protects you from later headaches.
    – MikeSW
    Jun 16 at 1:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.