views:

265

answers:

3

I have a domain Aggregate, call it "Order" that contains a List of OrderLines. The Order keeps track of the sum of the Amount on the Order Lines. The customer has a running "credit" balance that they can order from that is calculated by summing the history of their database transactions. Once they use up all the money in the "pool" they can't order any more products.

So every time a line is added to the order, I need to get to check how much is left in the pool and if the order pushes them over it. The amount in the pool is continually changing because other related customers are continually using it.

The question is, thinking in terms of DDD, how do I get that amount since I don't want to pollute my Domain Layer with DataContext concerns (using L2S here). Since I can't just query out to the database from the domain, how would I get that data so I can validate the business rule?

Is this an instance where Domain Events are used?

+1  A: 

In such a scenario, I off-load responsibility using events or delegates. Maybe the easiest way to show you is with some code.

Your Order class will have a Predicate<T> that is used to determine if the customer's credit line is big enough to handle the order line.

public class Order
{
    public Predicate<decimal> CanAddOrderLine;

    // more Order class stuff here...

    public void AddOrderLine(OrderLine orderLine)
    {
        if (CanAddOrderLine(orderLine.Amount))
        {
            OrderLines.Add(orderLine);
            Console.WriteLine("Added {0}", orderLine.Amount);
        }
        else
        {
            Console.WriteLine(
                "Cannot add order.  Customer credit line too small.");
        }
    }
}

You will probably have a CustomerService class or something like that to pull the available credit line. You set the CanAddOrderLine predicate before adding any order lines. This will perform a check of the customer's credit each time a line is added.

// App code.
var customerService = new CustomerService();
var customer = new Customer();
var order = new Order();
order.CanAddOrderLine = 
    amount => customerService.GetAvailableCredit(customer) >= amount;

order.AddOrderLine(new OrderLine { Amount = 5m });
customerService.DecrementCredit(5m);

No doubt your real scenario will be more complicated than this. You may also want to check out the Func<T> delegate. A delegate or event could be useful for decrementing the credit amount after the order line is placed or firing some functionality if the customer goes over their credit limit in the order.

Good luck!

Kevin Swiber
I like that. What I'm doing now is injecting a service into the domain object so it doesn't know where it's getting the amount. In concept that's similar to what you are doing here.I'll try this and see how I like it. Thanks Kevin!
jlembke
That's okay if it works for you. An alternative is to keep your Domain Model in a lower layer than Domain Services. It feels like a better separation of concerns and helps with testing. I really like using Jeffrey Palermo's Onion Architecture. You should check it out. http://jeffreypalermo.com/blog/the-onion-architecture-part-1/
Kevin Swiber
The domain entities should be able to perform these business requirements without requiring special injection of interfaces, predicates, etc. The key is to have your repositories return objects tailored for the exact role that they're needed for at that moment. This completely liberates your service and application code from having to know **anything** about the domain logic, which is fully encapsulated in your domain entities. Thus your code ends up much clearer in its intent, without a load of messy baggage.
Mike Scott
@Kevin, Domain Events (thank you Udi Dahan) got me out of the injected service situation. Works beautifully. DDD/Onion Architecture style separation of concerns is what made me uncomfortable with injecting the service anyway.
jlembke
@jlembke: Udi is a genius. Seems like a good fit! You should post your solution and answer your own question.
Kevin Swiber
+1  A: 

In addition to the problem of getting the "pool" value (where I would query the value using a method on an OrderRepository), have you considered the locking implications for this problem?

If the "pool" is constantly changing, is there a chance that someone elses transaction creeps in just after your rule passes, but just before you commit your changes to the db?

Eric Evans refers to this very problem in Chapter 6 of his book ("Aggregates").

Vijay Patel
Vijay, you are 100% correct. This is the next problem we are losing sleep over. I missed that part of the book. I'll read that now!!
jlembke
+3  A: 

Your Order aggregate should be fully encapsulated. It therefore needs to be able to determine whether it's valid to add an item, i.e. whether or not the customer credit is exceeded. There are various ways to do this but they all depend on the Order repository returning a specific aggregate that knows how to do this particular thing. This will probably be a different Order aggregate from one you'd use for satisfying orders, for example.

You have to recognise, then capture in code, the fact that you're expecting the order to fulfil a particular role in this case, i.e. the role of adding additional line items. You do this by creating an interface for this role and a corresponding aggregate that has the internal support for the role.

Then, your service layer can ask your Order repository for an order that satisfies this explicit role interface and the repository thus has enough information about what you need to be able to build something that can satisfy that requirement.

For example:

public interface IOrder
{
  IList<LineItem> LineItems { get; }
  // ... other core order "stuff"
}

public interface IAddItemsToOrder: IOrder
{
  void AddItem( LineItem item );
}

public interface IOrderRepository
{
  T Get<T>( int orderId ) where T: IOrder;
}

Now, your service code would look something like:

public class CartService
{
  public void AddItemToOrder( int orderId, LineItem item )
  {
    var order = orderRepository.Get<IAddItemsToOrder>( orderId );
    order.AddItem( item );
  }
}

Next, your Order class that implements IAddItemsToOrder needs a customer entity so that it can check the credit balance. So you just cascade the same technique by defining a specific interface. The order repository can call on the customer repository to return a customer entity that fulfils that role and add it to the order aggregate.

Thus you'd have a base ICustomer interface and then an explicit role in the form of an ICustomerCreditBalance interface that descends from it. The ICustomerCreditBalance acts both as a marker interface to your Customer repository to tell it what you need the customer for, so it can create the appropriate customer entity, and it has the methods and/or properties on it to support the specific role. Something like:

public interface ICustomer
{
  string Name { get; }
  // core customer stuff
}

public interface ICustomerCreditBalance: ICustomer
{
  public decimal CreditBalance { get; }
}

public interface ICustomerRepository
{
  T Get<T>( int customerId ) where T: ICustomer;
}

Explicit role interfaces give repositories the key information they need to make the right decision about what data to fetch from the database, and whether to fetch it eagerly or lazily.

Note that I've put the CreditBalance property on the ICustomerCreditBalance interface in this case. However, it could just as well be on the base ICustomer interface and ICustomerCreditBalance then becomes an empty "marker" interface to let the repository know that you're going to be querying the credit balance. It's all about letting the repository know just what role you want for the entity it returns.

The final part which brings this all together, as you mentioned in your question, is domain events. The order can raise a failure domain event if the customer's credit balance would be exceeded, to notify the service layer that the order is invalid. If the customer has enough credit, on the other hand, it can either update the balance on the customer object or raise a domain event to notify the rest of the system that the balance needs to be reduced.

I've not added the domain event code to the CartService class since this answer is already rather long! If you want to know more about how to do that, I suggest you post another question targeting that specific issue and I'll expand on it there ;-)

Mike Scott
Mike, this is an excellent answer in terms of the Aggregate design. If I get the opportunity in the next sprint, I will try to refactor to what you have here.The way I have solved this issue is by using Domain Events similar to Udi Dahan's recent article [http://www.udidahan.com/2009/06/14/domain-events-salvation/]. When the event signifying the addition of a line is fired, the Event Handler checks the balance and sets an internal property on the aggregate. Now a business rule checks that property and reports the success or failure back to the client.
jlembke
I think that domain events should be used for notification in a kind of "fire and forget" mode. The motivation for having them is to allow notification/callback without either side being coupled to the other.Your solution with domain events is perfectly viable, but it's using them as a kind of decoupled remote procedure call. I think it's better to reserve domain events for one-way notification using a messaging metaphor. This greatly helps scalability - you can then implement them with a service bus, for example.
Mike Scott
I forgot to say: Udi's previous two articles on domain events (http://www.udidahan.com/2008/02/29/how-to-create-fully-encapsulated-domain-models/) **also** answer your question - he uses domain events to signal validation failure **and** the technique I'm advocating here to do external validation checks. So you're getting 2 for the price of 1 in Udi's domain events articles ;-)
Mike Scott
@Mike Scott: So do you type-check T in Repository.Get<T> to determine the appropriate functionality? If so... that, along with empty marker interfaces, make me a little uneasy. I do really like the concept. I may implement it differently, however. Gives me some food for thought. Thanks!
Kevin Swiber
What makes you uneasy about empty interfaces? You don't have to do an explicit type check. Instead, I create a generic interface for a given behaviour, e.g. for fetching strategies I have IFetchingStrategy<T> and then I have a class that implements it, e.g. CustomerCreditBalanceFetchingStrategy: IFetchingStrategy<ICustomerCreditBalance> that gives the information required to control what is eagerly or lazily loaded, etc. The repository can get this in various ways, e.g. requesting IFetchingStrategy<ICustomerCreditBalance> from an IoC container or service locator.
Mike Scott
This is all based on Udi Dahan's work. This slide presentation gives the gist of it: http://cid-c8ad44874742a74d.skydrive.live.com/self.aspx/Blog/Intentions%20and%20Interfaces.pdf. Also http://www.udidahan.com/2007/04/23/fetching-strategy-design/
Mike Scott