polygene-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Niclas Hedhman <nic...@hedhman.org>
Subject Aggregates and AggregateRoots
Date Thu, 28 Apr 2016 15:01:38 GMT
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
-- 
Niclas Hedhman, Software Developer
http://zest.apache.org - New Energy for Java

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