Dealing with the Melted Cheese Effect: Contracts
Summary: Services have dependencies in often undocumented and unintended ways. The second in a collaborative series that focuses on the design of Web services, this article discusses contracts as a way to describe these dependencies and make them more manageable. (11 printed pages)
Click here for the first article in the series, Dealing with the "Melted Cheese Effect."
Designing Service Interactions - from the Outside In
Conversations, Contracts, and Policies
Loosely Coupling Conversations
Designing the Interaction
The Message Document
Creating the Contract
In the first article in this series, Dealing with the "Melted Cheese Effect," I talked about the interconnectivity of systems and how that affects design, particularly when we want to make changes to the system.
I introduced the melted cheese effect. Seemingly autonomous components and services are connected much like strands of melted cheese may connect raviolis. Services have dependencies in often undocumented and unintended ways. This article discusses contracts as a way to describe these dependencies and make them more manageable.
First, I'll position contracts using a sketch of the design process.
Looking at the whole design process, and being aware that we will only seldom get the chance to go back this far in the design, it all starts with the business. It should be possible to divide the business into units, each with their own responsibilities and reasonably separate from one another. Each of these units requires IT to offer certain functionality. We can identify services to provide that functionality and we can group related functionality together to define the responsibility of the services. I like to think of these groups in terms of sourcing: can I imagine outsourcing this business unit and its services, or can I imagine handing it to a different group during a reorganization, merger, or acquisition? This leads to a grouping of services, where service interaction between the groups requires more care than within the groups.
After identifying such groups, the design continues with more detailed business goals, with the flow of activity and information through the business and the identification of the role the services have to play in this. Consequentially, the design of the service should be outside in. The first thing to identify is what the service will be used for and then how it will be used. The design of the service internals, the object design, the information design, and the database design are all a consequence, not the starting point.
After identifying the services and their responsibilities, the design process continues by adding more specificity, with the use cases and the interactions in which the service participates. This is important. Before thinking about reuse, one has to think about use. It's not so important what the service exposes; it is important how it is used.
Services have interactions or conversations with other services and with their clients. These conversations are about something, they have a purpose. The goal of such a conversation may be to drive a business process forward, to execute a business transaction, to convey information, or anything else. These conversations are formed by a sequence of messages between the partners in the conversation. We model these conversations in what we call contracts. The services together with their contracts provide us an excellent high-level view of the functionality and information flow, a map of the service landscape.
I call the party sending the first message in the conversation the client and the other party the service. When using the word client, keep in mind that the client may be another service. A contract describes the structure of such a conversation, of a conversation pattern. The interaction between a client and a service may seem symmetrical; after all, the two are just sending messages back and forth. However, the relation is typically asymmetrical. One takes the initiative and the other responds. The service typically doesn't speak until spoken to. It just sits and waits. When it receives a message, a request, it processes the request and responds. As with interfaces, the service implements the contract (Class AService: AContract ...). The client uses the contract (AContract aService = (AContract)new AService();). The client will see every contract, or more precisely every endpoint, as a separate service. It will not be able to see whether multiple contracts are offered by the same or by different services. So, from the outside, each contract looks like a service. From the inside, a service can offer multiple contracts each on its own endpoint.
Ideally, contracts describe the conversations, their interaction patterns, their messages, the information, and the semantics. Note that a contract often only refers to the description of the messages and the information as described in WSDL, since currently not even a proposal to standardize on the description of the interaction pattern exists.
A contract models the conversation between services. It always models the interaction between two parties. Any party can be simultaneously involved in multiple conversations. These conversations may or may not be related and they may or may not influence each other. A contract models the possible sequence of messages sent back and forth between them as well as the possible payloads of these messages.
Policies, on the other hand, contain all the restrictions on top of this: the communication mechanism that is to be used, the security constraints that are required, who is allowed to participate in the interaction, and so on. There are efforts under way to put supporting standards such as WS-Policy and related domain-specific specifications, WS-SecurityPolicy, for example, in place.
There is an overlap between the two. There are constraints that can be part of a contract or of the policies. We can summarize them as follows:
- A contract is everything about the behavior of the service that is engraved in code. They are drawn up at design time.
- Policies are everything that can be changed without coding. They are typically detailed later in the process and they are easier to change.
- The contract is the domain of the application designer. It describes the interaction between the client application and the service application.
- Policies are the domain of the infrastructure designer. They describe the interaction between the client infrastructure and the application infrastructure.
Keep these responsibilities apart.
Keeping in mind the current standardization of Web services, a contract consists of the description of methods in a service contract and the description of all parameters used by these methods in a data contract. Most of these parameters are business documents or business entities.
As I discussed in the first article, "Dealing with the Melted Cheese Effect," the service should offer an interface and the client should bind to that interface. The Windows Communication Foundation, WCF (formerly Indigo), makes the same recommendation to define the behavior using an interface. By setting the ServiceContract attribute on that interface and decorating the methods with an OperationContract attribute, it becomes the service contract. WCF also offers a ChannelFactory that allows the client's programmatic binding to an interface rather than to the implementation in the service.
Various types of clients and services may use the same service. For instance, the sales system is used by the call center as well as by the sales team, and it interacts with the customer relations management (CRM) system. Together, this accounts for just a very small subset of the interactions. So, when services are often used by multiple types of clients and a change is made to the service, all those clients need to change as well, don't they? Do we even know which parts of the service are used? Do we know which clients use which parts? Do we know whether those parts actually did change?
Unknown dependencies present the tightest form of coupling.
Contracts help make the dependencies between services and their clients explicit. A service can implement multiple contracts and a client can consume multiple contracts. Each contract describes a particular set of dependencies. They offer a good foundation to track these dependencies.
We believe it's useful to have separate contracts for each conversation pattern.
The same method may be offered in different contracts and therefore in different interfaces of the same service. Having multiple contracts decouples the conversations. One contract may change, while the others remain unchanged. The clients of that one contract may need to change as well, while there is no need to update any of the others.
The first steps to defining a contract are to identify:
- The conversations in which the service will have to participate.
- The interaction patterns.
- What the individual messages should convey.
- What information or entities are passed.
Then during the further design process, each of these will become more concrete. This is clearly an iterative process. From there we recommend you define a contract for each conversation pattern a service has.
A service may work on reaching a specific business goal, such as negotiating or accepting an order, fulfilling an order, or managing a customer complaint. A service may be involved in an exploratory communication. For instance, what are the most profitable outstanding orders? Who are the customers involved? Who is the account manager? A call center application may have conversations with our ECommerce service about customer creation, order creation, or order inquiry. We may treat these as different conversation patterns and model them separately.
Most contracts will describe the interaction between one particular service and one particular client, but many contracts will describe the interaction between a set of clients and a service, e.g. the contract for general ledger postings is used to integrate invoicing, payment and incoming goods. The more clients, or partners in the conversation, or if there are clients external to the organization, the more important it is to reduce the dependencies and therefore to invest more in designing better contracts.
How many contracts should a service expose? We don't know.
How many operations should a contract describe? We don't know that either.
If we define too many contracts, we have a manageability problem and we introduce overhead. If we define too few, we lose our grip on the dependencies. We suggest a few things to look at when deciding on the contracts to define.
We should be able to identify the topic of the conversation and we should be able to give the contract a name.
If you can't name it, you probably don't know what it does.
This is a golden rule that applies at almost every level of design.
If there are multiple clients for the contract, their requirements should be similar enough to capture in one contract.
The clients of the contract should use the whole contract and not just a part of it. If one method in the contract changes or if one data schema changes, the client is affected regardless of whether that part is used or not. Therefore, the contract should be a logical entirety.
If we define a single contract for each service, we still haven't gained much. Since we can define multiple contracts for a service, we can organize these contracts by conversation pattern. This way, we have a reasonable chance that a change in one type of conversation does not have to change the others. In other words, by defining a contract per conversation pattern, we couple conversations more loosely.
In some cases, authorization is another good consideration for defining multiple contracts. Often one role is allowed to retrieve specific information and execute specific actions that another role may not. In the past, I have written filters to filter out what the user wasn't allowed to see and to intercept calls the user wasn't allowed to make. This was sometimes complicated code and it was always a lot of configuration. Another drawback was that the service would return sparsely populated XML so that making correct and useful schemas was much harder, especially if the results were to be used by generic code such as Microsoft Office Information Bridge Framework (IBF). An alternative is to define interfaces for specific roles and to configure access to contracts rather than to individual methods and schema elements. This may be useful if conversations are role specific and identifiable at design time. This is less useful if the differences are more granular or when access rights are likely to be set and changed by administrators during the lifetime of the services.
There is a lot of discussion about the granularity of messages, but the question should not be whether messages are big or small. The question should be whether the message is needed or useful and whether or not it contains sufficient information. Sometimes a small message suffices, such as when validating a credit card. Sometimes a huge message is required, such as when a subsidiary orders 6000 items from the head office. Of course, we want to avoid chattiness because the number of messages has more impact on the performance than the size of the messages. Thus, setting properties using a number of calls is obviously a bad idea; after all, this is about communication with a service and not with an object. Combining multiple messages into a single message may boost performance. An example of the latter would be sending a batch of offline requests to speed up the synchronization process.
Another constraint is that the contract should not expose too much of the inner workings of the service. In general, that means that the contract should describe business functionality and not technicality or implementation. A message may refer to an order, but not to a session; to a user, but not to a service; to a business transaction, but not to an ACID transaction; to an amount in some currency, but not to a float or a BCD (binary coded decimal).
Of course, there are different types of services and some services offer technical functionality. Depending on where the contract is in the hierarchy of services, it is more or less important to provide pure business functionality. The higher up in the hierarchy—for instance, workflows or other business processes and the services called by those workflows and business processes—the more important it is to provide business functionality and hide any technical details. Lower in the hierarchy of services and components, technical functionality may be required and exposed. For example, the database service and the authentication services will not understand customers and orders. However, the services offering business functionality ought to express their contracts in business terms.
When designing contracts, it is helpful to follow a simple rule: If I can't explain the contract to the business people, it's probably not good. This is true for everything that makes up the contract. I should be able to explain the sequence of the messages as well as the content of the messages.
For instance, can the people in the department working with my service understand:
- Why we need the message?
- All the information contained in the message?
- What responses are possible?
- All the information contained in the response?
- What the potential exceptions mean?
- Can the users provide all the information in the message?
- Will they expect the response?
This set of questions automatically makes it less likely that the internals of the service are exposed. The internal codes and algorithms will have a more technical nature and are not easy to explain to the people executing the business. This would not exclude the use of enumerations in the schemas. If the enumerations map to terms the users can understand, a translation mechanism would expose the meaning of the enumeration. Such a translation can be cached to make the communication more efficient and language-independent.
There is not yet much tool support for the design of the interaction, nor is there a standard. It is possible to use plain English to describe the interaction and that is infinitely better than no description at all. UML sequence diagrams offer a good and more formalized mechanism. However, the steps from here to interfaces, implementations, and schemas are still mostly manual.
Some may see BPEL4WS as a way to define contracts, but we believe that it is better suited to describe a process. Processes interact with many services and therefore interact through many contracts, so there is a clear relationship. We would argue that BPEL4WS is better suited to design or implement the processes using the contracts.
On the bright side, there are some tools on the market today that take a stab at managing the consistency between contracts defined using those tools and the BPEL processes. We believe that more tools will be emerging soon.
Not that far back in history all business was recorded on paper and, even today, many processes are still executed on paper. Government agencies are especially known for their use of forms. People fill out forms and send them to other people who then take subsequent action. The paper processes may be slower than their automated counterparts, but they typically scale very well and they are reliable, too.
When designing service-oriented systems, I like to compare their interaction with interactions between people using paper. Tellers in a bank, for instance, work with customers, handle money, accept new customers, and deal with address corrections or changes; or at least they used to. For each of these requests or actions that would typically happen, there was a standard form and that form would be transported to the back-office for further processing.
Dedicated personnel would collect the forms and take them to the back office. In the middle of the twentieth century and even today, this is still the case, although pneumatic tubes are used to transport the information, as well. Such forms do not just contain the old information and the new information; instead, they contain business requests that have been designed ahead of time and each of these requests makes it very clear what the request is. For example: is the customer moving or did the phone number change? These two requests lead to very different business processes.
Sometimes a simple form may not be enough and the request takes the form of a folder, but the principle remains the same: the supporting information is bound to a clear request. The teller may or may not know where the form is going, who is going to handle it, or what the transport mechanism is. It's not part of the form itself. It's part of the message and the messaging infrastructure. Forms of one type may go into one tube and forms of a different type into another. Forms may be sorted into different boxes, or the recipient may look at the form and start the appropriate action or hand it to someone else. It is almost like a service-oriented system. The method is the request and it maps nicely to the name or type of the form. The content of the form maps to the payload of the message and to the data classes passed as arguments of the method.
The message contains the request as well as the supporting information. The infrastructure interprets the message and takes care of delivery at the correct end-point. The request and the service's internals are oblivious to the routing that took place, to encryption, authentication, and so forth. The request directly maps to a method on the end-point and thus to a method on the interface implemented by that end-point. The data contract models the supporting information.
The infrastructure and the business logic change at different rates. The business logic need not know the details of routing, encoding, and encryption, but sometimes it needs to execute authorization, especially if the authorization rules are complex or are part of the business logic. So, is it a good idea then to define the caller's credentials in the data contract and thus to pass them as a parameter? I would prefer not to.
I advise separating the method and the payload from the infrastructure.
I'd prefer that the infrastructure passes the credentials and provides programmatic access to the caller information, for instance in the call context. This allows us to change the authentication method or even to support multiple methods without affecting the service itself. This means that I would like the designer and developer of the service and of the client to be responsible for the interaction, the methods, and their parameters. If they need access to infrastructure, they should use the infrastructure. If they need the user's name or role, they can call the infrastructure. I want the infrastructure designers and developers to be responsible for everything that happens between calling the method and implementing the method. If the infrastructure wants to use the header of the soap message, great. If they want to use the transport layer, that is great too. (Just don't tell the application designer.)
Soap headers are exclusively for the infrastructure. The application designers should stay away from them.
Today, the WSDL, with its embedded schemas, is the most important physical result of the design effort; it is the one artifact that can be used by both sides to steer the conversation. As we have seen in the previous paragraphs, the current schema standard does not support all of the design decisions, but it catalogs many important design details.
I cannot avoid entering the (sometimes religious) debate whether to design the schema first and then derive the code or to write the code first and then generate the schema.
In either case, you will have to look at both code and schema and iterate between them. Make sure to look at the code in all languages used, and make sure that the serialization and deserialization works in both directions and leads to the expected result. For any large project with Web services, it is highly recommended to have someone who understands the WSDL.
The result of the detailed specification of the contracts is a set of WSDLs. Of course, it could be the starting point, as well. It is possible to use WSDL and XSD all the way. You capture the data contract in XSD. Then you import the XSDs into the WSDL and add the interface descriptions. From these you generate the data classes as well as the endpoints.
This is a good and recommended approach. However, there are a few pros and some important cons to consider.
On the plus side,
- This approach gives you complete control over the contract. This is very important in these cases:
- When multiple organizations are involved and change management is hard.
- When integrating between multiple environments, such as C#, XML, and Java.
Guidelines for this approach can be found on the Internet: Good starting points may be:
People following the discussions on the Internet will find this a natural approach.
The downsides are the following:
- It requires skilled people who understand WSDL and XSD and can design and maintain your contracts. The larger the projects, the more people with such skills are required.
- With the current toolsets this is tedious and error-prone work.
If you want to follow this approach, you will probably like the WSCF (Web Services Contract First) toolset to counter at least the second of these.
I have worked with many customers, large organizations, who would prefer a different approach because they don't have enough people with the required skill set, or simply because they believe the schema-first approach is too expensive.
In this approach, too, the contract is the starting point and the WSDL is the resulting detailed specification. The difference is that the source code is used to generate the schema. The data contract is made by designing classes in C# or any other language and the mapping to XML is defined using attributes. This means that the data contract can support any feature that makes using the data easier. Data binding, sorting of collections, and other capabilities can be added. It requires some discipline to enforce consistency, though.
It is possible to implement the behavioral contract directly in the code behind an asmx file. We are not proponents of doing this. We recommend defining an interface instead and implementing that interface in the code behind. The interface may then be reused. I often have one or more separate assemblies with the interfaces and the data classes. If the client uses the same language, I will reuse these assemblies on the client side to create a proxy that exposes the same interface.
Many developers prefer this approach because code is much closer to their daily work than schema is.
These are the main disadvantages of this approach:
- Defining data classes by hand is a lot of work and changing the definition often requires change in multiple places.
- It requires some understanding of the schema to select the appropriate serialization attributes for all the data elements.
- The compiler does not check the placing of correct attributes on all the data elements.
These disadvantages will be reduced, although not entirely eliminated, with the arrival of WCF.
We believe that trying to get the best of both by mixing the two approaches may work well, which may look like this:
- Model the data using the schema editor in Microsoft Visual Studio. The XSD-schemas are part of the Visual Studio project. Use a custom tool to generate the data classes automatically from these and to keep schema and code always synchronized. I use XsdObjectGen) for this.
- Define interfaces in C# or any other CLR language and handcraft or generate the asmx code-behind files from these.
- The system ultimately generates the WSDL from the code.
The alternative solution is to define a separate project for the data schemas and to generate the classes from this schema repository.
We are working on a set of tools to support both approaches. This set of tools can be found on our Web site (GotDotNet or Thinktecture).
Designing the data contract in XSD is not simple, and we are considering building a tool to show the structure of the data and hide the complexities of the schema.
I must confess that I have had and continue to have many discussions with my coauthors on the subject, even though we agree on the fundamentals. We do believe that it's not about schema first or code first. We believe it's about contract first and that one of the outcomes should be an adequate schema.
In our opinion, the discussion about schema first or code first is about which tools are most suitable for the design of messages and entities. With a good complete set of tools that allow the design of the interaction patterns, the messages, and the entities, and that would allow generation of schemas and code in various languages, the discussion would be moot. I know that I'm making a controversial statement if I say that we believe that a very good toolset would not require the designer to look at the schema ever, although it would support looking at it. The schema is for the runtime to interpret, not for humans. In many cases, you are satisfied to know that the tool defines a good data format.
Do a client and a service need to share the class definitions? Clearly not! .Net and Java can interoperate, but clearly do not share classes. Even in a homogeneous environment, there may be many reasons to have different class implementations between client and service. One may require data-binding, the other may not; one may require simple addition and removal of elements to a collection, the other may not; one may be used in a single user environment, the other may have to be highly scaleable and may have to be reentrant.
Do they need to share the same schema? Yes, clearly! Or, not so clearly? They must understand each other's information and therefore there must be commonality between the schemas, but one may have a newer version of the schema, as long as the schema is compatible. But this detracts from the point I really wanted to make: the schema depends on the technologies used. In a pure .NET environment, or a pure Java environment for that matter, we could, and probably would, optimize our schemas for that environment.
We would like to define the data contract at a higher level of abstraction. We would like to define what information we would like to send and receive. Then we would like to have tools that generate the classes most suitable for the target environment and for the target requirements and that would generate schemas most suitable for the technologies used.
We do not know exactly what this higher abstraction will look like, but we do believe that it is the future direction. We also believe that WCF will move us in that direction.
We believe that something similar will happen with the behavioral contracts, and that definition and tooling will become available to define the interaction and generate the artifacts such as class interfaces and WSDLs.
Until that time, we believe that it's mainly a personal preference. Simply put:
- If you understand WSDL and XSD inside out, you may want to start with schema.
- However, if you're not an expert, we think that starting with schema may be an expensive proposition and it may be wiser to start with code.
Roughly half of the service-oriented world provides contracts for the other half to consume. This may not be entirely true, although I do not dare to make an estimate of contract reuse and sometimes these halves overlap. In many B2B scenarios and even in many inter-departmental scenarios, one party designs the service and another designs the client. Many others own both endpoints and control the contracts and the changes to them on both sides. Yet many others work on standard contracts like those provided for the health care industry. The more distant the designer of the service contract and the client of that contract are organizationally, the more negotiation it will take and the more expensive it will be to change that contract. The closer they are organizationally, the easier it is to manage change. Contract design is complex. Design contracts for the required organizational distance. With the current tool support, schema first is probably your best bet when service consumption crosses organizational boundaries or when interoperability is important. Otherwise, when the organizational distance is small and especially if you own both sides of the interaction, you may want to consider what I called the mixed approach.
In all cases, our recommendation is to design and describe the interaction as well as the expected business behavior. The schema in the form of WSDL is only one artifact.
The next article will go into the design of a simple set of services and the effects some design decisions may have on functional change.