Search code examples
oopdesign-patternsbusiness-logicbusiness-logic-layer

Business operations/workflow design pattern


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();
}

Where would be a good place to put code like this?

In it's own class?

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.


Solution

  • You can implement business operations in some dedicated API class. For example, API class which is primarily concerned with order-involving operations may be called OrdersAPI:

    class OrderOperationsAPI {
    
        void someOperation() {} // implement your SomeOperation here
        void someOtherOperation() {} // implement something else over orders, shippings, etc..
    
    }
    

    Popular approach is to pass such business operations, which can be of any complexity, business-wise, into "Unit of work" object.

    This unit-of-work usually implemented as a transaction over database or other persistent storage. Idea here is to keep track of which objects were modified during transaction, then persist changed objects on commit (or discard everything on rollback).

    It's important feature that there's special accounting object for this purpose, which is decoupled from business entities it oversees.

    Usual approach to make business API calls pass through unit-of-work objects is to employ some form of AOP (aspect-oriented programming). This wraps call to business API (someOperation() in your case) with entry code, which fills context for business objects with needed data sources and with finalizing code, which checks if transaction to be committed, and if yes, gathers all modified objects and updates corresponding entries in persistent storage.