Embracing Microservices : Scoping Services

overview

In this article, we will explore the strategic design pattern of bounded context and how it can be effectively utilized to size and scope microservices.

Let’s begin by introducing the bounded context technique as a method for effectively scoping and sizing microservices. We will explore how employing the ubiquitous language, aids in defining the boundaries of these services. Additionally, we will describe some of main drawbacks of sizing and scoping micro services without utilizing the bounded context approach.

Finally, we will conclude the module by discussing the concept of service aggregation. There are instances where combining services becomes necessary for specific reasons, and we will delve into the importance of this practice.

Little DDD Intro

Domain-Driven Design (DDD) is a software development approach that focuses on modeling complex business domains to create high-quality software solutions. It aims to align the design and implementation of software systems with the intricacies of the real-world business domains they represent. By putting the domain at the heart of the development process, DDD helps teams build software that closely reflects the language, behavior, and rules of the business, resulting in more effective solutions.

At its core, DDD recognizes that every software project revolves around a specific domain, which refers to the subject matter, processes, and rules that define a particular business. This domain is often complex, with various entities, relationships, and behaviors. DDD encourages developers and domain experts to collaborate closely, fostering a shared understanding of the domain and its intricacies.

One of the key concepts in DDD is the ubiquitous language. It emphasizes the importance of establishing a common language between developers and domain experts. This shared language becomes the foundation for modeling the domain in software, ensuring that the software artifacts, including code and documentation, accurately reflect the business concepts. The ubiquitous language promotes effective communication, reduces ambiguity, and enhances collaboration throughout the development lifecycle.

DDD also introduces the concept of bounded contexts, which help manage the complexity of large domains. A bounded context defines a clear boundary within which a specific model, along with its rules and terminology, is valid. By dividing a large domain into smaller, more manageable bounded contexts, teams can focus on understanding and implementing specific parts of the system without being overwhelmed by the entire domain. Each bounded context can have its own model, optimized for its particular purpose, while still maintaining the ability to interact with other bounded contexts.

Another crucial aspect of DDD is the emphasis on strategic design. This involves identifying and refining the core business domains and aligning them with software design decisions. Strategic design decisions consider long-term goals and the evolution of the software system over time. It involves defining aggregates, which are consistency boundaries that encapsulate and enforce business rules, as well as identifying entities, value objects, and services that model the behavior and data of the domain.

Tactical design, on the other hand, focuses on the implementation of individual models within a bounded context. This includes designing aggregates, entities, value objects, repositories, and other domain-specific components. Tactical design decisions aim to ensure that the models are expressive, maintainable, and aligned with the ubiquitous language.

DDD encourages also iterative development, where developers continuously refine and improve the models and codebase based on their understanding of the domain. By closely involving domain experts and constantly seeking feedback, developers can iterate on the software design and ensure it accurately reflects the evolving domain knowledge.

Bounded Context & Microservices Scoping

So as we commented previously, a bounded context represents a specific responsibility and is defined by a clear boundary that aligns with our definition of microservices. Microservices should have a singular responsibility, which can take the form of a business function or a business domain.

To design a microservice as a bounded context, we start by identifying the core domain concepts, such as main functions or subdomains. For instance, in a parcel delivery system, key core concepts could include delivery driver and customer orders. Organizing and scheduling a delivery would be a domain function within the parcel delivery domain, while driver and customer orders might be subdomains within that same domain. Each of these core domain concepts becomes the foundation for individual bounded contexts.

Within each bounded context, there are also internal models that support the closely related concepts within that context. Collaboratively, domain experts and software developers work together to define the internal language specific to the bounded context. This language becomes the internal means of communication within the bounded context.

A bounded context not only has a boundary to protect its internal context and responsibilities but also provides interfaces for interaction with other bounded contexts. In terms of microservices, this boundary takes the form of an interface that clearly defines the inputs and outputs of the microservice. Communication between bounded contexts occurs using shared models, ensuring that internal models within a context are not used to communicate with other external bounded contexts. This independence guarantees that each bounded context remains isolated from others, and the shared models facilitate their interaction.

This approach to bounded contexts aligns with the design of microservices. Microservices should maintain separate internal models from external models, which form part of the microservices’ contract. By separating these models, changes to internal models do not affect the external contracts, ensuring stability and consistency.

Considering the description thus far, the approach to bounded contexts as a starting point is consistent with microservice design. Similar to a microservice, a bounded context belongs to a team consisting of both domain and software experts. It has its own code repository and database to store context-specific data. The boundary of a bounded context defines the contract for the microservice, clearly stating its inputs and outputs and providing guidance on how to interact with the microservice.

While most of the time, a bounded context corresponds to a single microservice, there are cases where a bounded context may need to be divided into multiple microservices due to additional functionality within that context. Additionally, within a bounded context, you may encounter a model that partially relates to the core responsibility of the context but actually belongs to another bounded context that interacts with it. In microservices terminology, this model would be retrieved from another microservice that the current microservice depends on. Although the model may have some relevance within the context, it actually belongs to a different bounded context, and the microservice relies on it for specific purposes.I

In general, this demonstrates how bounded contexts align with microservice architecture. Bounded contexts provide a clear structure for teams, ensure isolation, define contracts, and enable effective communication and collaboration between microservices.

Drawbacks from Unbounded Approach to Microservices

Before going in deep into how to utilize bounded context and ubiquitous language techniques to define our microservices, it’s essential to understand the drawbacks of not employing these techniques in microservice design. Initially, when working with a team to identify core concepts that could potentially serve as candidates for our services, we encounter concepts such as drivers, customer orders, and delivery, along with supporting concepts for each of them within the context of a parcel delivery system. As we begin modeling these concepts and creating initial designs for our microservices, things may appear to progress smoothly at first due to the limited number of supporting and key concepts involved.

However, complications arise when we encounter overlapping concepts or concepts that partially relate to different areas while also being connected to our core concept. Without a well-defined language and clear boundaries for each of our services, these overlaps lead to significant confusion. As a result, our microservices start merging into a single monolithic microservice, defeating the purpose of having distinct services. This lack of boundaries and agreed-upon language allows for the inclusion of concepts that should be filtered out, ultimately breaking several microservice design principles.

This amalgamation of microservices results in low cohesion and excessive coupling between them, undermining the benefits of a microservices architecture. To avoid this situation, it is crucial to utilize techniques like bounded context and ubiquitous language to establish and reinforce the scope of our microservices. These techniques provide a clear structure, well-defined boundaries, and a shared language, enabling effective communication, reducing confusion, and maintaining the integrity of our microservices architecture.

HOW TO SCOPE USING BOUNDED CONTEXTS

In this section we’ll explore the bounded context technique and how it helps define the scope of our micro services. The first step is to identify the core concept within our domain. To achieve this, we collaborate with domain experts and software experts who are familiar with specific business functions or subdomains.

Differentiating between core concepts and supporting contexts is crucial. We visualize a boundary around the core concept to determine what falls within the context of our bounded context and what lies outside. Within the bounded context, we define supporting concepts that directly relate to the core concept. Simultaneously, we begin shaping the language specific to the bounded context.

For a supporting concept to be included within a bounded context, its natural language should align with the core concept. If necessary, we may need to rename supporting concepts to better match the core concept’s natural language.

We also need to identify any supporting concepts that don’t fit the natural language of the core concept or appear larger than the core concept itself. If we have a single supporting concept outside our bounded context or within it but not linked to any other supporting concepts, it suggests integration with an external bounded context. For instance, the order supporting context within the delivery bounded context indicates a need for integration with the customer orders bounded context to retrieve orders. Similarly, the driver supporting concept, located outside the boundary, suggests that a bounded context for driver should integrate with our delivery bounded context.

Working with aggregates

In this line, let’s  discuss briefly the concept of service aggregation. This involves combining separate services into a larger, unified service. This aggregation is an additional step that comes after decomposing your microservices into smaller components using the bounded context method.

There are various reasons why service aggregation might be necessary after breaking down your architecture using the bounded context approach. One reason could be the need for reporting functionality. You may have to extend the functionality of a service to provide reporting capabilities to an external entity. Another reason could be enhancing the micro service’s functionality to meet the requirements of other tools, APIs, or client applications.

Aggregating services can make your service/system more usable for client applications. By consolidating all the necessary functionality within a single service, you simplify the integration process. Performance optimization can also be a driving factor for service aggregation. Retrieving data from one service might be more efficient than making calls to multiple services.

Regardless of the reason, it’s crucial to first utilize the bounded context technique to break down your architecture into micro services. Only then should you consider the aggregation or combination of services if there are clear advantages to be gained.

So in summary these are the main reasons for introducing aggregations:

  • Reporting
  • Enhanced Functionality
  • Improving Services Performance
  • Usability

By employing a strategic approach, such as the bounded context technique, and carefully considering the benefits of service aggregation, you can design a robust and efficient micro services architecture.

That´s all for today, happy building