Decisions to Make
Applies to: Windows Communication Foundation
Published: June 2011
Author: Greg Cowin
This topic contains the following sections.
- Decision: WSDL First or Code First
- Decision: How Will You Design and Organize Your Services?
- Decision: How to Communicate Errors
- Decision: Use a Security Token or a User name and Password
- Decision: Is the Security Token Embedded in the SOAP Header or Is It an Explicit Parameter?
- Decision: Whether to Use XML for Parameters and Results
- Decision: How to Avoid Breaking Existing Consumers
Many existing WCF services are inconsistently designed, difficult to use, and have security and operational issues. One way to prevent these problems is to make the right decisions before you begin a WCF project. WCF allows you to connect systems in many different ways. This article, which presents a WCF decision framework, will help you to select the options that are best for you, and to deploy a well-designed set of services.
A decision framework presents a simple, yet potentially powerful, sequence of decisions to make and actions to take. Use the framework that is presented in this article as a starting point for your own. You and your team can provide additional alternatives, and add new elements, as you evolve your portfolio of services.
Hopefully, you can discuss these decisions and actions with your team, and pick the alternatives that help you to converge on the right solution. WCF is the best framework for implementing SOA (Service-Oriented Architecture) based services. By making the right decisions, you can create a flexible and adaptable service that can be used in the enterprise, for mobile applications, and as integration points for partners and customers.
In a competitive world, you need to make good decisions and do the right things.
This section discusses the decisions you and your team should make when you begin a WCF project. Alternatives and recommendations are included to help you see the different possibilities, and to choose the options that suit your requirements.
Each decision has the following sections:
Description - This is a description of the problem.
Alternatives - These are some of the possible solutions to the problem, with their pros and cons.
Recommendation - This is the recommended approach.
Be sure to talk over each alternative with your team. Try to consider alternatives that are not included here. If you follow this procedure, then whatever you decide will be easier to follow and implement, even if not everyone agrees. At least everyone will be able to accept and understand the decision because they all took part in the discussion.
The initial question is whether you should first create the Web Service Description Language (WSDL) and let it generate the service contract interfaces, or should you first code the service contracts, and let them generate the WSDL. WCF supports both options. If you have already developed a WCF project, then you may have a preference, and can probably omit this step. This decision often becomes a point of discussion for new WCF projects.
Here are the advantages and disadvantages to each approach.
WSDL First – In WSDL-first development, you create the WSDL, and then use it to generate the code for the service contracts. In the past, with the ASMX framework, the SDK (Software Development Kit) provided a tool, Wsdl.exe, which accepted a WSDL file and generated stubs for the service. Today, with WCF, use the Svcutil.exe tool. It generates the WSDL based on service and data contracts that you specify in XML. For a good overview of WSDL-first development, see the MSDN blog post at http://blogs.msdn.com/b/dotnetinterop/archive/2008/09/24/wsdl-first-development-with-wcf.aspx. Another useful article is "Schema-based Development with Windows Communication Foundation" at http://msdn.microsoft.com/en-us/magazine/ee335699.aspx.
The WSDL-first approach gives you precise control of the WSDL.
If you must create and edit large amounts of WSDL before you generate code,then the development velocity slows.
Code First - In code-first development, you begin with a service that has the appropriate ServiceContract, ServiceOperation, and DataContract attributes, and then generate the WSDL. Most WCF examples and tutorials use the code-first
The code-first approach is easier and faster than the WSDL-first approach. It isthe more accepted approach of the two, and there are more online resources available to help you.
The code-first approach gives you less fine-grained control of the WSDL.
Use the code-first approach, except when you must support a particular WSDL contract exactly. The rationale is that most people find it easier to write a five or six line service contract in C# than to write one hundred or more lines of obscure XML. There are tools available that can help you to code your service and data contracts. It is much more difficult to first specify the contracts in WSDL, and then generate and regenerate the representation. Instead, let Microsoft® Visual Studio®, or the .NET Framework SDK, generate the corresponding WSDL. In practice, most teams use the code-first approach for one reason — it is faster.
How you design and organize your services is the most important decision in any WCF project. You need to know what each service will do, and you must know how to group those services into blocks and projects.
A service block (or service category) is a logical set of services that pertain to a particular area of the application, such as security, notification, scheduling, or accounting. You can assign the development of a service block to an individual or to the team. If you are only implementing one or two services, then how you group them may not matter. If you are developing services for an existing application, or if you are working with a large enterprise-scale system, then this decision is critical. The way that you partition services determines how easy it will be to support and deploy them, not only now, but also in the future. Here are some guidelines for partitioning services into service blocks.
Avoid a monolithic set of services in your projects.
Keep the service blocks loosely coupled by reducing the dependencies between them.
Partition services so that individuals or teams can work on different service blocks concurrently with help from loose coupling.
Make the consumption of these service blocks easy and logical.
Provide a documented service portfolio.
One way to approach the design and organization of services is to use the subject area as the driving principle. Another approach is to use scenarios as the driving principle.
Using Subject Areas
The subject-area approach starts with a logical set of service categories, and then defines services for each of the service categories. Logical categories or subject areas that are part of a schema are often good candidates for service categories. Vertical service blocks are frequently based on categories, or subject areas, in a database schema. Begin with the subject area, add the domain logic, and then expose a thin service layer that can be consumed. Follow these design steps for each service category:
Use Unified Modeling Language (UML) models to define the services. Model them with stereotypes that are based on the subject or domain areas.
Use these models, as well as scenarios and use cases, to drive the initial development of the services.
Review the models with domain experts.
Conceptually test the categories with scenarios and use cases.
When you review the initial results, ask yourself the following questions:
Does the subject area provide a logical block for some services?
Is the service block loosely coupled to other service blocks?
Does the service block constitute a logical collection of services?
Do the developers on your team understand why the services were partitioned into those service blocks?
Another approach is to use scenarios to drive the design of your services, and then to use logical areas to group those services. Just as test-driven development produces well-designed APIs, so does scenario-driven development produce well-designed service blocks.
Use subject areas for larger projects that have established schemas. It's always helpful if you have a good schema already in place. Scenarios are better suited to "greenfield" projects, where there are no constraints imposed by an existing application. Ultimately, keep it as simple as possible. It is important to get the design of services right. Services are not as forgiving, from a refactoring point of view, as other .NET development projects.
In addition, you can consider using a hybrid approach. Use the subject-area approach to develop a set of service categories from the top down. Then, develop the actual services from the bottom up; collaborate with the consumer of your services. Alternatively, you can use scenarios, use cases, or even user stories to drive the design of the services.
It is important that you effectively communicate errors to a service's clients. Clients must know what action to take when there are failures and exceptions. If you do not make an actual decision on how to communicate errors, then the result will be a portfolio of services that have an inconsistent, or even a nonexistent, approach toward error handling.
This decision is a challenging one because it always involves multiple tradeoffs. For example, if you use the Simple Object Access Protocol (SOAP), you must determine whether your client supports SOAP faults, and if that client can turn SOAP faults into exceptions.
The two things that you can count on in a highly distributed computing environment are latency and failure. All errors, including application errors such as authorization, validation, and business rule errors, need to be communicated. You must provide clients with the information they need to recover from failure. If services communicate errors consistently, then it is easier for clients to provide a mechanism for recovery.
Two ways to communicate errors to clients are to use SOAP faults or to use result objects.
Using Soap Faults –A SOAP fault contains the fault type and the fault description.
One advantage to using SOAP faults is that the WSDL contains the type of fault that is declared in the service's interface definition. In other words, you formally specify the fault as a part of the WSDL. Another advantage is that some clients handle SOAP faults as exceptions, which is a better way to handle unexpected errors.
Using Result Objects – If you use a result object to communicate errors, then create a data contract class for the result object, and use that class or a subclass, for all of the service operations' return values. For example, you can create a ServiceResult class and have every service operation return either a ServiceResult object or an instance of a class that derives from the ServiceResult class. The ServiceResult class communicates all exceptions except for timeouts and communication exceptions, which still arrive as SOAP faults. If you use result objects, you generally do not need custom faults because most of the result statuses are captured as a part of the ServiceResult. The following figure illustrates a class diagram of the ServiceResult class, with its possible result statuses.
The advantages to result objects are that they are easy for clients to handle, theywill work with other service serialization behaviors, such as JSON, and they followa request/reply pattern. It works with SOAP-based and non-SOAP based services.
The disadvantage to result objects is that they are an outmoded way to handlefailures such as exceptions. Result objects go back to at least the C programminglanguage where almost every function returned a result code. Today, errorresults are usually communicated via exceptions.
Generally, use SOAP faults to communicate errors if your clients support them. Be sure to verify that assumption. Exceptions are certainly a more modern way to deal with unexpected errors. If you support technologies such as Silverlight 2 and other, older, types of clients, use result objects to communicate errors. Silverlight 3 and 4 support SOAP faults.
Service calls generally require that the caller of the service be authenticated. Most systems support only a few services that allow anonymous access. The implication is that you must provide some form of authentication. One choice is to use an authentication service that returns a security token after it validates the caller. Another choice is to pass a user name and password and authenticate them for every call.
Here are the considerations for security tokens versus user names and passwords.
Using Security Tokens - There are authentication services that return a security token after a caller is successfully authenticated. Subsequent calls to the service pass the security token. A security token generally expires in a short amount of time.
This is probably the most commonly used approach today. A token service is required. Your team can write its own token service, or you can use a commercially off the shelf solution (COTS) such as the Active Directory Federation Service (ADFS).
The security token approach is consistent with current security standards, and is intended for modern and emerging technologies such as ADFS.
This approach requires a token manager or service.
Using User Names and Passwords - This approach passes the user name and password in each call to the service. An advantage to this approach is that it is totally stateless. However, it requires that every call be authenticated (make sure that the user name and password are correct).
This approach is stateless, and requires no token service.
There are several disadvantages. One is that each operation must authenticate the principal. Also, each operation requires the user name and password in order to perform the authentication. This approach is a bit outdated because it sends the actual password, which could be compromised at some point with a brute force attack.
Use a security token whenever possible. The authentication service provides you with a security token when you provide it with valid credentials. The security token, or identifier, will be stored with the security schema. You do need a centralized security token service. Security tokens are a more modern way to deal with authentication and they are designed to work with ADFS and other single-sign-on mechanisms.
If you use security tokens for authentication and authorization, then you should decide if the token will be included in the SOAP header, or if it will be an explicit parameter in the call to the service.
Here are the considerations for placing a security token in the header, or making it a parameter in the service call.
Embedding the Security Token - The first choice is to embed the security token in the header of the SOAP envelope.
The advantage to this approach is that a handler can process every message to ensure that the principal associated with the security token in the header is valid.
The disadvantage to this approach is that anything that is in the SOAP header is really an embedded parameter. When other service behaviors are used such as JSON serialization, it is awkward and sometimes not possible for the client to process the header.
Making the Security Token a Parameter - The second choice is to make the security token a parameter. Typically, it is the first parameter in the service call.
This approach ensures that all of the parameters are explicit and clear to all consumers. It also works well with all types of clients and bindings.
One disadvantage is that you need to ensure that the token is included in the appropriate service calls. The other disadvantage is that each service operation must validate the security token.
Consider using the security token as a parameter. The essence of a service-oriented approach is to separate the interface from the implementation. Embedding the token within the header implies a particular implementation. For example, you might eventually deploy the service with another binding that does not have the concept of a header. This is true of JSON, which is widely used with mobile applications. However, if you are certain that you will only use SOAP, then you should embed the token in the header.
You can use XML-formatted strings for parameters and result objects. To do this, you pass a string parameter and parse it as XML within the application. This approach is fairly popular because it does not require you to update services and the associated WSDL when the schema of the XML data changes. Web service clients in Flash seem to have increased the use of this idiom. Some of the Flash controls, such as the tree control, require that parameters be given as XML strings.
The two choices are either to use XML, or to use data contracts and data members.
Using XML – Generally, developers who use XML as a parameter or a result pass it as a string.
The main advantage is that you do not have to change the WSDL when parameters and results change.
The disadvantages are that this is non-standard approach, and the client must parse the XML because it is not a part of the WSDL.
Using DataContract and DataMember – The alternative approach is to implement data contracts for every parameter and result by using the DataContract and DataMember attributes. For more information about how to implement a data contract, see "Using Data Contracts" at http://msdn.microsoft.com/en-us/library/ms733127.aspx.
This approach is the standard one, and a best practice. It also provides better performance. Finally, clients do not need to parse XML strings.
You must decorate classes that are used in parameters and results with the DataContract attribute, and class members with the DataMember attribute.
In reality, if you use XML as a parameter or result, you still must deal with a schema and its changes. You also have more complexity because you must manage the schema of the WSDL, as well as the XML schemas. It is an illusion to think that by using XML you avoid the issue of schema migration.
The recommendation is to use DataContract and DataMember attributes whenever possible. In almost every case, clients are better suited to this approach.
You should avoid making changes to your services that break existing consumers. A breaking change includes any change to an existing service operation or data contract. Breaking changes also include changes to the binding. Breaking changes do not include adding a new service operation or a new data contract because the consumer will be unaware of them. It is true that some clients that use relaxed versioning allow optional data members to be added, but it is recommended to treat this as a breaking change unless you know that all of your clients have this capability. Service versioning may be necessary if you do make changes that break consumers. Note that, even if you control the deployment of the consumers, you still need to make sure that you do not break them if you deploy a major change to a web service.
Here are a few alternatives for dealing with breaking changes, and for versioning service contracts.
Deploy Services and Consumers Concurrently – If you control the deployment of all of the consumers of a service, you can generally avoid versioning by deploying the services and the consumers at the same time. Naturally, you must refresh the service references in all of the consumers, and resolve any issues before the deployment.
This approach avoids the overhead of creating new contracts or endpoints. It does not break existing clients.
In cases where you do not control the deployment of the consumer, the client application has no way to migrate to the updated contract. Also, there is no realistic way to support third-party consumers.
Create a New Contract – A second approach is to create a new contract that has a new name and namespace combination. Remember that the name and namespace identify the contract. You cannot change the contract without changing the client. A variant of this approach is to include the version number in the contract name.
This approach enables you to support multiple versions of a service that share the same codebase. It does not break existing clients.
There are two disadvantages. One is that you must duplicate some code in order to support multiple versions of the service. You also need to create data contracts for each version; otherwise, the new contract could make breaking changes to the data contracts too.
Create a New Endpoint - The last alternative is to change the contract, provide a different implementation of the service, and deploy it to a new endpoint.
A new endpoint enables you to support multiple versions of the service.
You must maintain a deployment for each version of the service that you support. This means that you must maintain at least two versions of the contract. The implication is that you must either branch your codebase, or maintain additional codebases.
Another disadvantage is that all codebase branches and deployments must be updated whenever any external systems, such as databases, change. If older versions are still deployed, you must ensure that you do not make schema changes that break them. This is why you may need to branch the older codebases, as well as make sure that you keep the older versions up to date. All of this maintenance can make it difficult to avoid making breaking changes. You may have to carefully sequence the changes, both to the services and to the schemas. For more information, see "Service Versioning" at http://msdn.microsoft.com/en-us/library/ms731060.aspx.
If possible, avoid multiple versions of a service. If you do need multiple versions, consider giving each of them a new contract. Whatever you do, avoid breaking your service consumers.