Each service capability
encapsulates an independent set of functionality. Sometimes this
functionality will impose specific requirements that pre-determine the
capability’s granularity. Other times, we will be able to set the
granularity based on other factors. A general trend with service
contracts is to provide coarse-grained service capabilities that
accomplish more with less cross-service interaction. To understand why
this is important to service-orientation, let’s begin by looking at a
traditional fine-grained component interface:
Example 1. An example of fine-grained, object-oriented style technical interface.
public class LineItem { public int ItemSeqNo; ... } public class customer { public string CompanyName; public string CompanyAddress; ... } public class OrderStatus { public bool OK; public int OrderNumber; ... }
/*the order manager*/ public interface IOrderManager { OrderStatus CreateOrder(); OrderStatus AddLineItem(int orderNumber, LineItem item); OrderStatus AddCustomerInfo(int orderNumber, customer customer); OrderStatus SubmitOrder(int orderNumber); } public class OrderManager : IOrderManager { public OrderStatus CreateOrder() { throw new NotImplementedException(); } public OrderStatus AddLineItem(int orderNumber, LineItem item) { throw new NotImplementedException(); } public OrderStatus AddCustomerInfo(int orderNumber, customer customer) { throw new NotImplementedException(); } public OrderStatus SubmitOrder(int orderNumber) { throw new NotImplementedException(); } }
/*client application that uses OrderManager*/ public class OrderApplication { public void SubmitOrder() { OrderStatus status; LineItem item; customer customer; IOrderManager orderManager = new OrderManager(); //create order status = orderManager.CreateOrder(); if (!status.OK) { throw new Exception("Cannot create order!"); } item = new LineItem { ItemSeqNo = 1138 }; status = orderManager.AddLineItem(status.OrderNumber, item); if (!status.OK) { throw new Exception("Cannot add item 1138!"); } item = new LineItem{ItemSeqNo=327}; status = orderManager.AddLineItem(status.OrderNumber, item); if (!status.OK) { throw new Exception("Cannot add item 327!"); } customer = new customer { CompanyName = "Cutit supreme customer" }; status = orderManager.AddCustomerInfo(status.OrderNumber, customer); if (!status.OK) { throw new Exception("Cannot add customer info!"); } status = orderManager.SubmitOrder(status.OrderNumber); if (!status.OK) { throw new Exception("Cannot submit order!"); } return; } }
|
The methods in the preceding example can be unsuitable for service capabilities for two primary reasons:
consumer-to-logic coupling–
For example, changing the order of the capabilities should be performed
by verifying that the items of an order are in stock before creating
the order.
chatty interface– The
consumer will have to call multiple capabilities (CreateOrder,
AddLineItem, AddCustomerInfo and SubmitOrder) and will also have to call
the same capability several times (AddLineItem) to carry out a single
task. If the service is implemented as a Web service or a REST service,
this would add considerable network overhead.
The code style in
the following example demonstrates how this interface would be designed
with coarse-grained service capabilities. Note that this code belongs
inside applications and components and is not suitable for
consumer-to-service calls.
Example 2. An example of a coarse-grained service contract.
[DataContract] public class Order { [DataMember] public customer customer; [DataMember] public LineItem[] LineItems; ... } [DataContract] public class LineItem { [DataMember] public int ItemSeqNo; ... } [DataContract] public class customer { public string CompanyName; public string CompanyAddress; ... } [DataContract] public class OrderStatus { [DataMember] public bool Success; [DataMember] public int OrderNumber; ... }
[ServiceContract] public interface IOrderManager { [OperationContract] OrderStatus SubmitOrder(Order order); }
class OrderManager : IOrderManager { public OrderStatus SubmitOrder(Order order) { ... OrderStatus status = new OrderStatus(); status.Success = true; status.OrderNumber = 94; return status; } }
/*ServiceConsumer uses a generated proxy class to call the Web service*/ class ServiceConsumer { public boolSubmitOrderToService() { Order order = new Order(); List<LineItem> items = new List<LineItem>(); LineItem item = new LineItem(); item.ItemSeqNo=1138; items.Add(item); item = new LineItem(); item.ItemSeqNo = 327; items.Add(item); order.LineItems = items.ToArray(); customer customer = new customer(); customer.CompanyName = "Cutit supreme customer"; order.customer = customer; IOrderManager orderManagerProxy = new OrderManager(); OrderStatus orderStatus = orderManagerProxy.SubmitOrder(order); return orderStatus.Success; } }
|
In
this example, the service is called only once (via the SubmitOrder
operation), minimizing network overhead. However, the code inside the
SubmitOrder operation might still look a lot like the code from Example 2.
Note that coarse-grained
capabilities also have a potential downside. Because they tend to
perform more processing at the same time, there is the risk that they
end up carrying out more logic than a given service consumer may
actually require. When warranted, a service contract can accommodate
different service consumers by providing both fine and coarse-grained
variations of the same service capabilities. This is the basis of the
Contract Denormalization pattern.