Functional vs. Interface Granularity: Still Powerful Ideas
Ever since we called out coarse granularity as a Web Services Idée Forte (powerful idea) way back in 2002, SOA architects have continued to struggle with the concept of granularity. Unlike the other two Idées Fortes—asynchrony and loose coupling—the principle of granularity hasn’t gotten the respect that it deserves over the years. In spite of this oversight, ZapThink considers granularity to be a core SOA design principle, and we treat it as such in our Licensed ZapThink Architect course.
From a technical perspective, the granularity of an interface impacts performance. Excessively fine-grained interfaces lead to increased traffic on the network, because the consumer must re-query the provider multiple times to complete a task. However, if the performance impact of excessive fine granularity were the whole story, granularity would be an important consideration, but not important enough to rate as a core SOA design principle.
The reason we consider granularity to be so significant is because there is a general correlation between coarse granularity and business context. Generally speaking, when the business makes a request of a Service, then either the request or the response (or both) have some structure to it. For example, if you submit an order, you are sending a number of details that have business context—namely, the details of the order. Likewise, if you request customer information from a Service, for example, you typically want a number of details.
On the other hand, fine-grained Services tend to have a technical context but lack a business context. Submit a number, get a number in response, for example. There are exceptions, naturally: a credit score Service is the one we talk about in the LZA course. Submit a Social Security number, get back a three-digit credit score. In spite of examples like this one, however, coarse granularity generally correlates with business context.
The tradeoff with coarse granularity is that while it correlates with business context, coarse grained Services also tend to be less reusable, because the interface becomes increasingly specific to a particular purpose. Architects must therefore balance reusability and business context when they consider granularity in their interface designs.
Distinguishing Functional Granularity
Up to this point we’ve been discussing the granularity of the interface. After all, Services are interfaces or abstractions of interfaces, so among an architect’s first considerations when designing a Service is the granularity of the interface. However, there is more to granularity than the interface: the granularity of the underlying implementation, what ZapThink calls functional granularity.
For example, let’s consider an “add product to order” Service. Send this Service a product number, and it adds the product to an existing order and returns a confirmation. Clearly, this Service has a fine-grained interface.
Now, what if the product you were adding was actually a set of products? For example, you add a laptop to an order and the laptop, battery and several questionable pieces of software come along for the ride. You could call add product Service many times, of course, but that might lead to a performance bottleneck, and you may not know all the individual product numbers in the set anyway. Instead, rework the add product to order Service so that you can send an ID that represents a set of products as an input. Now you can add any number of products with a single Service call. The granularity of the interface doesn’t change, but the new Service’s functional granularity is now much coarser.
The next design question is whether the add (single) product to order Service and the add multiple products to order Service should be two separate Services or a single Service. You could argue that you should implement two Services with different levels of functional granularity as two distinct Services. However, this design decision introduces unnecessary consumer-side coupling, because the consumer has to know which Service to call in order to elicit a specific response. Instead, you should define the functional granularity of a Service in the Service contract, as part of the semantic definition of the Service, so that you can combine both situations into the same Service.
Because the functional granularity of a Service would otherwise be hidden from the consumer, it’s important to provide the information about this behavioral aspect of the Service in the contract, so that you can expose a single abstracted business Service that represents whatever level of granularity consumers require from the Service. This approach also increases the reusability of the Service. In fact, separating functional granularity from interface granularity allows architects to have it both ways, because the interface may be fine-grained, improving reusability, while the functionality is coarse-grained, increasing the business context for the Service.
Functional Granularity of Composite Services
Adding products to an order is a simple example of coarse functional granularity, since a developer would likely be able to implement such a Service with a single well-crafted database call. In other instances, however, it’s more feasible to implement the underlying functionality of the Service as a composition.
For example, let’s say you wanted to implement an approve mortgage Service. Send the service an ID number for a mortgage application, and the Service executes a multiple-step mortgage approval process and returns a confirmation. The approve mortgage Service is another example of a Service with coarse functional granularity and fine interface granularity. Once again, the Service has a clear business context, which correlates with the functional more so than the interface granularity.
The ZapThink Take
Our 2002 Idées Fortes article focused on Web Services, first, because 2002 was the heyday of SOAP-based Services, and second, because SOAP messages lend themselves better to coarse-grained interactions, because of the substantial overhead that SOAP requires.
Our discussion today, however, refers to Services much more generally. The entire discussion applies equally well to REST-based interactions as it does to SOAP-based Web Service interactions. In fact, the use of REST-based Services should emphasize functional granularity in their design. The use of the GET operation (one of only four possible operations as part of the HTTP-centric REST approach) uses the URL as the mechanism to specify the resource-based Service the consumer is querying. While it is possible to use query strings to add more data to the GET call, this approach is problematic if you want to query a range of resources rather than a single resource. Alternatively, an architect can use a URL hierarchy or a different Service to refer to a group of resources rather than a single one, but these alternatives face the same tight coupling issue mentioned earlier.
Instead, the architect should leverage the concept of coarse functional granularity. Create a GET Service in which the resource in the query can refer to either a single resource or multiple resources. In this way, the Service can increase its functional granularity without any change to the interface granularity. Indeed, the existing Service won’t fail even as you increase its functional granularity.
This REST-specific angle highlights an important point. From the architect’s perspective, the choice of message and transport protocols is separate from the granularity consideration. The architect’s focus is instead on balancing reusability and business context while maintaining loose coupling: a focus core to proper Service-Oriented thinking, regardless of whether the Services are SOAP or REST-based.