Understanding Service-Oriented Architecture
A service should map to a distinct problem domain function. During the process of understanding the problem domain and creating a solution, the designer should create boundaries around service interfaces that map to a distinct area of the problem domain. This is important so that the designer creates a self-contained and independent module. For instance, interfaces that deposit, withdraw, and transfer from a checking account should map to the checking account service. This sounds simplistic, but it is easy to accidentally pollute a service's interface with functions that
- Logically belong in another existing service
- Belong in a new service
- Span multiple services and require a new composite service
- Are really internal knowledge that should not be exposed through an interface
To directly map a service's interfaces to a distinct business concept in the problem domain, the service designer needs a good understanding of the problem domain. Creating a conceptual service model provides this understanding.
The conceptual model of the business, sometimes referred to as the business architecture (Fowler 1997), helps drive the expected use of the services. A conceptual model is one created without regard for any application or technology. It typically consists of a structural model derived from a set of use cases that illustrate how the business works. For instance, a bank manages checking accounts, savings accounts, and customer information. Aconceptual service model for this domain might look similar to Figure 5, although it would be illustrated in much more detail.
Figure 5 A conceptual service model.
This logical model of the business provides the basis for creating and managing service interfaces. Each entity in the logical model is either a stateful entity or a stateless entity. The manager classes are stateless entities, and the other classes are stateful entities. For example, the account entity contains attributes that contain the state of a single account; namely, account number and amount. The savings account, checking account, customer, and address entities also maintain state and are also stateful entities. These entities can be translated into software as entity beans and/or rows in databases. Each stateful entity also has a key that uniquely identifies it within the system.
The entity classes are not directly accessible to the service consumer in SOA. However, in component-based systems, a component consumer accesses an entity component by obtaining a handle to the component. The handle maintains a stateful connection to an entity that has a unique key to identify it. In service-oriented architecture, service consumers cannot access these entities. The service consumer accesses them indirectly by going through the manager interfaces. In SOA, these manager interfaces are implemented as service interfaces.
The manager classes in Figure 5 are stateless classes that manage entities of a particular class. They are the classes that perform create, read, update, and delete (CRUD) operations on the entities they manage. Because the manager classes do not represent a single entity but manage multiple entities, the interfaces for the manager classes require that a unique key be passed in. The unique key identifies the entity for which the action is to be performed. For instance, the ApplyInterest method of SavingsAccountManager requires the rate as well as the account number to identify the entity for executing ApplyInterest behavior. The ApplyInterest method on the SavingsAccount entity does not need a unique key, because it represents a single savings account instance.
The manager classes comprise the basis of the design of the service layer interfaces. The manager interfaces may be converted directly into service interfaces, and the stateful entities may be converted directly into persistent state. The persistent state may be an Enterprise JavaBean, a database row, or both. Stateful entities are not exposed outside the service. The service interface is a stateless interface. Services manipulate stateful entities on behalf of consumers, based on the method consumers call when requesting an operation to perform. Consumers pass in the unique key of the entity they are manipulating and the data for the operation. The service locates the entity that matches the unique key and performs the operation on it with the data.
The integrity of the service layer interfaces will be maintained only if the interfaces map directly to the logical model for the business. Because different applications will use the same services, the logical model must cross application boundaries. Developers should add functionality to services as new applications need those functions. The logical model provides the city plan for developing the service layer. As developers build applications, the software will support more of the logical model's functionality. It is difficult to maintain services' in tegrity over time, because new applications need to interact with services in different ways. The more closely this conceptual model maps to the overall structure of the business it supports, the longer-lived the service layer will be.
Direct mapping is only the first rule we need to implement for modularity. Contracts and information hiding is the second.
Contracts and Information Hiding
An interface contract is a published agreement between a service provider and a service consumer. The contract specifies not only the arguments and return values a service supplies but also the service's preconditions and postconditions. The preconditions are those that must be satisfied before calling the service, to allow the service to function properly. For instance, consider a credit-card validation service that is a two-step process. In the first step, the application sends the account number and amount information to the service. The service responds with an OK. However, for the transaction to go through, the consumer must send a confirmation message to the service. The precondition of the confirmation function is that the information for the confirmation has previously been sent.
The postcondition is the system's state after a function has been executed. The postcondition of the initial submission of information is that the information has been stored for a subsequent commit request.
Parnas and Clements best describe the principles of information hiding:
Our module structure is based on the decomposition criterion known as information hiding [IH]. According to this principle, system details that are likely to change independently should be the secrets of separate modules; the only assumptions that should appear in the interfaces between modules are those that are considered unlikely to change. Each data structure is used in only one module; one or more programs within the module may directly access it. Any other program that requires information stored in a module's data structures must obtain it by calling access programs belonging to that module.
(Parnas and Clements 1984)
This statement assumes that the software executes in a single machine. With service-oriented architecture, we take this principle a little further. The service should never expose its internal data structures. Even the smallest amount of internal information known outside the service will cause unnecessary dependencies between the service and its consumers. Although the information stored in the data structures is necessarily exposed, that information must be transformed from the internal storage structure into an external structure. In other words, the internal data semantics must be mapped into the external semantics of an independent contract. The contract depends only on the interface's problem domain, not on any implementation details.
Exposing internal implementation details is easy to do by creating an interface design with arguments that map to the service's implementation aspects rather than to its functional aspects. For instance, consider a credit-card validation service. The service requires that a credit-card validation request contain the account number, amount, and a special system code. The service uses the system code to determine in which internal database to find the account. The special system code is exposed through the interface, and it exposes information about the internal structure of the service.
There is no functional reason to expose the system code outside the service, because the service should identify the database itself based on the functional data passed in to it. This information is necessary strictly for implementation. Service maintainability is severely affected when designers implement designs such as this. If the internal structure of the service changes, clients of this service are likely to require changes also. If a third internal system is added, for example, clients will have to be updated, even though the interface contract has not changed. This design is generally not consistent with the principles of information hiding and modular design.
The principle of separating the service's interface from its implementation is relevant to the topic of modular software design. It is often thought that service-oriented architecture enforcesthis principle, which is not strictly true. Service-oriented architecture promotes the idea of separation, but as the previous example illustrates, implementation details can pollute a service's interface.
These techniques and concepts help create modular services. Services also stress interoperability, or the ability of different types of systems to use a service.
Page 4 of 8