SOA Future Batches

time to read 11 min | 2154 words

Pair<CustomerDto, ICollection<OrderDto>> GetCustomerAndOrders(int customerId);

Today I had a very interesting lunch conversation, about designing the backend of a web side, SOA style. The initial suggestion called for an interface similar to this one:

public interface ICustomerService
{
    CustomerDto GetCustomerById(int id);
    ICollection<OrderDto> GetOrdersForCustomer(int customerId);
}

One the surface, that was reasonable, until we started talking about the implementation. Which would look somewhat similar to this:

PropertyBag["customer"] = customerSrv.GetCustmerById(customerId);
PropertyBag["orders"] = customerSrv.GetOrdersForCustomer(customerId);

I wasn't pleased with that, because this means two remote calls, instead of one. That, in general, is bad. Especially since developers tend to write small methods, requiring many roundtrips to the server. On the other hand, having an interface that closely matched the UI was going to make the whole SOA thing silly, and make it much harder for other clients to make use of the services (and there are other clients).

Beside, this is ugly:

Pair<CustomerDto, ICollection<OrderDto>> GetCustomerAndOrders(int customerId);

Clearly, something had to be done. I suggested the following, instead of having a service (and end point) per logical service, has a single service, with a single method:

public interface IHippoService
{
    AbstractResponse[] Process(params AbstractRequest[] requests);
}

And we define the following request/response pairs as well:

public class GetCustomerByIdRequest
{
   public int CustomerId;
}
 
public class GetCustomerResponse
{
    public CustomerDto Customer;
}
 
public class GetOrdersByCustomerRequest
{
   public int CustmerId;
}
 
public class GetOrdeersResponse
{
   public ICollection<OrderDto> Orders;
}

If this reminds you of NServiceBus, you are correct. Same pattern, but with vastly different goals.

The main point of going with this approach is that we can now do the following:

var responses = hippoService.Process( new GetCustomerById(customerId), new GetOrdersForCustmer(15));
PropertyBag["customer"] = responses[0];
PropertyBag["orders"] = respones[1];

And this allows us to perform the required operation in a single remote call, instead of two.

Furthermore, this style of programming, while efficient, is not really friendly, we can make this much friendlier by introducing futures into the deal, so the API that we will have will look like this:

var futureCustomer = ProcessInFuture<GetCustomerResponse>(new GetCustmerByIdRequest(customerId));
var futureOrders = ProcessInFuture<GetOrdersResponse>(new GetOrdersForCustomerRequest(customerId));
// use the future values, causing both to be sent to the server in one go

This programing model is much more natural for most developers, but it keep all the performance benefits of the batching approach.

This is also how I can get unbelievable performance from NHibernate, for code that looks very readable, but would execute like a dog without futures and batching.

We have spiked a small test case using WCF, and it works, pretty nicely, I might add.

Thoughts?