One of the questions I’m most frequently asked is how to go about decomposing a system into services to develop a service-oriented architecture (SOA). To understand how to divide a system into specific services, we first need to think about what we are trying to achieve with our software architecture. In most large development projects, experience has taught us that we rarely ever fully understand the problem we are designing for at the outset of the project. So, first and foremost, we want to design for change, because we’ll need to adapt our software design over the life of the project. Much of the software industry has embraced Agile development processes because they enable software teams to quickly adapt to their evolving understanding of requirements and design objectives. Similarly, we have found it beneficial to embrace architecture practices that enable flexibility throughout the design lifecycle, which is a major motivation for SOAs.
It is commonly accepted in the software engineering community that loosely-coupled classes make for better OO designs, because, in general, loose coupling can minimize the scope of changes that must be made to components within the system over time and can enable component reuse. Over the past several years, new schools of thought have emerged that expand on this concept to separate some aspects of a software application’s control flow from the application’s business logic. This Inversion of Control concept (aka Dependency Injection) has been embodied in the Java language (though annotations) and is supported by some application component frameworks (e.g. Spring and EJB containers). The same loose-coupling concept should be applied to services, and one key way to limit coupling is to separate business process workflows from business logic. Services should provide business logic, but we don’t want to bake workflows into the service logic because to do so would reduce the flexibility of the architecture. We want the option of changing the way business process workflows unfold, we want to promote reuse of business logic across multiple business processes and we want to enable our system to expand to meet new business demands, all without the need to significantly change the services. Extracting the workflow logic into a separate “orchestration” layer enables us to do just that.
Figure 1 illustrates the idea of embedding workflow orchestration into services. Service A is the entry point for the start of a business process — assume it is invoked by a client application — and Service A then calls Services B and C in order, blocking for each response. Service C then represents a subordinate business process, which calls and blocks for other services (D & E). The point to make from this simple diagram is that the business logic becomes intertwined with workflow if we don’t take steps to explicitly separate it. Service A has explicit knowledge of Services B and C, and its business logic is wrapped around invocations of those services, which locks that business logic into a specific business process. We can do better by extracting the business process orchestration into a layer above the business logic.
Figure 2 illustrates an alternative approach, where we utilize separate orchestration services to create the workflow across the services, which are design to behave independently. I’ve omitted any notion of ordering from the diagram to avoid comparison with the original workflow in Figure 1. When workflow is omitted from the services, we must design them to operate independently around a cohesive set of business functions, which could change the invocation strategy altogether to avoid the need to manage state across transaction boundaries. So, although there would certainly be an invocation order for the architecture illustrated in Figure 2, it would not necessarily look like the original workflow in Figure 1, and the important point is that the services should have no idea of the order in which they are invoked or of the other services in the business process.
Just like we desire high cohesion in OO designs, we also want it in the design of services. Each service should encapsulate a collection of strongly-related functionality, which will promote modularity, enable reuse and reduce coupling with other services. Before we talk about how to group functions into services, it is useful to talk a bit about the different types of services that we can expect to develop in an enterprise system. My favorite conceptual reference on this subject is the book Applied SOA [Rosen, et al.]. The authors describe a variety of different services, but the most significant are illustrated in Figure 3 below.
The most significant services — and probably the most challenging to interpret — are the Business and Domain services. The authors of Applied SOA define these service types in terms of granularity and accessibility. Business services are the coarsest (encompassing the most scope) and have the broadest accessibility (enterprise wide), followed by Domain services, which are medium-grained and are limited in accessibility to support Business services inside the enterprise.
To understand the role of a Business service, it is illustrative to examine the public service interfaces of prominent systems, such as the Twitter REST API. Twitter’s REST API constitutes the system’s publicly-exposed (enterprise) service interfaces, categorized into their major business functional areas (e.g. Tweets, Timelines, Users, Local Trends, Friends & Followers, etc), so these interfaces can be considered examples of Business services, which invoke Domain and Utility services that are not publicly exposed. For example, the basic service for updating a user’s status (aka posting a tweet) involves functions such as user validation, geolocation, URI shortening, linking references to other users and hashtags, conversation threading, etc. Some of these functions are satisfied by Domain and Utility services that are not publicly accessible.
The scope of Twitter’s Business services do not limit the range of potential business processes. The independent and functional nature of Twitter’s public interfaces enables external applications to retrieve and correlate data using multiple interfaces in many different ways, which may not be possible if the services were conflated with business process logic. The multitude of third-party Twitter clients and data analysis tools is a testament to the value of decoupling business logic from business processes.
Utility services and Integration services are more straightforward to understand. Utility services in our Twitter example might be used to perform simple, reusable functions, like sorting a list of tweets or returning a human-readable city name from a lat/long. Integration services are used to integrate legacy applications and generally provide a mediation facility between the interface technologies used in the new system and the interfaces of the legacy application.
Business Process Definition
So, how do we go about decomposing our system into a loosely-coupled, cohesive set of Business and Doman services? It is important to understand that we really have two goals. The first is to develop an understanding of our system’s business processes so that we can understand the behaviors and workflows the software must support. Understanding the business processes is what will allow architects to identify cohesive sets of behaviors that should be aggregated into individual services. However, our second goal is to avoid “over fitting” our architecture to our known set of business processes. We want to continuously think about the ways in which our system might need to be extended to meet evolving business objectives. Remember what I said at the beginning of this article — we rarely ever understand the full requirements for the end product at the outset of the project, so we need to design for change.
The first step in decomposing our services is to understand our known business processes, and there are established Business Process Modeling techniques that can help, such as Use Case Analysis. Whether you call them System Engineers, Business Analysts or Product Managers, there is likely a role within your organization with responsibility for defining business processes. Architects are key stakeholders in the business process development activity, because identifying and modeling business processes allows architects to identify cohesive sets of behaviors and to define entry points (i.e. Business services) into those behaviors to support business process orchestration.
Domain Modeling — described in detail in Eric Evan’s excellent book, Domain-Driven Design — is another useful analysis technique for defining service scope. Domain Modeling can be used successfully at different levels of abstraction. At the lowest level, Domain Modeling can be used by software engineers to define an object-oriented class structure that represents the entities and relationships in the business domains, and it can be used to inform the creation of a data model (e.g. XML messages, database tables) representing those same entities and relationships. At a more abstract level, architects can use Domain Modeling to define a system’s major conceptual entities (e.g. Customer, Account, Invoice, etc) and understand their behavioral lifecycle through the various business processes, which can help to identify cohesive sets of domain entities and their behaviors that we will encapsulate within domain services.
There are a few other related topics that are worth briefly mentioning.
Data Encapsulation — In my experience, it is not uncommon for software engineers to want to share data between services through a common database. In my view, this is a bad idea and conflicts with our loose coupling goal. Services should encapsulate their data, and access to that data should only happen through the service interface. Software engineers will argue that this can lead to redundant data being stored in the system. To me, redundant data should probably lead us to conclude that either the services are decomposed incorrectly, perhaps by misunderstanding the business process or domain model, or the services should not have been decomposed at all because the behaviors spread across the two services that need to share data were so cohesive that they should have been kept together.
REST vs. SOAP — Much has been written about the relative virtues of RESTful web services and SOAP-based (WSDL) web services, so I won’t repeat all of that here. However, in my experience, SOAP-based services are more complex to develop than RESTful services. The creation of WSDLs, designing complex XML schemas, working with a web service stack (e.g. Metro) and integrating the language-specific object-marshaling technologies (e.g. JAXB) can be challenging. There are simply more moving parts with SOAP-based services than with RESTful services. So, in my opinion, it is better to default to RESTful services unless there is a clear and compelling need for some of the advanced capabilities offered through the web service specifications. However, it is worth verifying that any given WS-* specification is supported in a variety of cross-language web service stacks (many are not), or you could be trading portability for capability.
I have also come to prefer RESTful web services because I feel that they abstract away more of the software architecture details from the consumers. SOAP-based services are often described as RPC services, because consumers reference specific methods on specific services through the WSDL. With RESTful services, architects design interfaces around the concept of a resource and use standard HTTP methods to manage operations on those resources, which can fit together nicely with Domain Modeling to help identify and organize resources.
SOA is a set of architecture concepts, not a set of enabling technologies. It is entirely possible to adopt the technologies without realizing any of the SOA benefits (beware of systems comprised primarily of legacy software that has been SOA-ized with XML wrappers). Above all, the way we go about decomposing our system into services is motivated by a desire to instill extensibility into our software architecture and to enable reuse so that we can reduce the cost of adapting to changing business needs. We achieve these SOA concepts by investing in developing an understanding of a system’s business processes to identify cohesive behaviors, by enforcing a separation of business process workflows from business logic and by driving our development teams to think about and design software in terms of the system’s Domain Model.