There are a variety of service-oriented architectures (SOAs) in existence, including many hybrids. Here are some common architectural patterns.
A particular manner of creating SOAs became popular several years ago, building an architecture based around services and coordination via a service bus — typically called an Enterprise Service Bus (ESB). The service bus acts as a mediator for complex event interactions and handles various other typical integration architecture chores such as message transformation, choreography, and so on.
While ESB architectures typically use the same building blocks as EDAs, the organization of services differs, and is based on a strictly defined service taxonomy. The ESB style differs from organization to organization, but all are based on segregating services based on reusability, shared concepts, and scope. A representative ESB SOA is shown in Figure 4-10.
Figure 4-10. Typical service taxonomy for an ESB SOA
In Figure 4-10, each layer of the architecture has specific responsibilities. The business services define the coarse-grained functionality of the business as abstract definitions, sometimes defined by business users using standards like BPEL (Business Processing Execution Language). The goal of business services is to capture what the company does in an abstract way. To determine whether you have defined these services at the correct level of abstraction, ask yourself, “Are we in the business of...” affirmatively for each of the service names (like CreateQuote or ExecuteTrade). While a developer might invoke a CreateCustomer service in order to create a quote, that isn’t the main thrust of the business but rather a necessary intermediate step.
The abstract business services must call code to implement their behavior, which are enterprise services: concrete implementations meant for sharing, owned by a distinct service teams. The goal of this team is to create reusable services integration architects can “stitch together” using choreography to form business implementations. Developers aim for high reuse and design enterprise services accordingly (to understand why this often fails, see “Antipattern: Code Reuse Abuse”).
Some services don’t need a high degree of reuse. For example, one part of the system may need geolocation, but it isn’t important enough to devote resources to make it a full blown enterprise service. The Application Services at the bottom left in Figure 4-10 handle these cases. These are bound to a specific application context and not meant for reuse, and are typically owned by a specific application team.
Infrastructure services are shared services owned by an infrastructure team to handle nonfunctional requirements such as monitoring, logging, authentication/authorization, and so on.
The defining characteristic of an ESB-driven SOA is the message bus architectural component, responsible for a wide variety of tasks as follows:
Mediation and routing
The message bus knows how to locate and communicate with services. Typically, it maintains a registry of physical location, protocols, and other information needed to invoke services.
Process choreography and orchestration
The message bus composes enterprise services together and manages tasks like invocation order.
Message enhancement and transformation
One of the benefits of an integration hub is its ability to handle protocol and other transformations on behalf of applications. For example, ServiceA may “speak” http and needs to call ServiceB, which only “speaks” rmi/iiop. Developers can configure the message bus to handle this transformation invisibly anytime this conversion is needed.
The architectural quantum for ESB-driven SOA is massive! It basically encompasses the entire system, much like a monolith, but is much more complex because it is a distributed architecture. Making singular evolutionary change is extraordinarily difficult in ESB-driven SOA because the taxonomy, while assisting reuse, harms common change. For example, consider the CatalogCheckout domain concept within an SOA — it is smeared throughout the technical architecture. Making a change to only CatalogCheckout requires coordination between the parts of the architecture, commonly owned by different teams, generating a tremendous amount of coordination friction.
Contrast this representation of CatalogCheckout with the bounded context partitioning of microservices. In a microservices architecture, each bounded context represents a business process or workflow. Thus, developers would build a bounded context around something like CatalogCheckout. It is likely that CatalogCheckout will need details about Customer, but each bounded context “owns” their own entities. If other bounded contexts also have the notion of Customer, developers make no attempt to unify around a single, shared Customer class, which would be the preferred approach in an ESB- driven SOA. If the CatalogCheckout and ShipOrder bounded contexts need to share information about their customers, they do so via messaging rather than trying to unify around a single representation.
ESB-driven SOA was never designed to exhibit evolutionary properties, so it’s no surprise that none of the evolutionary facets score well here:
While having a well-established technical service taxonomy allows for reuse and segregation of resources, it greatly hampers making the most common types of change to business domains. Most SOA teams are as partitioned as the architecture, requiring herculean amounts of coordination for common changes. ESB-driven SOA is notoriously difficult to operationalize as well. It typically consists of multiple physical deployment units, making coordination and automation challenging. No one chooses ESBs for agility and operational ease of use.
Guided change with fitness functions
Testing in general is difficult within ESB-driven SOA. No one piece is complete — every piece is part of a larger workflow and isn’t typically designed for isolated testing. For example, an enterprise service is designed for reuse, but testing its core behavior is challenging because it is only a portion of potentially a variety of workflows. Building atomic fitness functions is virtually impossible, leaving most verification chores to large-scale holistic fitness functions that do end-to-end testing.
From a potential enterprise reuse standpoint, extravagant taxonomy makes sense. If developers can manage to capture the reusable essence of each workflow, they will eventually write all the company’s behavior once and for all, and future application development consists of connecting existing services. However, in the real world this isn’t always possible. ESB-driven SOA isn’t built to allow independent evolvable parts, so it has extremely poor support for it. Designing for categorical reuse harms the ability to make evolutionary change at the architectural level.
Software architectures aren’t created in a vacuum — they always reflect the ecosystem in which they were defined. For example, when SOA was a popular architectural style, companies didn’t use tools like open-source operating systems — all infrastructure was commercial, licensed, and expensive. A decade ago, a developer proposing a microservices architecture, where every service runs on its own instance of an operating system and machine, would be laughed out of the operations center because the architecture would have been ludicrously expensive. Because of the dynamic equilibrium of the software development ecosystem, new architectures arise because of a literal new environment.
While architects may still choose ESB-driven SOA for integration heavy environments, scale, taxonomy, or other legitimate reasons, they choose it for those features rather than evolvability, for which it is spectacularly unsuited.