Understanding Service-Oriented Architecture
One of the benefits of service-oriented architecture is service composition. Developers compose services into applications. Unfortunately, one cannot always know how services will be used in these applications. It is especially difficult to predict how services will be used in future applications. This uncertainty is one of the greatest challenges for service designers, who typically attempt to anticipate future applications when determining the structure of an interface. Because services are executed across a network, it is especially important for interfaces to be correct. If they are not, service consumers will either receive more data than they need or will have to make multiple trips to the service to retrieve all the data they need.
While services in general support coarser-grained interfaces than distributed object-based systems and component-based systems do, the range of coarse still contains degrees of granularity, as Figure 8 shows. Within the range of granularity expected for services, designers still need to decide interface coarseness.
Figure 8 Degrees of granularity.
As explained previously, the service itself can be coarse-grained or fine-grained. This refers to how much functionality the service covers. Let's assume developers need to create an application for manipulating both a checking account and a savings account. Developers have two choices when creating a service to support this function. They could create a coarse-grained service called BankAccountService that manipulates both checking and savings accounts, or they could create two fine-grained services—a SavingsAccountService and a CheckingAccountService. Because BankAccountService supports the functionality of both checking and savings, it is coarser-grained.
Granularity also applies to the way developers implement service methods. Suppose BankAccountService contains a method called GetAccountHolder. A coarse-grained implementation of this function would return the account holder's name and address. A fine-grained version would return just the name. A separate method, called GetAccountHoldersAddress, would return the address. A service method that returns more data is a coarse-grained method. Aservice method that returns less, more specific, data is a fine-grained method. Sometimes service consumers need both fine-grained and coarse-grained methods for a similar function. This is the concept of multi-grained services.
Because services will be used in ways the designers cannot fully anticipate when designing them, the decision about granularity does not have to be absolute. Services do not have to be coarse-grained or fine-grained; they can be coarse-grained and fine-grained, or multi-grained (Stevens 2002). In other words, BankAccountService, SavingsAccountService, and CheckingAccountService can exist simultaneously, as in Figure 9. If service consumers needs access only to a customer's savings account, they should use SavingsAccountService. There is no need for them to know anything about checking accounts. If, on the other hand, they need to know about both checking accounts and savings accounts, they should use BankAccountService.
Figure 9 Multi-grained services.
Why is it necessary to create a composite BankAccountService if there is already a SavingsAccountService and CheckingAccountService? The reason is that services should be as easy as possible to use, and they should meet the expectations of the consumers that use them. It is logical that a consumer would more often than not want access to both checking and savings accounts. Implementing both interfaces is best, because it provides all service consumers with the interfaces that best suit their needs.
Service designers create multi-grained service interfaces by first creating fine-grained services and then wrapping them in coarse-grained façades. It is also possible to create fine-grained fagades that access coarse-grained services. However, it is better to create finer-grained base services, because developers will have more flexibility when deploying them. It is difficult to break up a larger service and deploy it onto multiple machines. However, it is easy to deploy a large number of small-grained services to multiple machines.
The granularity of the service is a crucial design decision. If it is incorrectly predicted, consumers will have access to more functionality than they need. This can be a problem for security at the service level. It might not be possible to restrict a consumer from some methods and not others, only to the entire service. If this is the case, the entire service might have to be opened up to consumers. Developers can do this if they design services at the appropriate level of granularity.
The service interfaces constitute an established contract between the services and the clients. One of the tradeoffs of creating multiple interfaces is that each interface is essentially a published contract. Additional interfaces make managing these contracts between clients and services more difficult, because a change to a functional requirement will affect multiple interfaces. Although it is important to provide the best possible interfaces to consumers, it is also important not to substantially compromise the service's maintainability.
The granularity of the methods within a service is of equal or greater importance than the granularity of the service itself. Using the previous bank account example, consider the retrieval of account holder information from the bank account service. There are several ways to implement this interface:
- A method in BankAccountService called GetAccountHolder that returns only account-holder information and not the address
- Two methods in BankAccountService, called GetAccountHolder and GetAccountHolderAddress; GetAccountHolder would not return address information
- A method in BankAccountService called GetAccountHolder that could return both the name and address of the account holder
- A method in BankAccountService called GetAccountHolder that could have a switch that tells the service whether to return address information as well as account-holder information
- A method in BankAccountService called GetAccountHolder that could accept a list of attributes it wants the service to return; the consumer can choose to get the address by adding the address attributes to the attribute list it passes in to the service
Let's examine these options and their consequences. As Figure 10 shows, BankAccountService returns just account-holder information.
Figure 10 A method that returns only account-holder information.
This scenario works well if the consumer needs only account-holder information. But a consumer who needs address information as well is out of luck. The address information could be retrieved from the service by adding a GetAccountHolderAddress method, as illustrated in Figure 11.
Figure 11 A method that returns both the account-holder's information and address.
This solves the problem of retrieving address information, but if most of the consumers need address information, more trips are necessary. Having the GetAccountHolder method return both account-holder information and address information in one call would improve performance and reduce the work necessary for the consumer to assemble the two results.
Figure 12 illustrates this scenario.
Figure 12 A method that returns either the account-holder's information or address.
This solution works well for consumers who always retrieve address information, but if they almost never need this information, more data than necessary will travel across the network. It will also take longer for service consumers to extract the account-holder data they need from the larger message.
Page 6 of 8