polygene-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Niclas Hedhman <hedh...@gmail.com>
Subject Re: Perstence/runtime for Aggregates and AggregateRoots
Date Fri, 29 Apr 2016 00:50:28 GMT
Out of the 3 suggestions, this one is hardest to agree with, probably
because the implications are big and far-reaching.

I don't like the Aggregated.root() concept. Not because it should be an
Association, which in reality is a Property<EntityReference>, but because
it creates a two-way association for no good reason, and stores it as well.

To be analogous with @This, I think it should be;

public class OrderItemMixin
{
    @Aggregate
    private Order root;
}

As for the list if benefits, I am not convinced that any of them are
exclusive to an Aggregated interface.

I agree that the OrderItem ends up being persisted by a regular Entity
Store, and if people really guess/construct Identities explicitly, I think
it is OK to say "all bets are off". That is similar to being able to dig
into private mixins via reflection, not easy, but doable.
The important bit is that the EntityReference of the OrderItem is
AggregateRoot relative, and the Entity Reference can only be used via its
AR. The mechanism to handle that can be an implementation detail, or even a
replaceable implementation detail if people ever need to loosen the
restrictions a little bit and know what they are doing, similar to the
Property/Association handling being customizable, but I don't think has
ever happened.

Niclas
On Apr 29, 2016 05:57, "Kent SĂžlvsten" <kent.soelvsten@gmail.com> wrote:

> A note on persistence.
>
> The OrderItem will probably be persisted in a standard EntityStore.
> But we definitely do not want the user to query for them
> And we do not want the user to load them (by key).
>
> What would happen, if we (under the hood) added a Aggregrated interface
> to the orderitem?
>
> public interface Aggregated {
>   public Property<EntityReference> root();
>  }
>
> and (under the hood) added an AggregatedMixin to the OrderItem - meaning
> the root will be persisted.
>
> This would allow the runtime to decide that
> - a unitofwork must not remove it
> - the unitofwork would be able to guard agains a direct get()
> - notification of indexers should be discarded for the OrderItem - but
> the indexer should still be able to find orders-with-items-with-product-X
> - it must never be added to an association
> - the user might modify it (he really should not, it should happen
> through the order instead) - but consistency of the whole Order is
> checked on UOW.complete() - that is, all constraints on the Order is
> checked.
>
> This way the Order itself would simply be a standard entity with a
> special-type-of-association - and the OrderItem is a "truncated entity"
> with some features removed.
>
> WDYT?
>
> /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
>
>

Mime
  • Unnamed multipart/alternative (inline, None, 0 bytes)
View raw message