John Evdemon
Microsoft Corporation, Architecture Strategy
August 2005
Applies to:
Enterprise Architecture
Service Oriented Architecture
Web Services
Summary: The Principles of Service Design series has been developed to communicate best practices and sample codes when relevant. The first in this multi-paper series, this paper provides fundamental principles for designing and implementing Web services, including a brief review of Service Oriented Architecture (SOA) concepts and a detailed discussion of several patterns and anti-patterns that developers can leverage when building Web services. Guidance is applicable to any programming language or platform for which Web services can be developed and deployed. (18 printed pages)
Inspired by a webcast given with Ron Jacobs, Microsoft Patterns & Practices
Thanks to Ron Jacobs of the Patterns and Practices team for many of the ideas and concepts expressed in this paper. Ron and I gave a webcast based on this content in late 2004. This paper would not have been written without Ron's assistance.
You can learn more about Ron's work with the Patterns and Practices team at http://www.microsoft.com/resources/practices/default.mspx and http://www.gotdotnet.com/team/rojacobs/.
Downloads for this article:
Principles of Service Design: Document Centric Pattern.
Principles of Service Design: Idempotent Pattern.
Principles of Service Design: Reservation Pattern.
About the SOA Design Principles Series
An Introduction to Service Oriented Architecture (SOA)
Patterns and Anti-Patterns for SOA
Conclusion
This is the first of a multi-paper series focusing on designing more effective Web services. While this first paper was developed in conjunction with Microsoft's patterns & practices team, future papers may include collaborations with other teams and individuals—both inside and outside of Microsoft.
Readers should exercise caution regarding the sample code developed for the SOA Design Principles Series. Sample code is provided for illustrative purposes only. Readers are encouraged to review, compile and learn from the code. Readers should avoid attempting to use any of the sample code in a production environment. MICROSOFT DISAVOWS ANY RESPONSIBILITY FOR DAMAGES IF READERS DECIDE TO USE ANY OF THE SAMPLE CODE IN A PRODUCTION ENVIRONMENT.
System requirements for the sample code:
While the sample code is not supported by Microsoft, the writers of this paper welcome your feedback.
Note Since SOA is rapidly evolving, the papers and sample code in this series may be revised as the space continues to mature.
SOA has become a well-known and somewhat divisive acronym. If one asks two people to define SOA one is likely to receive two very different, possibly conflicting, answers. Some describe SOA as an IT infrastructure for business enablement while others look to SOA for increasing the efficiency of IT. In many ways SOA is a bit like John Godfrey Saxe's poem about the blind men and the elephant—each of the men describes the elephant a bit differently because each of them are influenced by their individual experiences (for example, the man touching the trunk believes it's a snake while the one touching a tusk believes it's a spear). Mr. Saxe's elephant is much easier to describe because it exists as a physical entity—SOA, however, is a much harder to describe since design philosophies are not available as a physical manifestation.
SOA is an architectural approach to creating systems built from autonomous services. With SOA, integration becomes forethought rather than afterthought—the end solution is likely to be composed of services developed in different programming languages, hosted on disparate platforms with a variety of security models and business processes. While this concept sounds incredibly complex it is not new—some may argue that SOA evolved out of the experiences associated with designing and developing distributed systems based on previously available technologies. Many of the concepts associated with SOA, such as services, discovery, and late binding were associated with CORBA and DCOM. Similarly, many service design principles have much in common with earlier OOA/OOD techniques based upon encapsulation, abstraction, and clearly defined interfaces.
The acronym SOA prompts an obvious question—what, exactly, is a service? Simply put, a service is a program that can be interacted with through well-defined message exchanges. Services must be designed for both availability and stability. Services are built to last while service configurations and aggregations are built for change. Agility is often promoted as one of the biggest benefits of SOA—an organization with business processes implemented on a loosely-coupled infrastructure is much more open to change than an organization constrained by underlying monolithic applications that require weeks to implement the smallest change. Loosely-coupled systems result in loosely-coupled business processes, since the business processes are no longer constrained by the limitations of the underlying infrastructure. Services and their associated interfaces must remain stable, enabling them to be re-configured or re-aggregated to meet the ever-changing needs of business. Services remain stable by relying upon standards-based interfaces and well-defined messages—in other words, SOAP and XML schemas for message definition. Services designed to perform simple, granular functions with limited knowledge of how messages are passed to or retrieved from it are much more likely to be reused within a larger SOA infrastructure. As stated earlier, recalling basic OO design principles regarding encapsulation and interface design will serve us well as we design and build reusable Web Services. We can extend these OO principles into the world of Web services by further understanding the frequently cited "four tenets" of Service Orientation:
Services interact through explicit message-passing over well-defined boundaries. Crossing service boundaries may be costly, depending upon geographic, trust, or execution factors. A boundary represents the border between a service's public interface and its internal, private implementation. A service's boundary is published by means of WSDL and may include assertions dictating the expectations of a given service. Crossing boundaries is assumed to be an expensive task for several reasons, some of which are listed below:
The Service-Oriented Integration pattern tells us that "service invocations are subject to network latency, network failure, and distributed system failures, but a local implementation is not. A significant amount of error detection and correction logic must be written to anticipate the impacts of using remote object interfaces." While we should assume that crossing boundaries is an expensive process, we must also exercise caution in the deployment of local methods designed to minimize such boundary crossings. A system that implements monolithic local methods and objects may gain performance but duplicate functionality of a previously defined service (this technique was referred to as "cut and paste" in OOP and shares the same risks regarding versioning of the service).
There are several principles to keep in mind regarding the first Tenet of SO:
Services are entities that are independently deployed, versioned, and managed. Developers should avoid making assumptions regarding the space between service boundaries since this space is much more likely to change than the boundaries themselves. For example, service boundaries should be static to minimize the impact of versioning to the consumer. While boundaries of a service are fairly stable, the service's deployment options regarding policy, physical location, or network topology are likely to change.
Services are dynamically addressable through URIs, enabling their underlying locations and deployment topologies to change or evolve over time with little impact upon the service itself (this is also true of a service's communication channels). While these changes may have little impact upon the service, they can have a devastating impact upon applications consuming the service. What if a service you were using today moved to a network in New Zealand tomorrow? The change in response time may have unplanned or unexpected impacts upon the service's consumers. Service designers should adopt a pessimistic view of how their services will be consumed—services will fail and their associated behaviors (service levels) are subject to change. Appropriate levels of exception handling and compensation logic must be associated with any service invocation. Additionally, service consumers may need to modify their policies to declare minimum response times from services to be consumed. For example, consumers of a service may require varying levels of service regarding security, performance, transactions, and many other factors. A configurable policy enables a single service to support multiple SLAs regarding service invocation (additional policies may focus on versioning, localization, and other issues). Communicating performance expectations at the service level preserves autonomy, since services need not be familiar with the internal implementations of one another.
Service consumers are not the only ones who should adopt pessimistic views of performance—service providers should be just as pessimistic when anticipating how their services are to be consumed. Service consumers should be expected to fail, sometimes without notifying the service itself. Service providers also cannot trust consumers to "do the right thing." For example, consumers may attempt to communicate using malformed/malicious messages or attempt to violate other policies necessary for successful service interaction. Service internals must attempt to compensate for such inappropriate usage, regardless of user intent.
While services are designed to be autonomous, no service is an island. A SOA-based solution is fractal, consisting of a number of services configured for a specific solution. Thinking autonomously, one soon realizes there is no presiding authority within a service-oriented environment—the concept of an orchestration "conductor" is a faulty one (further implying that the concept of "roll-backs" across services is faulty—but this is a topic best left for another paper). The keys to realizing autonomous services are isolation and decoupling. Services are designed and deployed independently of one another and may only communicate using contract-driven messages and policies.
As with other service design principles, we can learn from our past experiences with OO design. Peter Herzum's and Oliver Sims's work on Business Component Factories provides some interesting insights on the nature of autonomous components. While most of their work is best suited for large-grained, component-based solutions, the basic design principles are still applicable for service design.
Given these considerations, here are some simple design principles to help ensure compliance with the second principle of SO:
As stated earlier, service interaction should be based solely upon a service's policies, schema, and contract-based behaviors. A service's contract is generally defined using WSDL, while contracts for aggregations of services can be defined using BPEL (which, in turn, uses WSDL for each service aggregated).
Most developers define classes to represent the various entities within a given problem space (for example, Customer, Order, and Product). Classes combine behavior and data (messages) into a single programming-language or platform-specific construct. Services break this model apart to maximize flexibility and interoperability. Services communicating using XML schema-based messages are agnostic to both programming languages and platforms, ensuring broader levels of interoperability. Schema defines the structure and content of the messages, while the service's contract defines the behavior of the service itself.
In summary, a service's contract consists of the following elements:
Service consumers will rely upon a service's contract to invoke and interact with a service. Given this reliance, a service's contract must remain stable over time. Contracts should be designed as explicitly as possible while taking advantage of the extensible nature of XML schema (xsd:any) and the SOAP processing model (optional headers).
The biggest challenge of the Third Tenet is its permanence. Once a service contract has been published it becomes extremely difficult to modify it while minimizing the impact upon existing service consumers. The line between internal and external data representations is critical to the successful deployment and reuse of a given service. Public data (data passed between services) should be based upon organizational or vertical standards, ensuring broad acceptance across disparate services. Private data (data within a service) is encapsulated within a service. In some ways services are like smaller representations of an organization conducting e-business transactions. Just as an organization must map an external Purchase Order to its internal PO format, a service must also map a contractually agreed-upon data representation into its internal format. Once again our experiences with OO data encapsulation can be reused to illustrate a similar concept—a service's internal data representation can only be manipulated through the service's contract. Pat Helland examines several issues related to public and private data representations in " Data on the Outside vs. Data on the Inside."
Given these considerations, here are some simple design principles to help ensure compliance with the third principle of SO:
While this is often considered the least understood design tenet, it is perhaps one of the most powerful in terms of implementing flexible Web services. It is not possible to communicate some requirements for service interaction in WSDL alone. Policy expressions can be used to separate structural compatibility (what is communicated) from semantic compatibility (how or to whom a message is communicated).
Operational requirements for service providers can be manifested in the form of machine-readable policy expressions. Policy expressions provide a configurable set of interoperable semantics governing the behavior and expectations of a given service. The WS-Policy specification defines a machine-readable policy framework capable of expressing service-level policies, enabling them to be discovered or enforced at execution time. For example, a government security service may require a policy enforcing a specific service level (Passport photos meeting established criteria must be cross-checked against a terrorist identification system, for example). The policy information associated with this service could be used with a number of other scenarios or services related to conducting a background check. WS-Policy can be used to enforce these requirements without requiring a single line of additional code. This scenario illustrates how a policy framework provides additional information about a service's requirements while also providing a declarative programming model for service definition and execution.
A policy assertion identifies a behavior that is a requirement (or capability) of a policy subject. (In the scenario above the assertion is the background check against the terrorist identification system.) Assertions provide domain-specific semantics and will eventually be defined within separate, domain-specific specifications for a variety of vertical industries (establishing the WS-Policy "framework" concept).
While policy-driven services are still evolving, developers should ensure their policy assertions are as explicit as possible regarding service expectations and service semantic compatibilities.
Now that you have an understanding of SOA concepts (including the SO design tenets), it's time to start applying what we have learned. The remainder of this paper introduces two Anti-Patterns and three Patterns. Each of these Anti-Patterns and Patterns has been designed to build upon the concepts discussed earlier.
People tend to think and communicate in patterns. Christopher Alexander, author of several books on pattern languages, has defined patterns as "an abstraction from a concrete form which keeps recurring in specific, non-arbitrary contexts." Patterns and pattern languages are ways to describe best practices, proven designs, and capture past experiences in a way for others to learn from these experiences. Patterns are a proven approach for rapidly understanding design guidelines and the various contexts in which they should be applied. Anti-patterns are, as one might expect, the opposite of patterns. Where patterns provide proven guidance and best practices, anti-patterns illustrate common design flaws and serve as a method for learning from the mistakes of others. The remainder of this paper provides an informal review of two anti-patterns and three patterns, all of which are designed to serve as a guide for developing more effective Web services.
The anti-patterns and patterns in this paper follow the format listed below:
Anti-patterns include an additional recommendation on how their associated design flaws can be improved.
A Word About the Code Samples
Listing 1. Simple Visual Basic .NET CRUD Service
<WebMethod()> _
Public Sub Create( ByVal CompanyName As String, ByVal
ContactName As String, ByVal ContactTitle As String,
ByVal Address As String, ByVal City As String, ByVal
State As String, ByVal Zip As String, ByVal Country As
String, ByVal Telephone As String, ByVal Fax As String)
<WebMethod()> _
Public Function MoveNext() As Boolean
End Function
<WebMethod()> _
Public Function Current() As Object
End Function
<WebMethod()> _
Public Sub UpdateContactName( ByVal NewName as String)
End Sub
<WebMethod()> _
Public Function CommitChanges()
End Function
The risks and issues listed above can be avoided by changing the service in the following manner:
Listing 2. Sample Visual Basic .NET Data Tier Service
<WebMethod()> _
Public Function QueryDatabase( ByVal Database as String,
SQLQuery as string) As DataSet
<WebMethod()> _
Public Function Execute( ByVal Command as Integer,
Arguments as string) As Boolean
The risks and issues listed above can be avoided by changing the service in the following manner:
The sample code for this pattern is available for download.
Listing 3. Sample C# Document Processor Service
[WebMethod()]
public FindCustomerByCountryResponse FindCustomersByCountry(
FindCustomerByCountry request)
{
...
}
Services defined using this simple pattern will be compliant with the SO design tenets:
There are some minor issues with this approach that should be noted:
The sample code for this pattern is available for download.
Return a cached copy of the response.
Process the message again as if the first request was never received.
Throw an exception and return an error.
.gif)
Figure 1. Idempotent Message Pattern
Idempotent messaging is a difficult issue. Each of the three options for handling duplicate UOW IDs has several considerations:
What is the current value is different from the cached value?
What if the response was an error?
What if the consumer reuses UOW IDs for unrelated units of work?
The UOW ID should be part of the request schema, tying the handling of duplicate requests to the associated business process. The UOW ID could also be added as a custom SOAP header, enabling duplicate handling to become part of the overall message processing infrastructure. A URI for the consumer should also be included to help detect reentrancy. An audit trail of modifications can be automatically maintained, enabling the tracing of modification requests back to a given UOW ID. Lastly, the issue associated with cache "freshness" can be addressed by reflecting what the response was at the time the request was received. Cache management is another difficult topic that is out of scope for this paper. (Readers interested in understanding caching issues in greater detail are encouraged to review the MSDN article Dealing with Concurrency: Designing Interaction between Services and Their Agents.)
Supporting idempotent messaging increases service autonomy (the first design tenet) since services are no longer dependent upon consumers to "do the right thing." There are some minor issues with this approach that should be noted:
The sample code for this pattern is available for download.
The tentative operation is confirmed (by completion of the message exchange).
The tentative operation is cancelled either implicitly due to a fault or explicitly by one of the participants.
The tentative operation (message exchange) does not complete within expected service levels (timeout).
.gif)
Figure 2. Reservation Pattern
The Reservation ID and expiration timestamp should be part of the Confirmation Request schema, tying the reservation processing to the actual business process. The service generates both Reservation IDs and expiration timestamps for each reservation request, enabling the service to periodically review and "expire" unconfirmed reservations. This pattern could potentially be combined with the Idempotent Message pattern, further insulating the service from duplicate reservation requests.
There are some minor issues with this approach that should be noted:
The four tenets of Service Orientation provide a discrete set of fundamental principles that can guide our service development efforts. This paper provided several patterns and anti-patterns designed to illustrate how these tenets impact service designs. We also provided some additional guidelines to help ensure your future service design and development efforts will be successful:
There are many additional design issues associated with Web services. Future papers in this series will address issues such as versioning, service factoring, and policy-driven service configurations.