Architecting Services with Design Patterns
As the number of your services expands you're going to need to start thinking about how to organize them. Applying these two design patterns can help, provided you understand all their variations.
With the current set of tools that come with the Microsoft .NET Framework, it's easy to create a service. But it's also easy to create what both Nayaki Nayyar (director of Enterprise Architecture and Technology Service at Valero Energy) and Benjamin Moreland (manager of Application Infrastructure at Hartford Financial Services Group) described as a "junk drawer of services." If you're going to avoid that as the number of services in your organization increases, then you need to think about your architecture.
Service vs. O-O Design
Some of the tools you use in building applications also apply as you build out your services. The Single Responsibility Principle (SRP), for example, makes as much sense at the service level as it does at the class level: Each service should do one thing well. Among other benefits, following SRP means that your services won't overlap in functionality and developers will know which service to look at for any particular functionality. SRP also makes it easier to understand, test, document or even just talk about a service.
On the other hand, some object-oriented "best practices" don't apply in service design. Because of network latency, it's always a good idea for a service to return all the data that a client might require rather than just the minimum requested. If a client wants the status of a salesorder, for example, it's better to send the complete salesorder or the entire status history to eliminate the need for a second, time-consuming request for the "rest of the information" (understanding your users will help you decide which is the better choice). In service design, therefore, it's better to think in terms of "business documents" rather than "application entities."
As in application development, design patterns can help you create well-architected solutions. As with application design patterns, services-oriented design patterns aren't so much about what's new as about providing a place to accumulate the best practices associated with the problem the design pattern solves.
Hiding Complexity with Façades
Some application design patterns are also service design patterns. For example, the Façade design pattern is as an essential tool in designing services as it's in application development. The need for the Façade pattern follows from applying SRP; Because of SRP, you'll have lots of services and most business processes will require coordinating activities across multiple services. Rather than require clients to handle that complexity for common requests, it makes sense to create a façade service that accepts a single request from a client and coordinates those other services (again, knowing your users will tell you what those common requests will be).
A subset of this design pattern is the Message Splitter/Aggregator pattern. This pattern, like the Façade pattern, hides the services that do the "real" work behind a front-end service to which clients send requests. However, in the Message Splitter/Aggregator pattern, the front-end service simply takes the payload that's sent by the client, splits it up into separate messages and passes those messages on to the back-end services. As the back-end services complete their work and return their results to the front-end service, that service assembles the message to be sent to the client. No real coordination, other than calling the services, is required.
Implementing either of these versions of the Façade pattern has multiple benefits. The façade service can also act as the only front-end to the back-end services, allowing you to implement the Trusted Subsystem best practice. With this practice, the façade service can become responsible for auditing, validating and filtering requests, relieving the services behind the façade from that requirement. Implementing this feature in a façade, however, can be controversial: Many organizations prefer a "defense-in-depth" strategy to centralizing protection at one point in the architecture.
Supporting Loose Coupling
A façade also supports loose coupling between clients and services, making it easier for a volatile set of services to evolve without disrupting clients. With a façade in place, if you need to change the way your services work you can do that with affecting clients by rewriting the façade to match the new service design. At this point, you make a case for calling this an example of the Adapter pattern (providing the interface the client wants to a set of services with a different API) rather than the Façade pattern.
In fact, many organizations fall into the Façade design pattern by necessity and without planning: The organization wants to modify its service design but, because it can't track down or rewrite all the clients using the existing services, it has to leave the existing service interface in place. To handle that, the organization guts the existing services and re-distributes their functionality across the new service design. The original services are left in place as a facade, however, to support the existing clients, now delegating their work across the new service architecture. As part of the process, the façade may transform or enrich the incoming requests and outgoing responses to conform to the client's or back-end services' needs.
Another variation on the Façade pattern is when the front-end service examines the client's payload and determines which service is to handle the client's request based on the payload's content (commonly referred to as content-based routing). This is loose coupling carried to the extreme: The client isn't even bound to the "real" service's endpoint. Instead, the client only knows the endpoint for the facade (or enterprise service bus) that takes care of getting the request to the right service.
The façade service can also act as a bridge between protocols. The façade may be made available to external business partners as an interoperable service. However, the services behind the façade might only be accessed by the façade. In that case, the services behind the façade can use platform-specific technologies to communicate, giving orders-of-magnitude better performance than interoperable protocols like HTTP.
While the Façade pattern supports multiple purposes and has several variations, the fundamental implementation doesn't change: The Façade accepts a request, delegates the work to other services, gathers responses and returns a result. Other design patterns, however, may have multiple implementations, depending on the scenario.
Supporting Ongoing Requests with Publish/Subscribe
The Publish/Subscribe pattern is an example of that kind of service. Often, services provide a way of distributing information -- the status of an order, a list of inventory items that need to be reordered, a collection of new customer complaints. While it's possible to send that information to every application or system component that's interested in it, as the number of interested clients increases, implementing the Publish/Subscribe design pattern often makes more sense. In this design pattern, the information is made available by the publisher service as a message. Clients subscribe to the service to retrieve the messages in which they're interested.
When the information available from a service falls into several categories and each client is only interested in some of the categories, the publisher can offer subscribers a filtering option. In the simplest version of this variation, each piece of information is assigned to a specific category (usually referred to as "topics"). Where the topics can overlap, a variation on this pattern is to tag each piece of information with all the categories to which it belongs.
When topics are used, clients may be restricted to specifying a single topic in a request and, as a result, need to make multiple requests to get all the information in which they're interested. Alternatively, clients might be allowed to specify multiple topics in a request, which makes for more complicated responses. More sophisticated systems may organize topics into hierarchies and restrict clients to a single topic. In this variation, clients are restricted to a single topic in a request but can still retrieve multiple sets of information by specifying a topic high in the hierarchy.
When implementing Publish/Subscribe, much depends on how quickly clients need to get the information. For immediate processing, a WebSockets implementation using Microsoft's SignalR library lets the publisher notify clients immediately. However, using WebSockets has scalability implications because it requires a more permanent connection between the client and the service than, for example, HTTP.
More scalable (and more loosely coupled) implementations are possible if the clients don't need to receive the information immediately. Having the clients regularly poll the publisher for new information at regular intervals is the simplest implementation, so simple that (other than letting clients specify a topic), it may not look much like the Publish/Design pattern at all. To prevent overloading the publishing service with requests, it may be necessary to establish a schedule to ensure all subscribers don't request data at the same time. Posting on various topics at specific times may be sufficient to spread out requests, providing that no single topic has "too many" subscribers.
More sophisticated solutions require the client to implement a callback service with a specific interface, known to the publisher. In this version, the client subscribes to the service by calling the publisher service, passing the endpoint for their implementation of the callback service (and, where used, a list of topics). When the publisher is ready to provide information, the publisher iterates through this list of endpoints, sending the requested information to the client service.
Separating Publishers and Publishing
Where the number of publishers and topics is expected to be volatile (and to more loosely couple the publisher and the subscribers), the functionality of accepting subscribers and sending messages may be separated from the information sources. In this design the information source (the publishers) and the publishing service are separate processes.
In this variation, publishers make information available at some endpoint where the publishing service can retrieve it. Much as subscribers notify the service of their interest in specific topics, publishers notify the publishing service that they exist, passing their endpoint and the topics the publisher provides. Publishers may notify the service when information is available or the publishing service may simply poll publishing sources at regular intervals, checking for new information.
The variations on the Publisher/Subscribe model also extend into the nature of the message that the subscriber receives. Where the amount of information is large, the message may simply consist of the endpoint where the actual information may be retrieved. Additional information (a "summary" or selection of keywords) may be included in the message so the client can decide if it needs to retrieve the information. With this variation, the endpoint for the information may be at the publisher rather than the publishing service, allowing the publishing service to offload storing and delivering the message to the publisher. This option allows the client to retrieve the information at the client's convenience rather than the publisher's.
As you can see, even with these two patterns, for design patterns to be useful they must be flexible. Many solutions will, in fact, require combining multiple patterns, so it might be difficult when looking at a solution to determine what pattern is being implemented. For example, is a separate publishing service actually an example of a Façade pattern that coordinates multiple publishers?
But you don't care. What makes you successful as a developer is being familiar with a wide variety of patterns (and their variations), knowing when each variation is applicable and applying the best practices associated with each pattern. This is the beauty of service design patterns: They let you avoid making other people's mistakes so you can concentrate on making your own.