Developer Product Briefs

Get Acquainted With SOA and Indigo

Learn about the core principles behind Windows Communication Foundation's service orientation (SO), so you can better understand and create service-oriented applications with Indigo.

Get Acquainted With SOA and Indigo
Learn about the core principles behind Windows Communication Foundation's service orientation (SO), so you can better understand and create service-oriented applications with Indigo.
by David Pallmann

November 18, 2005

Technology Toolbox: C#, Windows Communication Foundation

This article is excerpted from Chapter 1, "Microsoft Code Name ?Indigo,' the Microsoft Runtime for Services," of David Pallmann's book, Programming "INDIGO," with permission from Microsoft Press [2005, ISBN: 0735621519, www.microsoft.com/learning/books/]. It has been edited for length and format to fit the magazine. You can read a PDF of the full chapter here.

The drive to connect people, organizations, and devices in new and better ways seems to be never-ending. In the business world, enterprises are demanding seamless communication with their partners and customers without sacrificing essential features such as security, transactions, reliability, and performance. At the consumer level, connectivity is finding its way into everything from wristwatches to automobiles. Distributed systems are becoming the norm. Simply put, the modern application is a connected application.

In response to these trends, connectivity has moved to center stage in software development. Service orientation (SO) is an approach to software design and development that champions this view. In SO design, message-oriented programs called services are the building blocks from which solutions are created.

Windows Communication Foundation (WCF), which was code-named Indigo, is the Microsoft service-oriented communication infrastructure and programming model. You can think of Indigo as the Microsoft runtime for services.

Designing and building good software is somewhat like wearing bifocals. It's important to focus on the big picture first and get that right before examining the finer details of the solution. Architecture is all about figuring out that big picture by separating the important parts of a solution from the less important parts. In service-oriented design, architecture has communication at its heart.

The change in focus makes sense because of the prominent role connectivity is now taking in applications. It's hard to think of an application today that doesn't talk to something else. Everyone and everything needs to be connected, including people and organizations and their devices and data centers. This means communication needs to be a primary design consideration, not an afterthought.

Why do we need a different approach to software development when object-oriented design and programming have served us so well for decades? In one sense, we don't: Service orientation (SO) is a complement to object orientation (OO), not a replacement for it. Object orientation remains important in software development, but objects aren't the best way to tie together the programs in a distributed solution.

The primary distinction between SO and OO is in how you view applications. In a solely object-oriented approach, an application is a tightly coupled collection of programs built from class libraries that have dependencies on each other. An SO application is a different entity entirely, composed of loosely coupled, autonomous service programs (see Figure 1). SO plus OO is a winning combination. You use message-oriented services to build a distributed solution, and you create those services with object orientation.

Service orientation includes four basic tenets: boundaries are explicit; services are autonomous; services share schemas and contracts, not classes and types; and compatibility is policy-based. A true service-oriented solution follows all of these tenets faithfully. Let's see what they're all about.

Boundaries are Explicit
In SO, services interact by sending messages across boundaries. These boundaries are formal and explicit. No assumptions are made about what is behind boundaries, and this preserves flexibility in how services are implemented and deployed. Modeling services in this way allows them to be distributable, meaning they can be deployed anywhere, and composable, meaning they can call each other freely. Managing large, complex projects becomes simpler when formal boundaries are explicit.

Crossing a boundary can be expensive. If services interact across an implementation boundary, a trust domain boundary, or a geographic boundary, there might be a price to pay in terms of processing overhead, performance penalties, or communication costs. For this reason, SO urges discipline in what is exposed to a boundary. It's important to call services only when necessary and to do so as simply and efficiently as possible.

When you use objects instead of services to create a distributed system, little distinguishes a local object call from a remote object call: The whole idea of distributed objects is to make remote objects look like local objects. This can lead to an overuse of calls to distributed objects, where the expense is not obvious to a developer. In SO, you can't fall into this trap: Accessing a service is distinctly different from accessing a local object.

Distributed systems must tolerate change. Some systems are built in incremental stages, and services come online slowly over time. Any system might be subject to new requirements that result in the addition, upgrading, or removal of services. It should be possible to change a service without disrupting the overall system.

In SO, the topology of a distributed system is expected to change over time, and its services are designed to be deployed, managed, and versioned independently. There's no controlling authority to manage these changes, so services have to be robust enough to survive when other parts of the system are offline. Even though a service might depend on other services to perform its function, it should not fail when they are temporarily unavailable.

In a distributed object solution, the parts of an application often have to be deployed as an atomic unit, which can be terribly inconvenient in a production environment. Making a change to an application might require the entire solution to be rebuilt, redeployed, and restarted. Upgrading a server can require upgrading all clients. In SO, modifying a service's implementation doesn't cause a problem for the other services because they're loosely coupled.

It's important to keep in mind that services share schemas and contracts, not classes and types. SO maintains implementation independence between services by being careful about what they exchange. Services use agreed-upon schemas to pass data and contracts to specify behaviors. They don't pass classes or types. Services have no knowledge of each other's execution environment—and have no need to know—because the information they exchange is completely platform-neutral.

Contrast this to a distributed object solution, where types and classes are passed across end points, forcing the same type system on all parties. This makes for a tightly coupled system and holds all parties hostage to the same execution environment.

Services should expose data and behavior relevant to their capabilities, not their implementation. Business actions and business documents are likely to remain stable. A service's interface will be equally stable if that's what it models.

Compatibility is Policy-Based
Services don't just blindly access each other. They need to determine areas of compatibility and agree on how they will interact. Every service provides a policy—a machine-readable description of its capabilities and requirements. Policy allows services to find common ground.

Using policy allows the behavior of a service to be separated from the constraints of accessing the service. Imagine moving a service to a different environment where the parties allowed to access it are different. The policy changes to reflect this. The service behavior doesn't change at all.

When sophisticated services talk to each other, policy is used to determine the advanced capabilities that are mutual. When a limited service is in the mix, policy is used to find a reduced set of capabilities that both services can support. Policy is what allows services designed according to SO principles to interoperate with traditional Web services, which have limited capabilities in comparison.

In a service-oriented solution, services are loosely coupled, which means all parties make few assumptions about each other. Loosely coupled systems have excellent prospects for longevity, and they feature many compelling benefits. For example, services are isolated. Changing the internals of one service doesn't force matching changes, rebuilding, or restarting of other services. Services are also location-independent. Whether a service you need is on the same machine or halfway around the world, you access it in the same way. Services are also transport-neutral, protocol-neutral, and format-neutral. This means communication details between parties are flexible, not fixed.

Another key benefit: Services are platform-independent and implementation-independent. A service doesn't need to know anything about another service's execution environment in order to interact with it. Services are also scale-invariant, which means they scale well in all directions. For example, a service can be scaled out by fronting it with a router service that distributes traffic among a farm of services. You can also make services time-independent. If services make use of queue-based communication, they don't have to be online at the same time to interact. Another nice feature of services: They can be address-agnostic. If services employ a discovery mechanism, they can locate each other without any prior notion about where they reside. Finally, behavior is separated from constraints. This means that restricting, relocating, or outsourcing a service requires changes only in policy, not to the service itself.

You might wonder how services compare to Web services. They are related but not identical. You can think of services as the successor to Web services. Like Web services, services make use of SOAP, XML, XSD, WSDL, and other standard protocols. Unlike Web services, services aren't limited to HTTP request-reply communication: They're free to use other transports and other message-exchange patterns. Services are also more mature than Web services, providing enterprise-class features such as security, transactions, reliability, and performance. Services haven't forgotten their Web service roots, however. Through the use of policy, services and Web services can interoperate.

Create a Service-Oriented Example
Assume you're implementing a simplified customer support solution based on three primary services: a Help Desk service, a Dispatch service, and a Parts service. When a customer reports a problem, the Help Desk service assigns the problem to a support representative. You can resolve most problems over the phone by the help desk. If the help desk can't resolve a customer's problem, it escalates the problem to an on-site visit. A message is sent to the Dispatch service requesting a technician be sent to the customer.

If an on-site technician determines that a part needs to be replaced, a message is sent to the Parts service with a parts order. The Parts service sends a message to one of several vendors, depending on the specific part that has been ordered.

Now, consider how this solution holds up as time goes by and conditions change. Over time, the volume of customer activity increases until it is no longer practical for the Help Desk service to handle the volume with a single server. Also during this time, company policy changes, requiring technicians to obtain approval from the Accounting department before placing orders for parts. Lastly, the number of parts vendors increases (see Figure 2).

Each of these changes was simple, isolated, and not disruptive to the rest of the system. Customers still send requests to what they believe is the Help Desk service, unaware that it is now actually a router. The Dispatch service still sends out parts orders in the same way, unaware that an Approval service is intercepting them. No other part of the system is aware—or affected by—the fact that the Parts service now supports additional vendors. Despite the improvements to the solution, the original services' interfaces and relationships haven't changed at all.

Let's imagine a more dramatic change-management problem in which changes to one service do affect other services. Perhaps it is no longer sufficient for the Approval service to match the Parts service; the Approval service really needs its own unique interface. If the Approval service changes its interface, the Dispatch service, which calls it, must also change. One approach is to upgrade the Dispatch and Approval services at the same time, so the rest of the solution is not disrupted. Another approach is to add a new interface to the Approval service but continue to support the original interface. This allows the services to be updated at different times: The updated Approval service is deployed first, and the updated Dispatch service follows.

You've seen that the fabric of a service-oriented solution allows for constant change without constant headache. Now that you have a good feel for the principles of SO and the flexible nature of services, it's time to turn to look at Microsoft's technology for taking advantage of services most fully: WCF.

Build Connected Apps With WCF
Windows Communication Foundation (WCF) is the Microsoft platform for creating connected applications (see Figure 3). It was designed from the ground up with SO in mind. The result is a powerful infrastructure for integrated communication and enterprise services with a simple programming model. WCF has been described in several ways: as sockets for the 21st century, as the next generation of Web services, as the Microsoft runtime for services, and as a single programming model for communication and enterprise features. Each of these characterizations is a valid way to think about WCF, and gives you some insight into how you take advantage of it.

Microsoft created WCF with specific goals in mind. Chief among these were delivering a unified programming model, rich communication, broad interoperability, and other enterprise-ready features. WCF's unified programming model brings together a lot of capabilities—some new and some available formerly as separate technologies. The programming model gives you one way of doing things. It's no longer necessary to switch gears as you go from one technology to another. WCF's communication features include multiple transports, multiple message formats, and multiple messaging patterns, allowing you to use the right messaging tools for the right job. On the interoperability front, WCF's communication is fully platform-independent and its services are self-describing. All WCF messages are SOAP envelopes containing XML payloads. WCF follows the Web Services Architecture and uses established standards in its communication, including the WS-* protocols. Service descriptions are expressed using WSDL, XSD, WS-Policy, and WS-MetaExchange.

In the past, interoperability has often come at the expense of features important to enterprises. The benefits of ubiquitous Web services had to be weighed against the consequences of compromised security, transactions, reliability, and performance. WCF overcomes this problem, providing interoperability and enterprise-strength features at the same time. Services can coordinate many sophisticated activities through the use of Web Services Architecture standards, including security, transactions, and reliable messaging. These standards are composable, allowing them to be intermixed. WCF makes use of them as needed based on what your program is doing.

As you would hope with any SO technology, WCF is transport-neutral, protocol-neutral, and format-neutral. For example, services are free to make use of HTTP, TCP, named pipes, and any other transport mechanism for which there is an implementation. You are free to add new transport providers. These details never affect the way you write your services. WCF isolates your program code from communication specifics. This allows services to support a spectrum of communication methods without any extra work on your part.

Services written with WCF are scale-invariant. Services can scale up, out, down, and in. Services can scale up through deployment on more powerful servers, where WCF can leverage more resources. Services can scale out through routing and server farms, distributing traffic to multiple instances of services. Services can scale down by interacting with service-oriented devices such as printers and cameras. Services can scale in using efficient cross-process transports. Scale invariance maximizes the deployment options for your application.

Services are described at the contract layer. The service runtime layer loads and runs services, managing many behaviors such as instancing and concurrency. The messaging layer provides the communication infrastructure, which includes various communication channels for transport, reliable messaging, security, and queuing. The activation and hosting layer allows services to run in a variety of environments, ranging from a self-hosted EXE to automatic activation and hosting by Microsoft Internet Information Services (IIS).

Code in WCF
WCF simplifies service programming from a Visual Studio standpoint. You can take a "code-first" or "contract-first" approach to development. Tools assist development by generating code from service descriptions and vice versa.

The programming model allows you to perform common tasks with only a little code, yet it gives you full control when you want it. There are hundreds of classes in the complete object model, but you'll primarily work with a few high-level objects such as services, channels, and messages. WCF code is compact, easy to write, and easy to understand. For example, note how little code it takes to initiate communication with another service:

TradingSystemProxy proxy = new TradingSystemProxy();
   price = proxy.GetQuote(symbol);

The programming model offers three methods for writing programs: declarative programming using attributes, imperative programming using code and objects, and configuration-based programming through application configuration file settings. You typically declare contracts and service behaviors using attributes; configure end points, security, and management features using config files; and implement service operations in code.

Several levels of programming are possible. Typed services contain service operations that are similar to functions, with parameters and return values that might be simple or complex data types. Untyped services contain service operations that accept and return messages. You work directly with messages at this level. Programming against the messaging layer is the lowest level of all, giving you precise control over communication activities such as creating channels.

The programming model includes a collection of declarative attributes, known as the service model, that you can use in place of explicit code. The service model gives you attribute-based control over many areas, including communication, security, transactions, instancing, and error handling. To get a sense of this, take a look at this code fragment where a service contract specifies details about session, communication, and transaction behavior through the use of attributes:

[ServiceContract(Session=true)] 
[ServiceBehavior(InstanceMode=
   InstanceMode.PrivateSession)] 
public class MyService 
{ 
   [OperationContract(IsOneWay=false)] 
   [OperationBehavior(AutoEnlistTransaction=true)] 
   String DebitAccount(string accountNo, 
      double amount) 
   { 
      ... 
   } 
}

You can also specify many service model capabilities as configuration settings, allowing deployment-time decisions to be made about communication, security, and runtime behavior. The service model allows you to focus on what's important: your application. In a WCF program, most of the code will be your application code.

WCF's communication can be either synchronous or asynchronous, and one-way or two-way. It can also use datagram, duplex, or request-reply messaging patterns. The transport for communication can be HTTP, TCP, or named pipes (for cross-process communication). It's possible to add new transports by writing a transport provider.

A message can contain any digital information, from a simple string to a business data structure to video content. WCF represents messages in memory as SOAP envelopes, and you can render these in text, binary, or Message Transmission Optimization Mechanism (MTOM) format for transmission. Messages can be buffered (transferred in entirety) or streamed.

As a developer, you can specify your communication, reliability, and security requirements (called bindings); WCF builds the appropriate types of communication channels in response. More than a dozen predefined "bindings" delineate the combinations needed for common scenarios, but you remain free to create your own custom bindings. You can also specify bindings in code or configuration files.

Take Advantage of Other Features
On top of the core communication capabilities, a feature known as reliable sessions provides robust messaging, with automatic retry and recovery if communication is disrupted. With reliable sessions, your program can determine for sure whether messages make it to their destinations successfully. Simply specify the assurances you need, such as exactly-once, in-order delivery. Reliable sessions also provide the mechanics for clients and services to maintain sessions.

WCF's queuing provides first-in, first-out (FIFO) messaging. Queuing allows programs to work with a pull model rather than a push model. The queuing service integrates with Microsoft Message Queuing (MSMQ). Queuing allows you to store messages durably—an important capability for building reliable, recoverable messaging solutions. Queuing also allows programs to be time-independent of each other in their communication.

The service runtime manages the execution of services. You can specify runtime behaviors through attributes or configuration settings. Instancing behavior determines how many instances of a service are created for clients. Concurrency behavior controls thread synchronization. For example, a service can indicate that its instances are not thread-safe. Throttling behavior allows you to put limits on the number of connections, sessions, and threads. Error handling behavior controls how errors are handled and reported to clients.

Service orientation discourages the use of objects as a way to connect programs, but object-oriented programming remains important to developers. WCF treats objects as first-class citizens. Serialization converts objects to and from XML. Serialization honors the "no classes and types" tenet of service orientation by converting objects to XML schema for transmission. Deserialization performs the reverse steps on the receiving side. When you access typed services, the serialization and deserialization of parameters and return values is completely automatic. You can also perform serialization on demand in code explicitly.

Security is a major concern in any development environment, and WCF introduces a wide range of new security features. These include client authentication, server authentication, message integrity, message confidentiality, replay detection, and role-based access control. Message integrity ensures that messages aren't tampered with. Message confidentiality makes sure that only authorized parties can read the messages. Replay detection prevents a hacker from capturing and retransmitting messages. Multiple security mechanisms are supported, including X.509 and integrated Windows security.

Services indicate their security needs through declarative attributes, code, or configuration settings. Security environment information, such as certificate locations, is determined at run time through configuration files. For common security configurations, developers can take advantage of turnkey security by specifying predefined bindings for common scenarios.

Transactions ensure that related operations occur as an atomic unit, in which everything succeeds or everything fails. WCF provides a centralized transaction system. In the past, transaction technologies were available for database processing and components, but there were too many ways of doing things. WCF provides a single transaction system that supports traditional transactional work, such as database processing and transacted communication.

The programming model makes transactions easy to use. This code shows you how to implement a transaction around communication with two services:

using (TransactionScope scope = 
   new TransactionScope()) 
{ 
   mortgageService.SubmitMortgageApplication(
      mortgageApp); 
   insuranceService.SubmitInsuranceApplication(
      insuranceApp); 
   scope.Complete(); 
}

Services can flow transactions to other services. This allows enterprises to perform transactional work across platforms.

You can host WCF services in many kinds of environments, including Windows Forms applications, console applications, and Windows service applications (also called Windows NT Services). You can also host them with IIS, which allows services to be registered for automatic activation the first time a client attempts to access them. When a message arrives for a service that is not already running, it is launched automatically. IIS hosting also provides a reliable environment for services. Services are restarted if they enter an unhealthy state. If a service is updated, IIS hosting brings the new version online gracefully.

The sum of these features gives you a lot more options for building service-oriented applications more quickly and easily. Modern applications are connected applications. Service orientation treats connected applications as a cooperating set of message-oriented programs called services. Services expose service descriptions in WSDL and a collection of end points. Services contain contracts, bindings, and end points. Service contracts define the operations a service can perform. Bindings define how a service communicates with the outside world. Endpoints define the access points to a service.

About the Author
David Pallmann is a principal consultant with Neudesic. He has more than 25 years of experience in designing and developing software, much of it related to distributed computing. Reach him at [email protected].

comments powered by Disqus

Featured

  • Creating Reactive Applications in .NET

    In modern applications, data is being retrieved in asynchronous, real-time streams, as traditional pull requests where the clients asks for data from the server are becoming a thing of the past.

  • AI for GitHub Collaboration? Maybe Not So Much

    No doubt GitHub Copilot has been a boon for developers, but AI might not be the best tool for collaboration, according to developers weighing in on a recent social media post from the GitHub team.

  • Visual Studio 2022 Getting VS Code 'Command Palette' Equivalent

    As any Visual Studio Code user knows, the editor's command palette is a powerful tool for getting things done quickly, without having to navigate through menus and dialogs. Now, we learn how an equivalent is coming for Microsoft's flagship Visual Studio IDE, invoked by the same familiar Ctrl+Shift+P keyboard shortcut.

  • .NET 9 Preview 3: 'I've Been Waiting 9 Years for This API!'

    Microsoft's third preview of .NET 9 sees a lot of minor tweaks and fixes with no earth-shaking new functionality, but little things can be important to individual developers.

  • Data Anomaly Detection Using a Neural Autoencoder with C#

    Dr. James McCaffrey of Microsoft Research tackles the process of examining a set of source data to find data items that are different in some way from the majority of the source items.

Subscribe on YouTube