dimanche 24 janvier 2016

Design pattern for business operations involving several objects

Let's say I want to perform an operation that involves updating data in several objects.

Here's an example:

void SomeOperation()
{    
    order.SetStatus(OrderStatus.ReadyForShipping);
    order.Save();

    email = emailFactory.CreateOrderIsReadyEmail(order);
    mailService.QueueEmail(email);
    mailService.Save();

    shippingHandler = shippingHandlerSelectionStrategy.select(order.Location);
    shippingHandler.addItem(order);
    shippingHandler.Save();

    orderStatistics.AddReadyForShippingOrder(DateTime.Today);
    orderStatistics.Save();
}

Where exactly should this code go?

Should it be it's own class? Something like this?

class ReadyForShippingOperation : Operation 
{
    override void Execute() 
    {
        // ...
    }
}

But what if this operation has very specific business rules for when it can be executed, the sort of rules that would require intimate knowledge of those objects?

For example, say we can only execute this operation when an order is a certain state. But this state is not some value on the order, it's a very specific set of requirements that's only ever relevant to this operation.

Do I really want to add something like this to my order object?

class Order 
{
    bool IsReadyForThatShippingOperation();
}

This method only concerns the order insofar as it's implementation is highly coupled with the order's data. But conceptually it's not really part of the order. It's relevant only for our one operation.

Another option would be to make the details of the order public. This also doesn't feel right. For example, our operation class might look like:

class ReadyForShippingOperation : Operation 
{
    override void Execute()
    { 
        if (!OrderIsReady(order)) {
            // Handle error
        }

        // ...
    }

    bool OrderIsReady(order) 
    {
        if (order.CreatedDate > someValue) return false;
        if (order.LastUpdatedDate > someValue) return false;
        if (!order.IsValid) return false;
        if (!order.chachingState == InProgress) return false;
        return true;
    }
}

What happens in this case is I find myself forced to expand the Order API, for the single purpose of giving the OrderIsReady() method permission to grasp its dirty hands around it.

Perhaps this example is too specific. In general what I really want to know is how to best organize business operations that require intimate data from many objects but don't seem to belong to any one object.

Aucun commentaire:

Enregistrer un commentaire