Introducing Bounded Contexts in a monolithic application - Robert Baelde - DDD Europe 2022

Introducing Bounded Contexts in a monolithic application - Robert Baelde - DDD Europe 2022

Introduction to Bounded Context in Monolithic Applications

The Challenge of Complexity in Monolithic Applications

  • The speaker introduces the topic of implementing bounded context within a monolithic application, emphasizing the need for rapid value delivery in new projects.
  • As projects grow, complexity increases with more features and team members, leading to a decline in productivity despite initial success.
  • Productivity can decline exponentially as complexity rises, creating challenges for developers who struggle to manage the mental load of understanding the entire application.
  • Developers face difficulties predicting the impact of changes during planning sessions due to hidden complexities within the codebase.
  • Onboarding new engineers becomes challenging as they encounter an intertwined codebase without clear starting points or responsibilities.

Implicit Expertise and Problem Solving

  • Implicit expertise complicates project management; it's often unclear who has knowledge about specific aspects of the application, making feature planning difficult.
  • To address these issues, developers explore Domain-Driven Design (DDD), which advocates for introducing boundaries within applications.

Understanding Bounded Context

  • A bounded context is defined as an independent and loosely coupled component that communicates minimally with others while maintaining its own data and language.
  • Each bounded context should have explicit ownership assigned to individuals or teams responsible for its maintenance and development.

Coupling vs. Loosely Coupled Contexts

  • Strongly coupled contexts lead to complex interdependencies and excessive communication, resembling "spaghetti" architecture; merging them may be necessary if they are too similar.
  • In contrast, loosely coupled contexts allow for independent functionality where components can operate without relying heavily on one another.

Ubiquitous Language Across Contexts

  • Ubiquitous language is crucial; different departments may interpret concepts differently based on their roles—illustrated by how various teams at Amazon view a "book."
  • This variation highlights how understanding different perspectives can help identify bounded contexts effectively.

Data Sharing and Contracts Between Services

  • Sharing data between services creates implicit contracts that complicate interactions; each context must guard its own data to maintain integrity.

Introduction to Microservices and Bounded Contexts

The Initial Experience with Microservices

  • The speaker recounts their first job at a small startup where the advice was to start with microservices, which led to an unreliable application demo featuring an error page.
  • Despite the potential of microservices, the experience highlighted that they can lead to issues if not properly managed, especially for developers unfamiliar with them.

Challenges of Implementing Microservices

  • Microservices are beneficial primarily in large organizations equipped with adequate resources and expertise; smaller entities often lack the necessary knowledge or time.
  • High upfront design costs are associated with microservices due to the need for defining boundaries and communication methods; poor design can result in inefficiencies.
  • Changing boundaries within microservice architectures is costly and complex, requiring significant refactoring efforts when initial designs prove inadequate.

Productivity vs. Complexity in Microservices

  • A graph illustrating productivity versus complexity shows that while productivity may initially drop due to boundary adjustments, it can stabilize post-refactor—this is manageable for larger organizations but problematic for rapid validation needs.

Communication Patterns Between Bounded Contexts

  • The speaker emphasizes that it is possible to introduce boundaries effectively within monolithic structures by utilizing messaging or direct calls through well-defined interfaces.
  • Messaging allows loose coupling between contexts since consumers only need to understand event schemas without needing details about their origins.

Handling Messages Effectively

  • Storing messages can simplify future context introductions by avoiding complex migrations; having a history of messages aids in state reconstruction.
  • Asynchronous handling of messages is recommended to prevent system slowdowns caused by synchronous processing of multiple events triggered by a single action.

Code Examples and Practical Implementation

  • An example involving an "order placed" event illustrates how events can be published on a message bus, allowing consuming contexts to react appropriately without tight coupling.

Understanding Contexts in Software Development

The Importance of Providing Context

  • The order context is crucial; without it, challenges arise. A providing context exposes an interface that informs the consuming context about available methods and expected return types.
  • The consuming context only needs to understand the interface, not the implementation details, which are provided at runtime through dependency injection.

Implementation Example

  • An example involves a public user repository with methods to retrieve user data by ID. This highlights how interfaces define expected interactions.
  • Tests for the consuming context can be conducted independently of the providing context's implementation, allowing for effective mocking against contracts.

Steps to Introduce Context in Existing Monoliths

  • To refactor an existing monolith, start by creating a context map to design before coding. This step is essential for clarity and organization.
  • Move classes to their respective contexts using IDE tools that facilitate refactoring while ensuring namespace changes are applied throughout.

Detecting Dependencies Between Contexts

  • It's important to identify where one context depends on another's classes to avoid tight coupling. Static code analytics can help detect these dependencies.
  • A well-defined context map results from collaborative workshops and illustrates how different contexts interact within the system.

Refactoring for Improved Structure

  • After moving classes into appropriate contexts, a clearer structure emerges, reducing interdependencies and improving maintainability.
  • Utilize static code analysis tools (e.g., PHPStan) to measure cross-context usage and coupling levels between different contexts.

Measuring Coupling and Communication Patterns

  • Analyze communication patterns between contexts; excessive direct calls or message exchanges may indicate poor boundaries or design flaws.
  • Set measurable targets for reducing coupling over time as part of team goals, fostering better architectural practices.

Advantages of a Modular Monolith

  • Establishing clear boundaries between contexts enhances development efficiency and reduces complexity in managing dependencies.

Microservices vs. Modular Monoliths: Finding the Right Balance

Advantages of Modular Monoliths

  • Modular monoliths allow for clear boundaries without the added complexity associated with microservices, simplifying DevOps processes.
  • Redesigning boundaries within a modular monolith is more straightforward; developers can temporarily allow some coupling during refactoring before fully committing to new communication patterns.
  • Setting up a modular monolith is generally easier and faster than establishing a microservice architecture, which often requires significant upfront investment in tools like Docker.

Transitioning Between Architectures

  • Many companies oscillate between monolithic and microservice architectures due to past experiences, often leading to dissatisfaction with both approaches.
  • A modular approach serves as an effective middle ground, allowing teams to manage productivity while gradually introducing necessary boundaries as needed.

Refactoring Strategies

  • When productivity declines, it may be time to implement context mapping and design changes; this proactive approach helps maintain efficiency.
  • The transition from a modular monolith to microservices can be manageable when done at the right time, leveraging existing communication patterns established during earlier phases.

Conclusion and Further Engagement

Video description

http://dddeurope.com - https://twitter.com/ddd_eu - https://newsletter.dddeurope.com/ https://linkedin.com/company/domain-driven-design-europe Organised by Aardling (https://aardling.eu/) Introducing bounded contexts often is a great first step to get started with DDD. During this session you'll learn how a legacy application can be gradually refactored into bounded contexts. We'll also explore ways a team could keep track of their progress, and identify the level of decoupling of contexts within their application. About Robert Baelde The magic place where tech and business collide is where Robert finds the most interesting lessons to be learned. He has a passion for helping tech teams grow on multiple levels, from the low-level code to the overall processes in place. He's passionate about everything Event Sourcing and Domain Driven Design has to offer. Robert's talks are high-energy, practical, and to the point.