Saga of Bounded Context

Reeshabh Choudhary
6 min readNov 21, 2024

👷‍♂️ Software Architecture Series — Part 32

In the history of humankind, no entity or domain has evolved faster than software development. And the adaption of distributed systems was definitely an epoch. However, nothing has made life of software architects more difficult than the notion of ‘bounded context’ in distributed systems.

Early Days

In the earlier stages, distributed systems often relied on a centralized relational database for handling critical concerns like data integrity, transaction management, and consistency. With this centralized structure, architects used to focus mainly on the logic of distributed processing, without worrying much about data integrity. Transactions were ACID compliant, which meant operations will either succeed entirely or roll back completely. Developers did not had the responsibility to handle issues like deadlocks, rollbacks or isolation levels.

Introduction of Microservices

Once the microservices architecture was introduced, not just the processing, but the system even got divided and distributed. Now, architects were not dealing with just one monolith system, with all the logic at one place, but the business logic also go separated, either on the basis of scope or functionality or domain or other factors as deemed suitable. Initially, a central data repository for the application used to be maintained, irrespective of the separation of services. However, as the enterprises started growing bigger and ambitious, the notion of maintaining the business model of the organization as one single entity was too complex to comprehend. Architects started favoring more modular approach of designing and maintaining the system, with different modules interoperating to support coordination of various business exercises. These systems usually maintain their own version of the shared data.

However, soon architects realized that these different components or subsystems stitched together with ad-hoc interfaces ran into consistency issues at every integration point. For example, if one component or subsystem updates a piece of data but another subsystem does not reflect the change, the system will run into an inconsistent state. Maintaining transactional integrity is also a challenge in such systems because coordination across the subsystems can fail due to many factors (e.g. network issues). System would start favoring ‘eventual consistency’ however, this explicitly means that there would be temporary inconsistencies where some subsystems will have updated data while others may not have.

In a microservice set up, it is not unusual that different teams are working on their respective services and more often than not these teams (or services) will misinterpret domain models differently. Say, one service (or team) may define a particular entity say ‘customer’ with certain specific attributes while other service (or team) may have other additional attributes defined for the same entity or attributes may have different format for same data. These services interact with each other through API contract which specifies the structure, semantics, and expectations for data exchange between services or components. When the models are misaligned, there will be integration issues. During data transformation process within systems, there will be data mapping errors. A common example is numeric values with different units (e.g., kilograms vs. pounds, or dollars vs. cents) or text fields with different encodings or delimiters. If the source subsystem contains more detailed information than the target can accommodate, some data might get dropped during transformation. For example, if one system supports multiple phone numbers for a customer but another only supports one, extra phone numbers might be lost in the process of mapping. Incorrect transformations will result in incomplete or invalid data being passed to the target subsystem.

Bounded Context

Eric Evans introduced the concept of Domain Driven Design, which focussed on the principle of bounded context, to solve the issues discussed above. A bounded context for a service in microservice means that each service models a domain or workflow. This means a service will include ‘everything’ required to operate within application. And this ‘everything’ means classes, subcomponents as well as database schemas. Since, microservices are built with the intention to avoid coupling, this choice makes the architects to favour duplication than coupling.

Let us understand this better with a practical explanation:

In a customer driven large enterprise system adopting to domain based microservices pattern, the concept of “Customer” will be perceived differently by different services (or teams (people defining the services)). For example, a “Billing Subsystem” which manages invoices, payments, and financial transactions related to customers, has following attributes for the “Customer” entity:

· CustomerID

· BillingAddress

· Balance

· PaymentType

· Invoice

Another subsystem, “CRM Management” which tracks customer relationships, interactions, and marketing activities, has following attributed defined for the “Customer” entity:

· CutsomerID

· CustomerName

· Email

· Phone

· MarketingPreference

The challenges discussed so far are quite evident from the above hypothetical yet practical description of a single entity. One could argue for the case of “Customer” entity being forced into a single unified model; however, challenges are greater than rewards in this case. Every subsystem will have to parse a bloated entity loaded with irrelevant attributes. Moreover, different subsystem’s interpretation of the attributed might be quite different. For example, The CRM system may have more granular contact details definition of a customer than the billing system. No points for guessing that introducing change in one subsystem will inadvertently affect other systems.

The Bounded Context intends to solve these issues by isolating the definition of the “Customer” entity within the specific contexts of each subsystem. This means at code level the definition of the entity is always different in each bounded context. Each context focuses only on the aspects of the entity relevant to its domain. This helps to prevent coupling between contexts, which means changes in one context’s business logic or data model do not impact another.

At the database level, architects can choose a design as suitable, i.e. either each context has its own database and stores only the data it requires or a single database stores customer specific data and each context access only relevant columns or views, or each context has its own customer specific table in the shared database. There are pros and cons of both approaches, however one thing is certain, a bounded context often encapsulates its own transactional boundary and thus makes transactionality a first-class architectural concern.

Headache or Solution?

As there are no global transactions (ACID) anymore in a distributed system adhering to domain driven design, distributed transactionality across services need to be managed using patterns like sagas, event sourcing, etc., which are more complex than the traditional way of transaction handling. For example, in saga pattern, a distributed transaction is broken down into a sequence of smaller, independent operations which are managed by individual services. Each service performs its respective operation and publishes and event to trigger the next step. This further requires a compensation logic to handle failures. For example, if an “order service” reserves inventory of an item but the payment of the item fails, then the reservation by order service has to be rolled back. Further, as per the ease of operation, architects may choose to choreograph events across services on their own or may design a central orchestrator to the flow of transaction.

It is quite evident that more players we introduce in a system, more expense we incur at managing them. The point of this small discussion was to again reiterate the message that every architectural decision has its own trade-offs. We must be carefully analysing whether the costs associated with the decision are worthy enough for sustainability of the system in the longer run. Reason being the second law of thermodynamics, which states that any spontaneous process increases entropy of the universe. Again, we can see that laws of nature are constant everywhere.

--

--

Reeshabh Choudhary
Reeshabh Choudhary

Written by Reeshabh Choudhary

Software Architect and Developer | Author : Objects, Data & AI.

No responses yet