-
Notifications
You must be signed in to change notification settings - Fork 11
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:
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:
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.