Building with Composable Services

How composable services extend microservices

Dick Dowdell
Nerd For Tech
11 min readJun 18, 2023

--

Courtesy of Bounteous

The composable services pattern is based upon composing applications from self-contained, reusable services. To take full advantage of building with composable services, it is useful to understand how the pattern extends its parent pattern — traditional microservices.

How Is the Composable Services Pattern Different?

If the Composable Services Pattern seems a lot like microservices—that’s because it is … microservices. But it is also much more. Where microservices focus on structuring applications as groups of stand-alone services, the composable services pattern puts more emphasis on making those services reusable and pluggable components.

Many of the issues and problems that have plagued the development and use of microservices can be blamed upon a failure to pay sufficient attention to microservice composability, design, deployment, and communication.

The composable services pattern was designed to fix that. Let’s take a look at what makes the composable services pattern different from plain vanilla microservices.

Composable Services:

Are Discoverable

Services are discoverable by the message orchestrators that deliver request and event messages to them. They are accessible through any federated orchestrator on the network.

Benefits:

  1. Self-configuration: Discoverability allows message orchestrators to automatically configure themselves based on the available service instances in the network. They can dynamically discover new service instances, understand their capabilities, and adapt their behavior accordingly. This reduces the need for manual configuration and makes the system more flexible and scalable.
  2. Failover: In the event of a service failure, discoverability enables message orchestrators to identify alternative instances of the service to execute the functionality. This permits seamless failover and helps to ensure that requests and events are processed without disruption. By automatically redirecting traffic to healthy service instances, the system can maintain high availability and improve reliability.
  3. Load balancing: Discoverability enables message orchestrators to distribute the workload across multiple instances of the same service. By identifying available service instances and their current responsiveness, orchestrators can intelligently route requests and events to ensure optimal resource utilization and avoid overloading any single service instance. Load balancing improves performance, scalability, and responsiveness of the system.
  4. Network-wide accessibility: Service instances that are discoverable by any federated orchestrator can be accessed from any part of the network. This promotes interoperability and allows different orchestrators to collaborate and interact with services seamlessly. It facilitates the development of decentralized and loosely coupled systems, enabling components to be added, removed, or replaced without disrupting the overall system functionality.
  5. Scalability and flexibility: Discoverability of service instances simplifies the process of adding new services to the network. Orchestrators can automatically identify and integrate new service instances, making it easier to scale the system as demand increases or new requirements emerge. This flexibility helps the system to adapt to changing business needs and technological advancements.
  6. System observability and monitoring: Discoverability provides valuable insights into the state of services within the network. Orchestrators can gather information about the availability, performance, and health of service instances, allowing administrators to monitor the system and make informed decisions. This observability aids in troubleshooting, performance optimization, and capacity planning.

Use Self-Contained Messages

All the information required by the service to process a message is present in the incoming message itself or in a persistent data store. Messages are self-describing and do not require the added complexity of a separate schema management system.

Benefits:

  1. Service Autonomy: Self-contained messages enable services to operate autonomously without relying on external dependencies or shared resources. Each service has access to all the information it needs to process a message, allowing it to function independently and make local decisions. This promotes modularity and encapsulation, making services easier to develop, test, deploy, and maintain.
  2. Loose Coupling: By including all the necessary information within the message, services can communicate with each other in a loosely coupled manner. They do not need to rely on shared contracts or tightly coupled schemas. This loose coupling reduces dependencies and makes it easier to evolve and update services independently, as they are not tightly bound to specific versions or implementations.
  3. Version Management: Self-describing messages support version management in a more flexible way. Since the messages contain all the information required for processing, including data structures and semantics, services can handle different versions of messages concurrently. This allows for smooth evolution and gradual adoption of new message formats without disrupting compatibility with existing services.
  4. Simplified Schema Management: With self-contained messages, the need for a separate schema management system is eliminated. Services can rely on the message itself to understand the structure and content, reducing complexity and administrative overhead. This simplification streamlines development processes, as there is no need to manage and synchronize schemas across different services.
  5. Interoperability: Self-describing messages promote interoperability between different services and systems. By including all relevant information, messages can be processed by various services, even if they were not initially designed to interact. This flexibility enhances integration across heterogeneous environments, facilitating the exchange of data and functionality between different platforms or technologies.
  6. Improved Message Handling: Self-contained messages allow for better error handling and fault tolerance. Services can validate the integrity and completeness of the incoming messages, ensuring that all the required information is present before processing. This reduces the likelihood of errors and allows services to handle exceptional cases gracefully, improving the overall reliability of the system.

Are Stateless and Reentrant

Composable Services are stateless and capable of processing a single request or event message with a single thread to optimize efficiency. They are fully reentrant so that they can process multiple requests or events concurrently.

Benefits:

  1. Scalability: Services can be scaled horizontally by running multiple instances of the service. Since they are stateless, each instance can handle requests or events independently without relying on shared state. This allows for easier and more efficient horizontal scaling to meet increasing demand.
  2. Fault Isolation: Each service processes requests or events independently, any faults or failures that occur in one service instance do not directly affect others. This fault isolation enhances the robustness and reliability of the overall system, as failures are contained within individual services and can be handled or recovered from more easily.
  3. Performance Optimization: By processing a single request or event message with a single thread, composable services can optimize their efficiency. They can focus on executing the specific task at hand without being burdened by managing complex state or synchronization. This can result in improved response times and throughput.
  4. Concurrency and Parallelism: Services are designed to be fully reentrant, allowing them to process multiple requests or events concurrently. This enables efficient utilization of computing resources, as multiple requests can be processed simultaneously without blocking or waiting for each other. It enhances system responsiveness and can lead to better overall performance.
  5. Testing and Maintenance: The stateless nature of services simplifies testing and maintenance. Each service can be tested in isolation, allowing for easier verification of its functionality and behavior. Similarly, maintenance tasks such as upgrades or bug fixes can be performed on individual services without impacting the entire system.

Are Reactive

Services react to incoming request or event messages by executing logic, sending messages, or publishing events. They may also read from and write to persistent storage if they represent persistent entities.

Benefits:

  1. Responsiveness: Reactive services are designed to respond quickly to incoming requests or events. By reacting promptly to messages, they can provide near real-time responsiveness, reducing latency and improving user experience. This is particularly important in scenarios where timely responses are crucial, such as real-time applications or event-driven systems.
  2. Scalability: Reactive services are built to handle high loads and scale horizontally. Leveraging asynchronous and non-blocking communication models helps to handle a large number of concurrent requests or events efficiently. This scalability allows the system to handle increased demand by adding more service instances, distributing the workload across them, and achieving better resource utilization.
  3. Resilience: Reactive services emphasize resilience by adopting mechanisms such as fault tolerance, error handling, and self-healing. They are designed to handle failures gracefully and recover from errors without compromising the overall system stability. By reacting to messages and events, they can better detect and respond to failures, adapt to changing conditions, and maintain system availability.
  4. Message-Driven Communication: Reactive services rely on message-driven communication, promoting loose coupling and asynchronous interactions between services. This enhances decoupling, flexibility, and better isolation of service components, better enabling independent development, deployment, and evolution.
  5. Event-Driven Architecture: Reactive services can align well with event-driven processing, where events are used to trigger and propagate changes throughout the system. By reacting to events, services can respond to changes in the system or external environment, enabling real-time updates and event processing. This architecture promotes loose coupling, extensibility, and adaptability to evolving business requirements.
  6. Flexibility and Agility: The reactive nature of services allows for greater flexibility and agility in the system. Services can be more easily composed, replaced, or updated without disrupting the overall system. New services can be added or existing services can be modified to respond to new requirements or business needs, facilitating system evolution and adaptability.

Implement a Domain-Bounded Context

Each service has a clear domain scope and focuses on a specific functional or business capability.

Benefits:

  1. Modular and Maintainable Architecture: By dividing the system into services with clear domain scopes, you create a modular architecture that is easier to understand, develop, and maintain. Each service can focus on a specific set of functionalities or business capabilities, making it easier to reason about and modify independently.
  2. Independent Deployability: Services with well-defined domain scopes can be deployed independently, without affecting other services in the system. This allows for more flexible and agile deployment processes, as updates or changes can be made to individual services without requiring a full system deployment. It also enables different services to be developed, tested, and released independently, promoting faster time to market.
  3. Ownership and Responsibility Boundaries: Each service has a clear ownership and responsibility for a specific domain or business capability. This promotes accountability and clear delineation of responsibilities among development teams. It allows teams to focus on their specific areas of expertise and make decisions independently, leading to more efficient development and better overall system quality.
  4. Reusability and Interoperability: Services with clear domain scopes can be designed to expose well-defined interfaces, making them more reusable and interoperable. Other services or applications can more easily integrate and communicate with specific services to leverage their functionalities. This promotes code reusability, reduces duplication of effort, and enables better integration possibilities.
  5. Encapsulation of Business Logic: By focusing on specific functional or business capabilities, services encapsulate the corresponding business logic within their boundaries. This enhances maintainability and reduces complexity by keeping related logic and data together in a single service. It also enables easier testing, debugging, and troubleshooting of specific functionalities.
  6. Team Autonomy and Decentralization: Services with clear domain scopes empower development teams to work autonomously and make independent decisions within their areas of responsibility. This can lead to greater innovation, faster development cycles, and improved team efficiency. It also promotes decentralized decision-making and reduces the coordination overhead between teams.

Use a Single Service Class for Entity Access

Access to persistent data entities is through a single service class dedicated to that specific entity class.

Benefits:

  1. Code Organization: By having a dedicated service class for each data entity, the codebase becomes more organized and structured. All the operations related to a specific entity are encapsulated within a single class, making it easier to locate and maintain the relevant code. This promotes better readability and comprehension of the system’s logic, leading to improved development efficiency.
  2. Reusability: A single service class for a data entity can be reused across different components or modules of an application. This reusability eliminates code duplication and promotes a modular design approach. Other parts of the application can easily utilize the services provided by the dedicated class, reducing development effort and ensuring consistent handling of the entity throughout the system.
  3. Maintainability: With a dedicated service class, maintaining and updating the logic related to a specific data entity becomes simpler. Changes or enhancements to the entity’s behavior can be confined within the boundaries of the service class, minimizing the impact on other parts of the system. This modular approach makes it easier to track and manage changes, improving the overall maintainability of the codebase.
  4. Testability: Having a single service class for a data entity enhances the testability of the system. Since the service class encapsulates all the operations related to the entity, it can be easily isolated for unit testing. This focused testing approach allows developers to write comprehensive test cases specifically targeting the functionality and behavior of the entity, ensuring the correctness of its operations.
  5. Encapsulation and Abstraction: The dedicated service class acts as an abstraction layer for the data entity. It encapsulates complex operations and provides a simplified interface for interacting with the entity. This abstraction promotes loose coupling between different components of the system, enabling changes to the internal implementation of the entity with less chance of affecting the consuming code. It also enhances the security and integrity of the data by enforcing access controls and validation rules within the service class.
  6. Separation of Concerns: A dedicated service class for a data entity helps to maintain a clear separation of concerns within the codebase. It allows the entity-specific logic to be isolated from other parts of the system, such as user interfaces or external integrations. This separation makes the codebase more modular and easier to understand, enhancing the overall design and architecture of the application.

In Summary

The composable services pattern’s extensions to the microservices pattern can offer real benefits over vanilla microservices and traditional SOA patterns. It is a very cost-effective approach to building applications with the following advantages:

  1. Flexibility: Composable services allow for greater flexibility in designing and assembling services. They are intended to be highly modular and loosely coupled, enabling developers to compose services in various combinations to meet specific business requirements. This flexibility can lead to more efficient development and deployment processes.
  2. Granularity: Composable services provide finer-grained control over the functionality of the services. They can be implemented to be more focused and specialized, allowing for more precise composition and reuse of services across different applications or use cases. This level of granularity can lead to more efficient development resource utilization and reduced redundancy.
  3. Scalability: Composable services promote units of functionality that can be individually scaled based on demand. Instead of scaling an entire monolithic application or a large number of microservices, composable services allow for targeted scaling of specific services, optimizing resource allocation and minimizing unnecessary scaling efforts.
  4. Agility: Composable services promote agility in software development and system evolution. With a composable architecture, it becomes easier to adapt and evolve the system over time. New services can be added, existing services can be modified or replaced, and the composition of services can be adjusted without requiring significant changes to the entire architecture. This agility can lead to faster iteration and better responsiveness to changing business needs.
  5. Development Efficiency: Composable services can improve development resource efficiency by allowing services to be shared and reused across different applications or contexts. Instead of developing and maintaining separate microservices for each application, composable services can be leveraged across multiple applications, reducing duplication of effort and optimizing resource consumption.
  6. Performance: Composable Services are stateless and capable of processing a single request or event message with a single thread to optimize processing efficiency. They are fully reentrant and reactive so they can process multiple requests or events concurrently and are built to handle high loads and scale horizontally.

If you found this article useful, a clap would let us know that we’re on the right track.

Thanks!

Read the companion piece, Designing Composable Services.

Take a look at the Composable Services Pattern.

Learn more about The Magic of Message Orchestration.

--

--

Dick Dowdell
Nerd For Tech

A former US Army officer with a wonderful wife and family, I’m a software architect and engineer who has been building software systems for 50 years.