Skip to content

Using client service proxies

Aaron Hanusa edited this page Jan 13, 2016 · 3 revisions

Using client service proxies

Many of the configurations for the sample application involve the use of the Web API project resulting in a configuration of the following workflow: .NET Client → Business Service Command → HTTP Proxy → Web API Controller → Business Service Command → Database (or In-Memory) Proxy.

The great thing about this configuration is that the .NET clients share the same business logic as the Web API application. Not only does this ensure that both applications consume the same business logic, but it ensures that non-.NET clients are also subjected to the business logic, workflows, and business/validation rules.

For the majority of Insert, Update, and Delete service command methods, these types of configurations work great. This is especially true when these commands simply interact with a single data proxy upon successful validation of business rules. Most of the service commands in the sample business layer fall into this category.

However, things can become complicated when dealing with more complex commands, especially those responsible for orchestrating workflows or business logic that updates multiple resources.

Further, often times workflows such as these need to be executed atomically within the context of a transaction. What may not be immediately obvious is that the .NET client that consumes a service command configured to use HTTP data proxies will have a difficult time orchestrating a rollback strategy in the event that one of the updates against a data proxy fails.

While you could certainly inject a transaction strategy into a service command that handles transactional support against multiple out-of-band HTTP invocations, it's nothing short of trivial.

To help illustrate these issues, let's observe the ShipCommand method of OrderItemService, which is responsible for updating the status of an order item and reducing the inventory associated with this item.

public virtual ICommand<OrderItem> ShipCommand(long orderItemID)
{
    var proxy = DataProxy as IOrderItemDataProxy;
    return new ShipOrderItemCommand(orderItemID, proxy, _inventoryDataProxy, _transactionContext);
}

ShipCommand simply returns an instance of the ShipOrderItemCommand, and will invoke the following code when Execute() is invoked (ShipOrderItemCommand.Execute() and ShipOrderItemCommand.ExecuteAsync() will invoke OnExecute or OnExecuteAsync, respectively):

protected override OrderItem OnExecute()
{
    return _transactionContext.Execute(() =>
    {
        var inventoryItem = _inventoryDataProxy.GetByProduct(CurrentOrderItem.ProductID);
        if (inventoryItem.QuantityOnHand - CurrentOrderItem.Quantity >= 0)
        {
            CurrentOrderItem.OrderStatus().SetShippedState();
            CurrentOrderItem.ShippedDate = DateTime.Now.ToUniversalTime();
            inventoryItem.QuantityOnHand -= CurrentOrderItem.Quantity;
            _inventoryDataProxy.Update(inventoryItem);
        }
        else
        {
            CurrentOrderItem.OrderStatus().SetBackorderedState();
            CurrentOrderItem.BackorderedDate = DateTime.Now.ToUniversalTime();
        }
        return _orderItemDataProxy.Ship(CurrentOrderItem);
    });
} 

This method is responsible for decrementing inventory followed by updating the status of the current order item to 'shipped' within an atomic transaction.

Let's take a look at a sequence diagram that entails a .NET client consuming the OrderItemService injected with HTTP data proxies, with the receiving Web API application consuming the OrderItemService injected with database (EF6) data proxies:

service-proxies

In this scenario, the .NET client invokes the Execute method of the ShipOrderItemCommand returned by the ShipCommand method of OrderItemService. The ShipCommand.Execute method then invokes the Ship method of the injected OrderItemsHttpServiceProxy, which issues an HTTP PUT request to the receiving Ship method of the OrderItemsController (Web Api).

Because the Ship method of OrderItemsController is also configured to use the ShipOrderItemCommand, this logic will be executed again, thus decrementing the inventory twice (initially done on the client then on the server).

To address this issue, the OrderItemClientService class was created. This class extends OrderItemService and overrides the ShipCommand method to bypass the logic of the OrderItemShipCommand and directly marshal calls to the OrderItemsHttpServiceProxy.Ship method, delegating the responsiblility of executing the OrderItemShipCommand logic on the server (Web API Controller).

Here is a sequence diagram illustrating a new configuration using the client version in the .NET client:

client-proxies

In this configuration, the client shares business logic with the server, excepting the shipping functionality which is now handled exclusively on the server.

Something else to mention is that the shipping logic in the OrderItemShipCommand executes atomically within the context of a transaction. As stated previously, it is notoriously difficult to orchestrate transactions against out-of-band HTTP invocations. Creating a client service proxy allows us to delegate that responsibility to the server, where it can orchestrate these transactions directly against the configured database within a transaction context (DTC, etc.).

One final note is that sometimes business services contain alot of business rules that interact with a database. In this scenario, it might pay off to bypass business logic in the .NET client and delegate it to the server (Web Api application) to reduce database chattiness. Creating client service proxies can help facilitate this as well.