Export (0) Print
Expand All
Expand Minimize

Principles of Service Design: Service Patterns and Anti-Patterns

 

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.


Contents

About the SOA Design Principles Series
An Introduction to Service Oriented Architecture (SOA)
Patterns and Anti-Patterns for SOA
Conclusion

About the SOA Design Principles Series

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:

  • Microsoft Windows XP (samples are untested on other platforms)
  • Microsoft .NET Framework 1.1
  • Microsoft Internet Information Server 6.0
  • Microsoft SQL Server or Microsoft Access
  • Microsoft Visual Studio 2003 is not required but will provide a better overall experience

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.

An Introduction to Service Oriented Architecture (SOA)

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:

Tenet 1: Boundaries Are Explicit

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 physical location of the targeted service may be an unknown factor.
  • Security and trust models are likely to change with each boundary crossing.
  • Marshalling and casting of data between a service's public and private representations may require reliance upon additional resources—some of which may be external to the service itself.
  • While services are built to last, service configurations are built to change. This fact implies that a reliable service may suddenly experience performance degradations due to network reconfigurations or migration to another physical location.
  • Service consumers are generally unaware of how private, internal processes have been implemented. The consumer of a given service has limited control over the performance of the service being consumed.

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:

  • Know your boundaries. Services provide a contract to define the public interfaces it provides. All interaction with the service occurs through the public interface. The interface consists of public processes and public data representations. The public process is the entry point into the service while the public data representation represents the messages used by the process. If we use WSDL to represent a simple contract, the <message> represents the public data while the <portType> represents the public process(es). The article "Data on the Outside vs. Data on the Inside" examines these issues in greater detail.
  • Services should be easy to consume. When designing a service, developers should make it easy for other developers to consume it. The service's interface (contract) should also be designed to enable evolving the service without breaking contracts with existing consumers. (This topic will be addressed in greater detail in future papers in this series.)
  • Avoid RPC interfaces. Explicit message passing should be favored over an RPC-like model. This approach insulates the consumer from the internals of the service implementation, freeing service developers to evolve their services while minimizing the impact on service consumers (encapsulation by using public messages instead of publicly available methods).
  • Keep service surface area small. The more public interfaces that a service exposes, the more difficult it becomes to consume and maintain it. Provide few well-defined public interfaces to your service. These interfaces should be relatively simple, designed to accept a well-defined input message and respond with an equally well-defined output message. Once these interfaces have been designed they should remain static. These interfaces provide the "constant" design requirement that services must support, serving as the public face to the service's private, internal implementation.
  • Internal (private) implementation details should not be leaked outside of a service boundary. Leaking implementation details into the service boundary will most likely result in a tighter coupling between the service and the service's consumers. Service consumers should not be privy to the internals of a service's implementation because it constrains options for versioning or upgrading the service. The Anti-Patterns section of this paper provides a detailed example of this issue.

Tenet 2: Services Are Autonomous

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:

  • Services should be deployed and versioned independently of the system in which they are deployed and consumed.
  • Contracts should be designed with the assumption that once published, they cannot be modified. This approach forces developers to build flexibility into their schema designs.
  • Isolate services from failure by adopting a pessimistic outlook. From a consumer perspective, plan for unreliable levels of service availability and performance. From a provider perspective, expect misuse of your service (deliberate or otherwise), and expect your service consumers to fail—perhaps without notifying your service.

Tenet 3: Services Share Schema and Contract, Not Class

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:

  • Message interchange formats defined using XML Schema.
  • Message Exchange Patterns (MEPs) defined using WSDL.
  • Capabilities and requirements defined using WS-Policy.
  • BPEL may be used as a business-process level contract for aggregating multiple services.

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:

  • Ensure a service's contract remains stable to minimize impact upon service consumers. The contract in this sense refers to the public data representation (data), message exchange pattern (WSDL), and configurable capabilities and service levels (policy).
  • Contracts should be designed to be as explicit as possible to minimize misinterpretation. Additionally, contracts should be designed to accommodate future versioning of the service through the extensibility of both the XML syntax and the SOAP processing model.
  • Avoid blurring the line between public and private data representations. A service's internal data format should be hidden from consumers while its public data schema should be immutable (preferably based upon an organizational, defacto, or industry standard).
  • Version services when changes to the service's contract are unavoidable. This approach minimizes breakage of existing consumer implementations.

Tenet 4: Service Compatibility Is Based Upon Policy

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.

Patterns and Anti-Patterns for SOA

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.

Why Patterns and Anti-Patterns?

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:

  • Context: A brief background of the pattern or anti-pattern. Context is provided so that readers can recognize possible opportunities to either apply a pattern or recognize the characteristics of an anti-pattern before it is fully instantiated.
  • Problem: A simple statement designed to frame the objectives associated with the pattern or anti-pattern.
  • Forces: Additional considerations that must be taken into account when applying a given pattern or recognizing an anti-pattern.
  • Solution: A description of the resolution for a given anti-pattern or the steps necessary to apply the associated pattern.
  • Symptoms & Consequences: For anti-patterns, the factors that cause an anti-pattern to exist. For patterns, symptoms and consequences may address other factors or considerations to take into account prior to applying the associated pattern.

Anti-patterns include an additional recommendation on how their associated design flaws can be improved.

A Word About the Code Samples

  • Sample code for each of the patterns is available for download.
  • Each of the samples has been packaged as an installation file (MSI) and includes a README file explaining how to install and configure the samples.
  • As stated earlier, readers should exercise caution regarding the use of the sample code. Sample code is provided for illustrative purposes only. While 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.

Anti-Pattern #1: CRUDy Interface

  • Context:
    • You have been asked to design Web services for your new company SOA project.
  • Problem:
    • How do you design SOA services with .NET?
  • Forces:
    • You have been a Visual Basic developer since Visual Basic 5 and "know" how to create components.
    • This is your first SOA project.
    • Your organization expects that other platform applications will consume your service.
  • Solution:
    • You design your service interface just like you designed previous component interfaces.
    • You create a service that implements the CRUD (Create, Read, Update, Delete) operations.
    • A fragment of sample code appears in Listing 1.

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

  • Symptoms & Consequences:
    • The interface design encourages RPC-like behavior, calling Create, MoveNext, and so on, instead of sending a well-defined message that dictates the action to be taken. This is a violation of the first (Well Defined Boundaries) and third (Share only Schema) tenets.
    • Interface is likely to be overly chatty, since consumers may need to call two or three methods to accomplish their work.
    • Using a Sub for Create means that the consumer will have no idea if the operation succeeds or fails. When designing a service always keep the consumer's expectation in mind—what does the consumer need to know?
    • CRUD operations are the wrong level of factoring for a Web service. CRUD operations may be implemented within or across services, but should not be exposed to consumers in such a fashion. This is an example of a service that allowed internal (private) capabilities to bleed into the service's public interface.
    • The interface implies stateful interactions such as enumeration (see the MoveNext and Current functions).
    • Abstract types (such as the Object returned by the Current function) result in a weak contract. This is another example of violating the third tenet (Share only Schema).
    • This is a very dangerous service since it could leave the underlying data in an inconsistent state. What would happen if a consumer added a new Contact (or updated an existing Contact) and never called the CommitChanges function? As stated earlier, service providers cannot trust consumers to "do the right thing."

The risks and issues listed above can be avoided by changing the service in the following manner:

  • Change the service's interface to communicate using XML schema. The schema could also include targeted service actions (New Contact Schema or Change Contact Schema, for example). Developers should consult existing industry standards prior to developing their own schemas. A schema that meets your needs may already exist.
  • Encapsulate data manipulation behind private methods, accessible only using schemas passed to a public interface.
  • Ensure service consumers receive an acknowledgement with information indicating the status of their request.

Anti-Pattern #2: Loosey Goosey

  • Context:
    • You are building a service-based solution.
    • Your organization is very interested in service re-use.
  • Problem:
    • How do you factor service interfaces to maximize flexibility and extensibility?
  • Forces:
    • You are planning to provide integration points at all levels of the solution.
    • Other groups need access to your database.
  • Solution:
    • You design your service interfaces as Data Tiers (see Listing 2).
    • You design highly extensible interfaces by focusing on late binding.

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

  • Symptoms & Consequences:
    • There is virtually no contract. A service consumer has no idea how to use the service (for example, what are valid Command arguments, encoding expectations, and so on).
    • The interface errs on the side of being too liberal in what it will accept. The contract is both unclear and a high security risk, susceptible to a SQL injection attack.
    • The contract does not provide enough information to consumers on how to use the service. If a consumer must read something other than the service's signature to understand how to use the service, the factoring of the service should be reviewed.
    • Consumers are expected to be familiar with the database and table structures prior to consuming the Web service. This results in a tight coupling between service providers and consumers.
    • Performance will suffer due to dependencies on late binding and encoding/decoding between boundaries within the same service.

The risks and issues listed above can be avoided by changing the service in the following manner:

  • Change the service's contract to communicate using a well-defined XML schema. The schema should not expose any information about the underlying data store. The semantics of the schema should provide the context to service consumers, enabling them to understand the purpose of the service. (This approach will also improve the performance of the service.)
  • Database interactions should be encapsulated within private methods, insulating the consumer from the details of the database and its associated tables.
  • Ensure service consumers receive an acknowledgement with information indicating the status of their request.

Pattern #1: Document Processor

The sample code for this pattern is available for download.

  • Context:
    • You are building a service-based solution.
    • You want to be compliant with the SO design tenets.
  • Problem:
    • How do you create a simple to use, well defined contract that remains compliant with the SO design tenets?
  • Forces:
    • The contract for your service should encourage document-centric thinking.
    • The contract should clearly define service semantics (avoiding the Loosey Goosey anti-pattern).
    • The contract should promote loose-coupling by encapsulating the implementation within or behind the service itself (avoiding the CRUDy Interface anti-pattern). The service contract is a boundary that must not leak details about internal implementations (remember the first design tenet).
    • The service must comply with the WS-I Basic Profile, enabling it to be consumed by any platform or programming language that supports Web services.
    • The service should represent a business process as a complete unit of work (avoiding stateful assumptions such as those in the CRUDy Interface anti-pattern). As stated earlier, service providers cannot trust service consumers to "do the right thing." A service should not rely upon the user calling another service to "roll back" or "fix" things if an exception should occur.
  • Solution:
    • Define or reuse an XML schema to represent request and response messages for your service. Ensure that all public interactions with your service use these schemas. (See Listing 3 for a sample code fragment.)
    • Generate objects directly from your schemas to speed up development. This is sometimes referred to as a Contract-First approach. With Contract-First you define your service contract before developing the actual service. The service contract can then be used to generate service code. Contract-First is a proven approach for reducing the barriers to interoperability since it builds upon decades of experience (CORBA, COM, and DCE all used interface languages). Web services sometimes adopt a "Contract Last" approach because SOAP is often used without WSDL for simple solutions. Regardless, many development environments provide simple support for Contract-First while tools such as WSCF and the XSD Object Code Generator are available to help further automate this process.
    • As stated earlier, service providers cannot expect consumers to call or use their service in a specific manner. This means that using a compensating process for a given transaction is acceptable, but the service provider should not rely on the service consumer to trigger the compensation. A reservation pattern (see following) provides the most autonomous protection for transactions, removing reliance upon the service consumer.

Listing 3. Sample C# Document Processor Service

[WebMethod()]

public FindCustomerByCountryResponse FindCustomersByCountry(
FindCustomerByCountry request) 
{
   ...
}

  • Symptoms & Consequences:

    Services defined using this simple pattern will be compliant with the SO design tenets:

    • Mapping a document-centric Web service to a business process is much easier since customers tend to think of a business processes as sending and receiving documents (note that the documents may represent business events and not actual business documents).
    • The service boundary serves as a clear line for the transformation between public and private data representations.
    • The implementation details of the service are encapsulated from the consumer.
    • Adopting a Contract-First approach helps ensure that the service will be highly interoperable.
    • Document-centric services are easier to evolve since all interactions occur through the message instead of a hard-coded RPC method.

    There are some minor issues with this approach that should be noted:

    • Performance may become an issue with transfers of data from internal to external representations.
    • Service consumers must map their data representations to the schema used by the service. Some consumers may find mapping to a service's schema to be a lossy process.

Pattern #2: Idempotent Message

The sample code for this pattern is available for download.

  • Context:
    • You want to be compliant with the SO design tenets.
    • You are building a service-based, transactional solution.
    • The service must be able to compensate if multiple deliveries of the same message should occur (messages should be idempotent, for example).
  • Problem:
    • How can you ensure that messages are idempotent?
  • Forces:
    • You cannot expect anything more from the consumer that the contract that defines your service.
    • You are working with a transactional database system that requires frequent, reliable updates.
    • A reliable messaging platform may not be the total answer since consumers could still send multiple copies of the same request.
  • Solution:
    • The service contract (schema) should require messages from the consumer to be tagged with a Unit of Work identifier (hereafter referred to as a UOW ID). See Figure 1 as an example.
    • The contract cannot insist that the UOW ID is unique across time.
    • The UOW ID will represent a unit of work that will only be performed once, regardless of the number of messages that arrive with the same UOW ID value.
    • The service will use the UOW ID to determine if a unit of work has already been performed prior to starting it. There are there possible options for handling UOW IDs associated with already completed work:

      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.

ms954638.soadesign_04(en-us,MSDN.10).gif

Figure 1. Idempotent Message Pattern

  • Symptoms & Consequences:

    Idempotent messaging is a difficult issue. Each of the three options for handling duplicate UOW IDs has several considerations:

    1. Return a cached response—This option requires the service to maintain a cache of possible responses for a given amount of time. Determining time-out values/cache freshness should be determined by the associated business process. There are additional issues that must be considered:

      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?

    2. Process the message again—While this is probably harmless for simple read operations, it can be disastrous for write operations (pay off an invoice, for example). What if processing the UOW ID the first time that it was received resulted in an error? Processing it again will likely result in the same error (this is sometimes referred to as a "poison message loop").
    3. Throw an exception—In this case the service consumer may not have received the service's original response and simply re-sent the same UOW ID again (probably due to a time-out on the expected response). How will the consumer receive your response if the last one sent was lost?

    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:

  • Services using this pattern will potentially consume large volumes of durable storage to accommodate caching of responses.
  • There may be a significant impact on the service's performance due to management of the cache.

Pattern #3: Reservation

The sample code for this pattern is available for download.

  • Context:
    • You want to be compliant with the SO design tenets.
    • You have a complex business process that you want to expose to your customers with a Web service. The business process is long-running with a single transaction spanning several hours.
  • Problem:
    • How can you maintain data consistency across a long running process?
  • Forces:
    • Distributed transactions cannot be shared.
    • The business process described above requires several messages to complete.
    • The time required for message exchange varies from a few seconds to several hours.
  • Solution:
    • Messages create tentative operations—these tentative operations are atomic transactions that leave the database in a consistent state.
    • There are three possible outcomes associated with each of these tentative operations:

      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).

    • A dialog must be defined to track the state of each tentative operation. Assigning a unique identifier (Reservation ID) and expiration timestamp to each dialog enables the service to "remember" where each dialog left off. An illustration of this pattern appears in Figure 2.
    • Service consumers attempting to confirm a previously registered reservation must include a valid Reservation ID. Confirmation requests with a missing or invalid Reservation ID will are rejected by the service.
    • The expiration timestamp enables the service to "expire" unconfirmed reservations after a given period of time has passed.

ms954638.soadesign_05(en-us,MSDN.10).gif

Figure 2. Reservation Pattern

  • Symptoms & Consequences:
    • Assigning unique Reservation IDs and expiration timestamps ensures that data consistency issues are handled by the service and its associated business rules. As stated earlier, the service cannot rely upon the consumer to "do the right thing."
    • Reservation IDs are used to track the state of a given dialog.
    • Expiration timestamps are used to deal with timeouts or lost messages in a predictable fashion.

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 business rules for handling reservation requests must be clearly defined. How will "overbookings" be handled?
  • We have been somewhat spoiled by the atomic two-phase commit model and frequently attempt to apply it in places in which it is ill-suited (such as long running processes). Long running processes must preserve the consistency of the atomic processes from which they are composed. Isolation of work in a long running progress is not an easy task and a pattern such as Reservation is a simple attempt to address this issue.

Conclusion

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:

  • When factoring services, model the business processes based upon existing documents and recognized business events.
  • While it's important for service interfaces to be flexible, avoid being so flexible that the underlying service contract becomes unclear.
  • Don't expect service consumers to "do the right thing." If your service requires consumers to execute a series of steps in a predefined manner look for patterns (such as reservation) to help enforce these steps.
  • Never leave the service or its associated resources in an inconsistent state.

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.

Show:
© 2014 Microsoft