2

I've only began with DDD and currently trying to grasp the ways to do different things with it. I'm trying to design it using asynchronous events (no event-sourcing yet) with CQRS. Currently I'm stuck with validation of commands. I've read this question: Validation in a Domain Driven Design , however, none of the answers seem to cover complex validation across different aggregate roots.


Let's say I have these aggregate roots:

  • Client - contains list of enabled services, each service can have a value-object list of discounts and their validity.
  • DiscountOrder - an order to enable more discounts on some of the services of given client, contains order items with discount configuration.
  • BillCycle - each period when bills are generated is described by own billcycle.

Here's the usecase:

Discount order can be submitted. Each new discount period in discount order should not overlap with any of BillCycles. No two discounts of same type can be active at the same time on one service.

Basically, using Hibernate in CRUD style, this would look something similar to (java code, but question is language-agnostic):

public class DiscountProcessor {
    ...
    @Transactional
    public void processOrder(long orderId) {
        DiscOrder order = orderDao.get(orderId);
        BillCycle[] cycles = billCycleDao.getAll();

        for (OrderItem item : order.getItems()) {
            //Validate billcycle overlapping
            for (BillCycle cycle : cycles) {            
                if (periodsOverlap(cycle.getPeriod(), item.getPeriod())) {
                    throw new PeriodsOverlapWithBillCycle(...);
                }
            }
            //Validate discount overlapping
            for (Discount d : item.getForService().getDiscounts()) {
                if (d.getType() == item.getType() && periodsOverlap(d.getPeriod(), item.getPeriod())) {
                    throw new PeriodsOverlapWithOtherItems(...);
                }
            }
            //Maybe some other validations in future or stuff
            ...
        }

        createDiscountsForOrder(order);
    }
}

Now here are my thoughts on implementation:

  1. Basically, the order can be in three states: "DRAFT", "VALIDATED" and "INVALID". "DRAFT" state can contain any kind of invalid data, "VALIDATED" state should only contain valid data, "INVALID" should contain invalid data.
  2. Therefore, there should be a method which tries to switch the state of the order, let's call it order.validate(...). The method will perform validations required for shift of state (DRAFT -> VALIDATED or DRAFT -> INVALID) and if successful - change the state and transmit a OrderValidated or OrderInvalidated events.

Now, what I'm struggling with, is the signature of said order.validate(...) method. To validate the order, it requires several other aggregates, namely BillCycle and Client. I can see these solutions:

  • Put those aggregates directly into the validate method, like order.validateWith(client, cycles) or order.validate(new OrderValidationData(client, cycles)). However, this seems a bit hackish.
  • Extract the required information from client and cycle into some kind of intermediate validation data object. Something like order.validate(new OrderValidationData(client.getDiscountInfos(), getListOfPeriods(cycles)).
  • Do validation in a separate service method which can do whatever it wants with whatever aggregates it wants (basically similar to CRUD example above). However, this seems far from DDD, as method order.validate() will become a dummy state setter, and calling this method will make it possible to bring an order unintuitively into an corrupted state (status = "valid" but contains invalid data because nobody bothered to call validation service).

What is the proper way to do it, and could it be that my whole thought process is wrong?

Thanks in advance.

Community
  • 1
  • 1
bezmax
  • 25,562
  • 10
  • 53
  • 84
  • 1
    When multiple aggregates are at play in a process, you can usually use a domain service to encapsulate this logic. However, since the `validate` state changing behavior seems to find it's natural home in the `Order`, I would probably opt for either passing in dependencies required to perform the validation to `order.validate` like you wanted, or passing an `IOrderValidatingSpecification` to `order.validate` and have the order double dispatch to it. – plalx Nov 20 '14 at 15:30

2 Answers2

7

What about introducing a delegate object to manipulate Order, Client, BillCycle?

class OrderingService {
    @Injected private ClientRepository clientRepository;

    @Injected private BillingRepository billRepository;

    Specification<Order> validSpec() {
        return new ValidOrderSpec(clientRepository, billRepository);
    }

}

class ValidOrderSpec implements Specification<Order> {
    @Override public boolean isSatisfied(Order order) {
        Client client = clientRepository.findBy(order.getClientId());
        BillCycle[] billCycles = billRepository.findAll();
        // validate here
    }
}

class Order {
    void validate(ValidOrderSpecification<Order> spec) {
        if (spec.isSatisfiedBy(this) {
            validated();
        } else {
            invalidated();
        }
    }
}

The pros and cons of your three solutions, from my perspective:

  1. order.validateWith(client, cycles)

It is easy to test the validation with order.

#file: OrderUnitTest
@Test public void should_change_to_valid_when_xxxx() {
    Client client = new ClientFixture()...build()
    BillCycle[] cycles = new BillCycleFixture()...build()
    Order order = new OrderFixture()...build();

    subject.validateWith(client, cycles);

    assertThat(order.getStatus(), is(VALID));
}

so far so good, but there seems to be some duplicate test code for DiscountOrderProcess.

#file: DiscountProcessor
@Test public void should_change_to_valid_when_xxxx() {
    Client client = new ClientFixture()...build()
    BillCycle[] cycles = new BillCycleFixture()...build()
    Order order = new OrderFixture()...build()
    DiscountProcessor subject = ...

    given(clientRepository).findBy(client.getId()).thenReturn(client);
    given(cycleRepository).findAll().thenReturn(cycles);        
    given(orderRepository).findBy(order.getId()).thenReturn(order);

    subject.processOrder(order.getId());

    assertThat(order.getStatus(), is(VALID));
}

#or in mock style
@Test public void should_change_to_valid_when_xxxx() {
    Client client = mock(Client.class)
    BillCycle[] cycles = array(mock(BillCycle.class))
    Order order = mock(Order.class)
    DiscountProcessor subject = ...

    given(clientRepository).findBy(client.getId()).thenReturn(client);
    given(cycleRepository).findAll().thenReturn(cycles);        
    given(orderRepository).findBy(order.getId()).thenReturn(order);

    given(client).....
    given(cycle1)....

    subject.processOrder(order.getId());

    verify(order).validated();
}
  1. order.validate(new OrderValidationData(client.getDiscountInfos(), getListOfPeriods(cycles))

Same as the above one, you still need to prepare data for both OrderUnitTest and discountOrderProcessUnitTest. But I think this one is better as order is not tightly coupled with Client and BillCycle.

  1. order.validate()

Similar to my idea if you keep validation in the domain layer. Sometimes it is just not any entity's responsibility, consider domain service or specification object.

#file: OrderUnitTest
@Test public void should_change_to_valid_when_xxxx() {
    Client client = new ClientFixture()...build()
    BillCycle[] cycles = new BillCycleFixture()...build()
    Order order = new OrderFixture()...build();
    Specification<Order> spec = new ValidOrderSpec(clientRepository, cycleRepository);        

    given(clientRepository).findBy(client.getId()).thenReturn(client);
    given(cycleRepository).findAll().thenReturn(cycles);  


    subject.validate(spec);

    assertThat(order.getStatus(), is(VALID));
}

#file: DiscountProcessor
@Test public void should_change_to_valid_when_xxxx() {
    Order order = new OrderFixture()...build()
    Specification<Order> spec = mock(ValidOrderSpec.class);
    DiscountProcessor subject = ...

    given(orderingService).validSpec().thenReturn(spec);
    given(spec).isSatisfiedBy(order).thenReturn(true);        
    given(orderRepository).findBy(order.getId()).thenReturn(order);

    subject.processOrder(order.getId());

    assertThat(order.getStatus(), is(VALID));
}
Yugang Zhou
  • 7,123
  • 6
  • 32
  • 60
  • Hm, so basically, it's the same as my (1) solution, but instead of delegating a set of aggregate entities, the idea is to delegate a set of aggregate repositories with much better choice of naming. Also I like that all the validation is coupled in one "Specification" class. I believe this is as good as it can go, with the only weird thing remaining, that the order needs specification to change states. What I mean, is, there will ever be one way of validating this kind of order, and all of the orders should respect same kind of specification, still, they require a specification instance. – bezmax Nov 04 '14 at 08:40
1

Do the 3 possible states reflect your domain or is that just extrapolation ? I'm asking because your sample code doesn't seem to change Order state but throw an exception when it's invalid.

If it's acceptable for the order to stay DRAFT for a short period of time after being submitted, you could have DiscountOrder emit a DiscountOrderSubmitted domain event. A handler catches the event and (delegates to a Domain service that) examines if the submit is legit or not. It would then issue a ChangeOrderState command to make the order either VALIDATED or INVALID.

You could even suppose that the change is legit by default and have processOrder() directly take it to VALIDATED, until proven otherwise by a subsequent INVALID counter-order given by the validation service.

This is not much different from your third solution or Hippoom's one though, except every step of the process is made explicit with its own domain event. I guess that with your current aggregate design you're doomed to have a third party orchestrator (as un-DDD and transaction script-esque as it may sound) that controls the process, since the DiscountOrder aggregate doesn't have native access to all information to tell if a given transformation is valid or not.

guillaume31
  • 13,738
  • 1
  • 32
  • 51
  • The sample code was merely a sample, the task is not to migrate it to DDD, but to make the UseCase properly using DDD. The example is taken from real-life business case, where it does have these states. The waiting time between DRAFT and VALID has to be minimized in my case, as the end-user is synchronously waiting for that to happen. As about it requiring an orchestrator because of bad aggregate root, could you describe a better choice of aggregate roots to cover this scenario more DDD-ishly? This whole project is merely a draft itself for me to understand DDD, so I can do whatever changes. – bezmax Nov 04 '14 at 10:28
  • I wasn't implying a better design exists. You could include bill cycles and service discounts in the `Order` aggregate, but I guess this wouldn't make any sense conceptually, except if it's acceptable to copy them over when the Order is created and leave them untouched there (because synchronization is hard and evil). Some invariants span whole parts of a system by nature, there's not a lot you can do. I think the solutions you proposed are valid, except maybe I wouldn't name it `Validate` but something closer to the domain language (you mentioned Submit, for instance). – guillaume31 Nov 04 '14 at 10:54
  • 1
    Yet another approach would be to make `DraftOrder` and `Order` two separate entities, and include the validation as part of the `Order` constructor, or create an `OrderFactory` if the constructor becomes too bloated. – guillaume31 Nov 04 '14 at 10:57
  • Yeah, I've thought about separating DraftOrder and Order too. It could be another solution that looks nicer. I'm going to try it and see how it looks. – bezmax Nov 04 '14 at 11:36