You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@polygene.apache.org by Kent Sølvsten <ke...@gmail.com> on 2016/04/28 23:08:58 UTC

Constraints (Re: Aggregates and AggregateRoots)

Interesting stuff!

Now first to the constraints:

@AggregateConstraints( CreditLimitConstraint.class )
public interface Order extends EntityComposite
{}


It seems to me, that this  could and should be made much more general.
Constraints on a composite level is as relevant for both values (checked
on creation) and "plain entities" (hmmm ... probably to be checked both
on builder completion and on UOW completion)

Enforcing rules such as
"either property A or B should be defined."
"password must not be the same as username"

The only special stuff related to aggregates, i think, is that if we
complete a UOW involving changes to an OrderItem, constraints on the
order itself (and possibly also constraints directly on the
OrderItem???) should be enforced.

So i think this part of your suggestion should be a separate topic
having value in it's own right.

/Kent




Den 28-04-2016 kl. 17:01 skrev Niclas Hedhman:
> Gang,
>
> since we are on the topic of persistence, I have some thoughts that I would
> like to share.
>
> I think code is the best way to show, and as usual we start with a usecase.
>
> So, let's say I want to create the classic Order example with OrderItem
> entities (orders can be massively big doh!!! (should really just use values
> as items)). Any way, so I have the OrderItem
>
> public interface OrderItem extends EntityComposite
> {
>     Property<Integer> quantity();
>     Association<Product> product();
>     Property<BigDecimal> pricePerItem();
>     Property<BigDecimal> discount();
> }
>
> The we have the Order itself...
>
> public interface Order extends EntityComposite
> {
>     Customer customer();
>     BigDecimal totalSum();
>     void addItem( Product p, int quantity, BigDecimal price, BigDecimal
> discount );
>     void removeItem( Product p, int quantity );
>     Iterable<OrderItem> items();
>
>     interface State
>     {
>         Association<Customer> customer();
>
>         Property<ShippingAddress> shippingAddress();
>
>         @Aggregated
>         ManyAssociation<OrderItem> items();
>
>         Property<Boolean> onCredit();
>     }
>
> }
>
> Let's say that we just want to make sure an order doesn't exceed the credit
> limit of the customer.
>
> For sake of simplicity in this discussion, the current outstanding credit
> is stored in the Customer itself and a simple exposed method for the
> validation.
>
> public interface Customer
> {
>       :
>     boolean hasCreditFor( BigDecimal additional );
>       :
> }
>
> We already have a validation mechanism called Constraints, but they work on
> input arguments to methods. We need something similar, but work on the
> entire Composite,
>
> public class CreditLimitConstraint
>     implements AggregateConstraint<Order>
> {
>     public boolean isValid( Order order )
>     {
>         if( ! order.onCredit().get() )
>             return true;
>         return order.customer().hasCreditFor( order.totalSum() );
>     }
> }
>
> And we could annotate with a Constraint-like annotation,
>
> @AggregateConstraints( CreditLimitConstraint.class )
> public interface Order extends EntityComposite
> {}
>
> But this doesn't solve the problem. We could have code that queries for the
> OrderItem instances and manipulates them directly. That is against the
> principle of Aggregates. Or we could hand over a reference of an OrderItem
> to another composite which use it to direct access to the aggregated
> entity. Also not cool.
>
> So, what we need is that EntityReference of an aggregated Entity to be
> invalid if it is not accessed from the AggregateRoot instance.
>
> How can we do that, in relatively simple terms?
>
> Well, one option could be to create a new composite meta type;
>
> public interface Aggregate extends EntityComposite, UnitOfWorkFactory{}
>
> And its implementation of UnitOfWorkFactory has two purposes;
>   1. Only work for types that exists as @Aggregated Associations within the
> same subtype.
>    2. Manage the EntityReferences to be Aggregate "relative".
>
> The second of those is a bit tricky, but effectively needs to provide a UoW
> implementation that leverage the EntityReference of the owning Aggregate.
> Doable, but tricky.
>
>
> But hold on a second; If the Aggregate is a UnitOfWorkFactory and possibly
> handles its own UnitOfWork, how does this fit with DDD on one hand and with
> the current UoW system on the other hand??
>
> Well, with DDD it is a perfect match. The Aggregate is a transactional
> boundary. So it seems to suggest a good fit. Cool.
> But, does DDD really tackle this, since you can't get the UnitOfWorkFactory
> of the Aggregate without first having a UnitOfWork to retrieve it with.
> This seems to suggest that DDD doesn't have it correct, or has made a
> simplification that is somewhat unfortunate.
>
> Could it be that there is actually a difference between the Aggregate being
> a non-Entity composite and the AggregateRoot that is an entity and part of
> the Aggregate??
>
> Perhaps...
>
> public interface Order extends EntityComposite {}  // back to normal entity
>
> @AggregateConstraint( CreditLimitConstraint.class )
> public interface OrderAggregate extends Aggregate<Order> {}
>
> meaning
>
> public interface Aggregate<T extends EntityComposite> extends
> UnitOfWorkFactory, Composite
> {
>     T root();
> }
>
> (I have place EntityComposite supertypes everywhere, but that is for
> clarity. Since we don't require that anymore, they shouldn't be there)
>
> So, then how would this be used??
>
> Aggregate<Order> aggregate = module.newAggregate( Order.class, identity );
>
> the newAggregate method, would do
>    a. create Aggregate composite,
>    b. create a new unit of work
>    c. read the entity from the store and populate root()
>    d. if entity doesn't exists, create a entity builder internally and use
> when populating state,
>    e. keep the UoW open,
>
> Since it should handle both existing and new aggregates uniformly, I think
> a
>
>     boolean isCreating();   // or similar
>
> method should also exist on the Aggregate type.
>
>
> To me, this feels a lot better. The DDD "rules" and "intents" are still in
> effect and can be enforced, and we can manage to implement it in Zest, if
> we choose to do so.
>
> I am really keen on hearing thoughts on this topic.
>
> Cheers


Re: Constraints (Re: Aggregates and AggregateRoots)

Posted by Niclas Hedhman <he...@gmail.com>.
Yes, that is possibly true. For entities, there is enough "management" to
ensure that the constraints are not violated (on .complete() ), but for
other composites, it becomes trickier.

However!!! I think it would be quite easy to define InvariantConstraints as,

* Invariant validation happens on return of all methods of the Composite.
Invalid invariants are only tolerated within a single method call

That would then imply two consequences;

  a. When obtaining a Property or  Association, from outside the Composite,
the InvariantConstraints are checked on mutating operations (set(), add()...

  b. When obtaining a Property or Association within the Composite, the
InvariantConstraints are NOT checked, only on method exist from Composite.

That should lead to less "anemic object" anti-pattern, which is good stuff.

With the above, there is another anomaly showing its head; newXXX() methods
become a "special case" as they would also do InvariantConstraints check.
Does this indicate that newXXX() should be part of the Composite itself,
similar to constructors on regular classes?? How would that look like?

Cheers
Niclas
On Apr 29, 2016 05:08, "Kent Sølvsten" <ke...@gmail.com> wrote:

> Interesting stuff!
>
> Now first to the constraints:
>
> @AggregateConstraints( CreditLimitConstraint.class )
> public interface Order extends EntityComposite
> {}
>
>
> It seems to me, that this  could and should be made much more general.
> Constraints on a composite level is as relevant for both values (checked
> on creation) and "plain entities" (hmmm ... probably to be checked both
> on builder completion and on UOW completion)
>
> Enforcing rules such as
> "either property A or B should be defined."
> "password must not be the same as username"
>
> The only special stuff related to aggregates, i think, is that if we
> complete a UOW involving changes to an OrderItem, constraints on the
> order itself (and possibly also constraints directly on the
> OrderItem???) should be enforced.
>
> So i think this part of your suggestion should be a separate topic
> having value in it's own right.
>
> /Kent
>
>
>
>
> Den 28-04-2016 kl. 17:01 skrev Niclas Hedhman:
> > Gang,
> >
> > since we are on the topic of persistence, I have some thoughts that I
> would
> > like to share.
> >
> > I think code is the best way to show, and as usual we start with a
> usecase.
> >
> > So, let's say I want to create the classic Order example with OrderItem
> > entities (orders can be massively big doh!!! (should really just use
> values
> > as items)). Any way, so I have the OrderItem
> >
> > public interface OrderItem extends EntityComposite
> > {
> >     Property<Integer> quantity();
> >     Association<Product> product();
> >     Property<BigDecimal> pricePerItem();
> >     Property<BigDecimal> discount();
> > }
> >
> > The we have the Order itself...
> >
> > public interface Order extends EntityComposite
> > {
> >     Customer customer();
> >     BigDecimal totalSum();
> >     void addItem( Product p, int quantity, BigDecimal price, BigDecimal
> > discount );
> >     void removeItem( Product p, int quantity );
> >     Iterable<OrderItem> items();
> >
> >     interface State
> >     {
> >         Association<Customer> customer();
> >
> >         Property<ShippingAddress> shippingAddress();
> >
> >         @Aggregated
> >         ManyAssociation<OrderItem> items();
> >
> >         Property<Boolean> onCredit();
> >     }
> >
> > }
> >
> > Let's say that we just want to make sure an order doesn't exceed the
> credit
> > limit of the customer.
> >
> > For sake of simplicity in this discussion, the current outstanding credit
> > is stored in the Customer itself and a simple exposed method for the
> > validation.
> >
> > public interface Customer
> > {
> >       :
> >     boolean hasCreditFor( BigDecimal additional );
> >       :
> > }
> >
> > We already have a validation mechanism called Constraints, but they work
> on
> > input arguments to methods. We need something similar, but work on the
> > entire Composite,
> >
> > public class CreditLimitConstraint
> >     implements AggregateConstraint<Order>
> > {
> >     public boolean isValid( Order order )
> >     {
> >         if( ! order.onCredit().get() )
> >             return true;
> >         return order.customer().hasCreditFor( order.totalSum() );
> >     }
> > }
> >
> > And we could annotate with a Constraint-like annotation,
> >
> > @AggregateConstraints( CreditLimitConstraint.class )
> > public interface Order extends EntityComposite
> > {}
> >
> > But this doesn't solve the problem. We could have code that queries for
> the
> > OrderItem instances and manipulates them directly. That is against the
> > principle of Aggregates. Or we could hand over a reference of an
> OrderItem
> > to another composite which use it to direct access to the aggregated
> > entity. Also not cool.
> >
> > So, what we need is that EntityReference of an aggregated Entity to be
> > invalid if it is not accessed from the AggregateRoot instance.
> >
> > How can we do that, in relatively simple terms?
> >
> > Well, one option could be to create a new composite meta type;
> >
> > public interface Aggregate extends EntityComposite, UnitOfWorkFactory{}
> >
> > And its implementation of UnitOfWorkFactory has two purposes;
> >   1. Only work for types that exists as @Aggregated Associations within
> the
> > same subtype.
> >    2. Manage the EntityReferences to be Aggregate "relative".
> >
> > The second of those is a bit tricky, but effectively needs to provide a
> UoW
> > implementation that leverage the EntityReference of the owning Aggregate.
> > Doable, but tricky.
> >
> >
> > But hold on a second; If the Aggregate is a UnitOfWorkFactory and
> possibly
> > handles its own UnitOfWork, how does this fit with DDD on one hand and
> with
> > the current UoW system on the other hand??
> >
> > Well, with DDD it is a perfect match. The Aggregate is a transactional
> > boundary. So it seems to suggest a good fit. Cool.
> > But, does DDD really tackle this, since you can't get the
> UnitOfWorkFactory
> > of the Aggregate without first having a UnitOfWork to retrieve it with.
> > This seems to suggest that DDD doesn't have it correct, or has made a
> > simplification that is somewhat unfortunate.
> >
> > Could it be that there is actually a difference between the Aggregate
> being
> > a non-Entity composite and the AggregateRoot that is an entity and part
> of
> > the Aggregate??
> >
> > Perhaps...
> >
> > public interface Order extends EntityComposite {}  // back to normal
> entity
> >
> > @AggregateConstraint( CreditLimitConstraint.class )
> > public interface OrderAggregate extends Aggregate<Order> {}
> >
> > meaning
> >
> > public interface Aggregate<T extends EntityComposite> extends
> > UnitOfWorkFactory, Composite
> > {
> >     T root();
> > }
> >
> > (I have place EntityComposite supertypes everywhere, but that is for
> > clarity. Since we don't require that anymore, they shouldn't be there)
> >
> > So, then how would this be used??
> >
> > Aggregate<Order> aggregate = module.newAggregate( Order.class, identity
> );
> >
> > the newAggregate method, would do
> >    a. create Aggregate composite,
> >    b. create a new unit of work
> >    c. read the entity from the store and populate root()
> >    d. if entity doesn't exists, create a entity builder internally and
> use
> > when populating state,
> >    e. keep the UoW open,
> >
> > Since it should handle both existing and new aggregates uniformly, I
> think
> > a
> >
> >     boolean isCreating();   // or similar
> >
> > method should also exist on the Aggregate type.
> >
> >
> > To me, this feels a lot better. The DDD "rules" and "intents" are still
> in
> > effect and can be enforced, and we can manage to implement it in Zest, if
> > we choose to do so.
> >
> > I am really keen on hearing thoughts on this topic.
> >
> > Cheers
>
>