What is the purpose of child entity in Aggregate r

2019-05-25 05:46发布

问题:

[ Follow up from this question & comments: Should entity have methods and if so how to prevent them from being called outside aggregate ]

As the title says: i am not clear about what is the actual/precise purpose of entity as a child in aggregate?

According to what i've read on many places, these are the properties of entity that is a child of aggregate:

  1. It has identity local to aggregate
  2. It cannot be accessed directly but through aggregate root only
  3. It should have methods
  4. It should not be exposed from aggregate

In my mind, that translates to several problems:

  1. Entity should be private to aggregate
  2. We need a read only copy Value-Object to expose information from an entity (at least for a repository to be able to read it in order to save to db, for example)
  3. Methods that we have on entity are duplicated on Aggregate (or, vice versa, methods we have to have on Aggregate that handle entity are duplicated on entity)

So, why do we have an entity at all instead of Value Objects only? It seams much more convenient to have only value objects, all methods on aggregate and expose value objects (which we already do copying entity infos).

PS. I would like to focus to child entity on aggregate, not collections of entities.


[UPDATE in response to Constantin Galbenu answer & comments]

So, effectively, you would have something like this?

public class Aggregate {
    ...
    private _someNestedEntity;

    public SomeNestedEntityImmutableState EntityState {
       get {
          return this._someNestedEntity.getState();
       }
    }

    public ChangeSomethingOnNestedEntity(params) {
       this._someNestedEntity.someCommandMethod(params);
    }
}

回答1:

  1. It has identity local to aggregate

In a logical sense, probably, but concretely implementing this with the persistence means we have is often unnecessarily complex.

  1. We need a read only copy Value-Object to expose information from an entity (at least for a repository to be able to read it in order to save to db, for example)

Not necessarily, you could have read-only entities for instance.

The repository part of the problem was already addressed in another question. Reads aren't an issue, and there are multiple techniques to prevent write access from the outside world but still allow the persistence layer to populate an entity directly or indirectly.

So, why do we have an entity at all instead of Value Objects only?

You might be somewhat hastily putting concerns in the same basket which really are slightly different

  • Encapsulation of operations
  • Aggregate level invariant enforcement
  • Read access
  • Write access
  • Entity or VO data integrity

Just because Value Objects are best made immutable and don't enforce aggregate-level invariants (they do enforce their own data integrity though) doesn't mean Entities can't have a fine-tuned combination of some of the same characteristics.



回答2:

You are thinking about data. Stop that. :) Entities and value objects are not data. They are objects that you can use to model your problem domain. Entities and Value Objects are just a classification of things that naturally arise if you just model a problem.

Entity should be private to aggregate

Yes. Furthermore all state in an object should be private and inaccessible from the outside.

We need a read only copy Value-Object to expose information from an entity (at least for a repository to be able to read it in order to save to db, for example)

No. We don't expose information that is already available. If the information is already available, that means somebody is already responsible for it. So contact that object to do things for you, you don't need the data! This is essentially what the Law of Demeter tells us.

"Repositories" as often implemented do need access to the data, you're right. They are a bad pattern. They are often coupled with ORM, which is even worse in this context, because you lose all control over your data.

Methods that we have on entity are duplicated on Aggregate (or, vice versa, methods we have to have on Aggregate that handle entity are duplicated on entity)

The trick is, you don't have to. Every object (class) you create is there for a reason. As described previously to create an additional abstraction, model a part of the domain. If you do that, an "aggregate" object, that exist on a higher level of abstraction will never want to offer the same methods as objects below. That would mean that there is no abstraction whatsoever.

This use-case only arises when creating data-oriented objects that do little else than holding data. Obviously you would wonder how you could do anything with these if you can't get the data out. It is however a good indicator that your design is not yet complete.



回答3:

  1. Entity should be private to aggregate

Yes. And I do not think it is a problem. Continue reading to understand why.

  1. We need a read only copy Value-Object to expose information from an entity (at least for a repository to be able to read it in order to save to db, for example)

No. Make your aggregates return the data that needs to be persisted and/or need to be raised in a event on every method of the aggregate.

Raw example. Real world would need more finegrained response and maybe performMove function need to use the output of game.performMove to build propper structures for persistence and eventPublisher:

  public void performMove(String gameId, String playerId, Move move) {
    Game game = this.gameRepository.load(gameId); //Game is the AR
    List<event> events = game.performMove(playerId, move); //Do something
    persistence.apply(events) //events contains ID's of entities so the persistence is able to apply the event and save changes usign the ID's and changed data wich comes in the event too.
    this.eventPublisher.publish(events); //notify that something happens to the rest of the system
  }

Do the same with inner entities. Let the entity retun the data that changed because its method call, including its ID, capture this data in the AR and build propper output for persistence and eventPublisher. This way you do not need even to expose public readonly property with entity ID to the AR and the AR neither about its internal data to the application service. This is the way to get rid of Getter/Setters bag objects.

  1. Methods that we have on entity are duplicated on Aggregate (or, vice versa, methods we have to have on Aggregate that handle entity are duplicated on entity)

Sometimes the business rules, to check and apply, belongs exclusively to one entity and its internal state and AR just act as gateway. It is Ok but if you find this patter too much then it is a sign about wrong AR design. Maybe the inner entity should be the AR instead a inner entity, maybe you need to split the AR into serveral AR's (inand one the them is the old ner entity), etc... Do not be affraid about having classes that just have one or two methods.

In response of dee zg comments:

What does persistance.apply(events) precisely do? does it save whole aggregate or entities only?

Neither. Aggregates and entities are domain concepts, not persistence concepts; you can have document store, column store, relational, etc that does not need to match 1 to 1 your domain concepts. You do not read Aggregates and entities from persitence; you build aggregates and entities in memory with data readed from persistence. The aggregate itself does not need to be persisted, this is just a possible implementation detail. Remember that the aggregate is just a construct to organize business rules, it's not a meant to be a representation of state.

Your events have context (user intents) and the data that have been changed (along with the ID's needed to identify things in persistence) so it is incredible easy to write an apply function in the persistence layer that knows, i.e. what sql instruction in case of relational DB, what to execute in order to apply the event and persist the changes.

Could you please provide example when&why its better (or even inevitable?) to use child entity instead of separate AR referenced by its Id as value object?

Why do you design and model a class with sate and behaviour?

To abstract, encapsulate, reuse, etc. Basic SOLID design. If the entity has everything needed to ensure domain rules and invariants for a operation then the entity is the AR for that operation. If you need extra domain rules checkings that can not be done by the entity (i.e. the entity does not have enough inner state to accomplish the check or does not naturaly fit into the entity and what represents) then you have to redesign; some times could be to model an aggregate that does the extra domain rules checkings and delegate the other domain rules checking to the inner entity, some times could be change the entity to include the new things. It is too domain context dependant so I can not say that there is a fixed redesign strategy.

Keep in mind that you do not model aggregates and entities in your code. You model just classes with behaviour to check domain rules and the state needed to do that checkings and response whith the changes. Theese classes can act as aggregates or entities for different operations. Theese terms are used just to help to comunicate and understand the role of the class on each operation context. Of course, you can be in the situation that the operation does not fit into a entity and you could model an aggregate with a V.O. persistence ID and it is OK (sadly, in DDD, without knowing domain context almost everything is OK by default).

Do you wanna some more enlightment from someone that explains things much better than me? (not being native english speaker is a handicap for theese complex issues) Take a look here:

https://blog.sapiensworks.com/post/2016/07/14/DDD-Aggregate-Decoded-1 http://blog.sapiensworks.com/post/2016/07/14/DDD-Aggregate-Decoded-2 http://blog.sapiensworks.com/post/2016/07/14/DDD-Aggregate-Decoded-3



回答4:

These questions that you have do not exist in a CQRS architecture, where the Write model (the Aggregate) is different from a Read model. In a flat architecture, the Aggregate must expose read/query methods, otherwise it would be pointless.

  1. Entity should be private to aggregate

Yes, in this way you are clearly expressing the fact that they are not for external use.

  1. We need a read only copy Value-Object to expose information from an entity (at least for a repository to be able to read it in order to save to db, for example)

The Repositories are a special case and should not be see in the same way as Application/Presentation code. They could be part of the same package/module, in other words they should be able to access the nested entities.

The entities can be viewed/implemented as object with an immutable ID and a Value object representing its state, something like this (in pseudocode):

class SomeNestedEntity
{
    private readonly ID;
    private SomeNestedEntityImmutableState state;

    public getState(){ return state; }
    public someCommandMethod(){ state = state.mutateSomehow(); }
}

So you see? You could safely return the state of the nested entity, as it is immutable. There would be some problem with the Law of Demeter but this is a decision that you would have to make; if you break it by returning the state you make the code simpler to write for the first time but the coupling increases.

  1. Methods that we have on entity are duplicated on Aggregate (or, vice versa, methods we have to have on Aggregate that handle entity are duplicated on entity)

Yes, this protect the Aggregate's encapsulation and also permits the Aggregate to protect it's invariants.



回答5:

I won't write too much. Just an example. A car and a gear. The car is the aggregate root. The gear is a child entity