Service-oriented architecture (SOA)

Service-oriented architecture is a distributed software design pattern. It sees an application divided into multiple services, each of which can be developed and tested independently of other services within the system. The defining feature of a [service] is that it runs in its own [process] and communicates with other services within the system via network calls. Optionally, individual services may have distinct code repositories and production infrastructure.

The system design of service-oriented architecture involves [decomposing] an application’s functionality into separate, [loosely coupled] services, each managing its own data stores and caches, and which communicate with each other via network APIs or message brokers.

In a service-oriented design, there tends to be a hierarchy of services, in which lower-level services are shared by the higher-level domain and application services. Low-level, general-purpose services may include things like authentication, user management, and payment processing. These services would rarely change, and would therefore require minimal maintenance. Higher-level services will tend to be domain-specific (they are also known as domain services). For example, an e-commerce platform may have services for inventory management, order processing, and customer reviews – each a [subdomain] of the [domain model].

Domain services are more likely to be [stateful], due to requirements to maintain state across multiple requests, while utility services are more likely to be optimized for performance using a [stateless] design.

Comparisons to other architectural styles

Microservices architecture is a subset of service-oriented architecture with further design constraints. A microservices architecture emphasizes the design of very small, independently deployable services. The distinction between service-oriented and microservices architectures is mostly a matter of granularity, which is subjective. The two terms are often used interchangeably.

Web-oriented and resource-oriented architects are other variations on SOA.

Message-driven and event-driven architectures help to enforce the loose coupling between services that is a key principle of SOA. However, SOA itself does not prescribe particular communication patterns for service contracts, and a service-oriented system may even implement a mix of communication styles, including [synchronous] and [asynchronous] styles.

If each service in a service-oriented design were to deliver an end-to-end solution for a discrete user-facing function, then the architectural style could be said to be a cross between service-oriented and vertical-slice architecture. More commonly, service-oriented designs tend to involve a hierarchy of services, in which lower-level cross-cutting services are reused by multiple higher-level user-facing or domain-oriented services.

Design trade-offs

Service-oriented architectures share the same trade-offs as all distributed software. The main trade-off is that greater [scalability] is achieved at a cost of increased [accidental complexity]. Common difficulties in service-oriented designs include planning for fault tolerance, reasoning about concurrency, and achieving consistency in replicated data. See distributed software for more on these trade-offs.

A widely-promoted advantage of service-oriented architecture is that it allows for [flexibility] in optimizing software components in different ways for [performance optimization] and [scalability]. Each service in a service-oriented system can be scaled independently, and optimized in different ways as appropriate for their respective use cases and load patterns. This can include using different programming languages for different services. The trade-off for a higher degree of micro-optimization is the increased complexity at the macro level. For example, within an organization there may be increased friction involved in moving people between teams, if different teams use different technology stacks for the services they maintain. For this reason, many organizations define a set of approved technologies that can be used, finding a pragmatic balance between standardization and flexibility. For example, Python might be used for a machine learning service while the rest of a system uses Java.

Service-oriented architecture tends to work best where service boundaries clearly map to bounded contexts in the domain model, and where the bounded contexts in turn map directly to [team topologies], allowing teams to each work independently on a discrete group of services.

A critical design consideration in service-oriented architectures is the granularity of services. If services are too fine-grained, the system can become overly complex (due to the operating overheads of managing lots of services) and slow (due to the overhead of higher network communication) – see microservices for more on these trade-offs. On the other hand, if services are too coarse-grained, the system keeps more of the limitations of monolithic architectures.


References

  • Four pillars of service-oriented architecture, Grace A Lewis and Dr Dennis B Smith, Software Engineering Institute, Carnegie Mellon University, 2007 — Paper on the four key principles of service-oriented architecture: loose coupling, abstraction, reusability, and composability.