Learning Domain-Driven Design by Vlad Khononov offers a comprehensive guide to applying Domain-Driven Design (DDD) principles to align software architecture with business strategies. The book is praised for its practical approach and insights into solving real business problems using DDD.
Khononov emphasizes the importance of understanding the business domain and competitive strategy to design effective software solutions. The text explores the relationship between DDD and other methodologies, providing guidance on making architectural decisions that fulfill business requirements.
Key concepts include:
- Business Domain Analysis: Understanding the system’s fit within a company’s strategy by identifying subdomains and their boundaries.
- Ubiquitous Language: Establishing a common language for all stakeholders to improve communication and model consistency.
- Bounded Contexts: Managing domain complexity by defining clear boundaries and interactions between different parts of the system.
- Integration Patterns: Techniques like Shared Kernel and Anticorruption Layer help coordinate bounded contexts and maintain model integrity.
The book also delves into tactical design patterns, such as:
- Transaction Script and Active Record: Simple patterns for straightforward business logic.
- Domain Model and Event Sourcing: Advanced patterns for complex logic, focusing on time modeling and event-driven architecture.
Khononov discusses architectural patterns, including:
- Layered Architecture and Ports & Adapters: These structures separate concerns and facilitate integration.
- Command-Query Responsibility Segregation (CQRS): Segregates read and write models to optimize performance and scalability.
Communication patterns like Model Translation and Saga are explored to ensure smooth interactions between aggregates and systems.
In practice, the book advises on:
- Design Heuristics: Guidelines for implementing bounded contexts and business logic.
- Evolving Design Decisions: Adapting to changes in business domains and organizational structures.
The book introduces EventStorming, a collaborative workshop technique for exploring complex domains and identifying key events and processes.
Khononov addresses the application of DDD in real-world scenarios, emphasizing strategic and tactical modernization, and fostering a ubiquitous language. He also explores relationships with other methodologies, such as microservices and data mesh, highlighting how DDD principles can enhance these approaches.
Overall, “Learning Domain-Driven Design” serves as a valuable resource for both newcomers and experienced practitioners, offering insights into effectively aligning software design with business objectives.
Domain-driven design (DDD) is a collaborative methodology for software development that emphasizes the importance of understanding the business domain. Coined by Eric Evans in 2003, DDD aims to tackle complexity by fostering a partnership between software developers and domain experts. This approach involves two main stages: strategic design, which focuses on understanding and breaking down the business problem, and tactical design, which translates these insights into software architecture and implementation. DDD encourages communication in the language of the domain, enhancing clarity and reducing complexity.
The DDD community has grown since the publication of Evans’s “The Blue Book,” sharing experiences and tools to advance DDD practices. Vlad Khononov’s book “Learning Domain-Driven Design” provides a fresh perspective on DDD, making it accessible for newcomers and valuable for experienced practitioners. The book not only explains DDD principles but also covers evolved practices like EventStorming, discusses the impact of business changes on software, and explores the integration of DDD with microservices and other software patterns.
Khononov’s journey into software engineering began with a focus on understanding business logic, which he found to be the core of software development. His experience with DDD was transformative, leading him to teach the methodology and write this book. The book aims to democratize DDD, making it simpler and more accessible for software engineers at all levels. It provides tools for effective software modeling and implementation, emphasizing the importance of understanding the business context to avoid under- or over-engineering solutions.
The book is structured into four parts: strategic design, tactical design, DDD in practice, and DDD’s relationships with other methodologies. It covers topics such as establishing a business domain’s context, using a ubiquitous language for communication, designing system architecture with bounded contexts, and implementing business logic through various patterns. Advanced topics include event-sourced domain models, architectural patterns for component structuring, and the evolution of software design over time.
Khononov also addresses the challenges of introducing DDD to existing projects and explores the synergy between DDD and microservices, event-driven architecture, and data mesh architecture. The book includes exercises to reinforce learning, using a fictional company, WolfDesk, as a case study to illustrate DDD concepts.
In summary, “Learning Domain-Driven Design” provides a comprehensive guide to understanding and applying DDD principles and practices, offering valuable insights for both newcomers and seasoned practitioners. It emphasizes the importance of aligning software design with business goals and adapting to changes over time, ensuring the software remains effective and manageable.
Domain-driven design (DDD) addresses the persistent issue of software project failures, often attributed to communication problems. Despite various methodologies like Agile and DevOps, the “software crisis” persists, with many projects failing to meet time, budget, or client requirements. DDD offers a strategic and tactical approach to align software design with business domains, fostering effective communication among stakeholders.
Strategic Design in DDD:
- Business Domain Analysis: Understanding a company’s business domain and subdomains is crucial. A business domain defines the main area of activity, whereas subdomains are finer-grained areas necessary for achieving business goals.
- Types of Subdomains:
- Core Subdomains: Provide competitive advantage and involve complex, unique processes or intellectual property. For example, Uber’s ridesharing model and Google’s search algorithm.
- Generic Subdomains: Common across companies, like authentication systems, where existing solutions are preferred.
- Supporting Subdomains: Essential but do not provide competitive advantage, often involving simpler business logic.
Tactical Design in DDD:
- Ubiquitous Language: Cultivating a shared language among stakeholders to ensure clear communication and understanding of the business domain.
- Bounded Contexts: Defining clear boundaries within which a particular model is applicable, aiding in transforming business knowledge into software models.
- Integration Patterns: Understanding technical and social constraints to design effective system integrations, using tools like context maps to visualize collaborations.
DDD emphasizes the importance of aligning software design with business strategy, making it easier to maintain and evolve systems. By focusing on strategic and tactical patterns, DDD aims to reduce project failures and enhance the effectiveness of software engineering.
In the context of a company’s strategy, subdomains are categorized into core, supporting, and generic types, each with distinct roles and implications for competitive advantage and software design.
Core Subdomains: These are crucial for a company’s competitive edge and are characterized by high complexity and volatility. They require in-house development to maintain uniqueness and adaptability. Core subdomains often involve complex business logic and are strategic investments that demand advanced engineering techniques. Their continuous evolution is essential to maintain competitiveness.
Generic Subdomains: These represent complex but already solved problems, such as encryption algorithms or authentication mechanisms. They offer no competitive advantage and are best addressed through off-the-shelf or open-source solutions. The knowledge for these subdomains is readily available, making it unnecessary to develop them in-house.
Supporting Subdomains: These have simple business logic and low complexity, often resembling basic ETL operations or CRUD interfaces. They do not provide competitive advantage and can be outsourced or implemented using rapid application development frameworks. These subdomains are stable and change infrequently, making them suitable for training less experienced staff.
Identifying Subdomains: Determining subdomain boundaries is crucial for informed software design decisions. Subdomains align with a company’s business strategy and are often reflected in organizational structures. However, identifying them requires a detailed domain analysis, often starting with coarse-grained areas like departments and refining into finer-grained components, such as specific business functions or use cases.
Design Decisions: Understanding subdomains aids in strategic design choices. Core subdomains should be developed in-house with a focus on innovation and adaptability. Generic solutions should be adopted for generic subdomains to save resources. Supporting subdomains may be outsourced due to their simplicity and stability.
Case Studies: Two fictional companies, Gigmaster and BusVNext, illustrate these principles. Gigmaster’s core subdomains include its recommendation engine and data anonymization, while BusVNext focuses on a complex routing algorithm for optimizing bus rides. Both companies leverage generic subdomains like encryption and authentication through existing solutions, while supporting subdomains involve integrations and simpler functionalities.
In summary, effectively categorizing and understanding subdomains enables companies to strategically allocate resources, optimize software design, and maintain competitive advantages through targeted innovation and efficient use of existing solutions.
BusVNext focuses on key subdomains essential for managing a bus fleet, including routing, analysis, mobile app user experience, and fleet management. These core subdomains are critical for maintaining competitive advantage and require in-house development using advanced technical tools. Generic subdomains, such as traffic conditions, accounting, billing, and authorization, are common across companies and can be outsourced to external providers. Supporting subdomains, like promotions management, support the core business without providing competitive advantage and can also be outsourced.
Domain experts play a crucial role in domain-driven design (DDD). They possess deep knowledge of the business domain and help translate business problems into software requirements. Understanding domain experts and using their terminology is essential for creating effective software solutions. Effective communication between domain experts and engineers is vital, as miscommunication can lead to project failure.
The concept of a ubiquitous language is central to DDD, ensuring all stakeholders use a common language to describe the business domain. This language should be free of technical jargon and reflect the business domain accurately, fostering a shared understanding. Ambiguity and synonymous terms should be avoided to maintain clarity and consistency.
Modeling in DDD involves creating a simplified representation of the business domain, emphasizing necessary aspects while omitting others. This abstraction helps manage complexity and ensures the model aligns with the business’s purpose. Effective models capture domain experts’ mental models, enabling precise implementation of business logic.
In summary, BusVNext’s strategic design decisions are guided by understanding core, generic, and supporting subdomains, leveraging domain experts, and employing a ubiquitous language to create effective domain models. These practices ensure alignment between business goals and software development, ultimately leading to successful project outcomes.
The text discusses the concept of a ubiquitous language in domain-driven design (DDD), emphasizing its role in bridging the communication gap between technical and business teams. A ubiquitous language should capture essential aspects of the business domain to facilitate system implementation without overwhelming stakeholders with unnecessary details. Effective communication with domain experts is crucial, especially in complex domains, to prevent misunderstandings that can lead to severe bugs.
The development of a ubiquitous language involves continuous interaction with domain experts to uncover inaccuracies and assumptions. It should be consistently used across all project communications, including requirements, tests, documentation, and source code. This language must evolve as new domain insights are gained.
Tools like wikis and Gherkin tests can help manage a ubiquitous language. Wikis serve as glossaries for terminology, aiding new team members in onboarding. However, glossaries mainly capture nouns and struggle with documenting behaviors, which are better represented in use cases or Gherkin tests. Gherkin tests, readable by domain experts, help verify expected system behavior and bridge gaps between experts and engineers.
Challenges arise in cultivating a ubiquitous language due to the tacit nature of domain knowledge, often requiring co-creation with domain experts. Ambiguities and inconsistencies in domain experts’ mental models must be addressed. When introducing DDD to existing projects, patience is needed to align current language with DDD principles.
The text also introduces the concept of bounded contexts, which divide the ubiquitous language into smaller, consistent languages applicable within specific contexts. This approach resolves inconsistencies, such as different interpretations of a term like “lead” in marketing and sales departments. Bounded contexts define the applicability of a model and its language, ensuring consistency within each context.
A ubiquitous language is not universal but specific to its bounded context, focusing on relevant models and terminology. The scope of a bounded context is a strategic design decision, balancing the need for consistency with the utility of the model. Models should be as wide or narrow as necessary to be effective, avoiding unnecessary complexity or oversimplification.
Overall, the text emphasizes the importance of a shared language in software projects, supported by tools and continuous collaboration with domain experts to manage domain complexity effectively.
In domain-driven design (DDD), bounded contexts and subdomains are key concepts for managing domain complexity. Subdomains are discovered based on business strategy and requirements, while bounded contexts are designed to define clear boundaries within which models operate. The size of a bounded context should align with business needs and organizational constraints, balancing integration overhead and the ability to scale independently.
Bounded contexts serve as both physical and ownership boundaries. Each context should be implemented, evolved, and maintained by a single team, preventing implicit assumptions and requiring explicit communication protocols for integration. This segregation allows each context to use the most suitable technology stack and evolve independently.
The relationship between subdomains and bounded contexts is strategic. While a one-to-one alignment between them can be reasonable, different strategies may be more suitable depending on the project’s context. Bounded contexts ensure that a ubiquitous language remains consistent within a boundary, though terms may vary across different contexts.
In real life, bounded contexts are akin to semantic domains, where the same term can have different meanings depending on the context. For example, a tomato is a fruit in botany, a vegetable in culinary arts, and a feedback mechanism in theatrical performances. Similarly, scientific theories like Newton’s and Einstein’s models of gravity are useful in their respective contexts.
The design of bounded contexts involves strategic decisions about boundaries, trade-offs, and integration. Integration between contexts requires defining contracts to manage differences in models and languages. These contracts are crucial for ensuring that the components of a system work together to achieve overarching goals.
To integrate bounded contexts effectively, DDD offers patterns based on team collaboration types: cooperation, customer-supplier, and separate ways. These patterns help manage the interactions and dependencies between contexts, ensuring that they can evolve independently while maintaining system coherence.
Overall, bounded contexts are essential for aligning software design with business domains, allowing for independent evolution and integration of different parts of a system. They provide a structured approach to handling complexity and change within software projects.
In Domain-Driven Design (DDD), integrating bounded contexts involves several patterns that facilitate communication and collaboration between teams. Two primary patterns for cooperating teams are the Partnership and Shared Kernel.
Partnership
The partnership model involves ad hoc integration between bounded contexts where teams notify each other of changes and adapt accordingly. This requires high levels of communication, collaboration, and continuous integration to minimize feedback loops. It is less suitable for geographically distributed teams due to potential synchronization issues.
Shared Kernel
A shared kernel involves implementing a common model across multiple bounded contexts. This model must be consistent and is often used when the cost of duplication is higher than coordination. It introduces strong dependencies and requires continuous integration to prevent inconsistencies. The shared kernel is suitable for core subdomains that change frequently but contradicts the principle that a single team should own a bounded context. It can be a temporary solution during system modernization or when communication barriers prevent a partnership.
Customer–Supplier Patterns
These patterns address power imbalances between upstream (supplier) and downstream (consumer) teams.
- Conformist: The downstream team adopts the upstream model, often due to external constraints or organizational politics.
- Anticorruption Layer: The downstream team translates the upstream model into one that fits its needs, protecting its domain from inefficient or frequently changing upstream models.
- Open-Host Service: The upstream team provides a stable interface for consumers, allowing internal changes without affecting downstream contexts. This decouples implementation from the public interface and supports multiple protocol versions.
Separate Ways
This pattern involves avoiding collaboration due to communication issues, generic subdomains, or significant model differences. It is not recommended for core subdomains due to strategic importance.
Context Map
A context map visually represents bounded contexts and their integrations, offering strategic insights into system design, communication patterns, and organizational issues. It should be maintained collaboratively and updated regularly.
In summary, the integration patterns—Partnership, Shared Kernel, Conformist, Anticorruption Layer, Open-Host Service, and Separate Ways—provide various approaches to managing dependencies and collaborations between bounded contexts. These patterns are plotted on a context map to guide high-level design and organizational strategy. The choice of pattern depends on factors like power dynamics, communication capabilities, and the cost of coordination versus duplication.
This chapter explores two patterns for implementing business logic: transaction script and active record. Both are suitable for simple business logic but differ in their approach and use cases.
Transaction Script Pattern:
- Organizes business logic into procedures, each handling a single request.
- Procedures act as encapsulation boundaries and must ensure transactional behavior—operations must either succeed completely or fail without leaving the system in an invalid state.
- Example: Converting JSON files to XML within a transactional script ensures system consistency even if failures occur.
- Common issues arise from failing to implement proper transactional behavior, such as data corruption due to incomplete transactions.
- In distributed systems, challenges include managing transactions across multiple storage mechanisms or message buses, often requiring complex solutions like CQRS or the outbox pattern.
- Transaction scripts are best suited for straightforward problem domains, like ETL operations, where business logic is simple. They are not recommended for complex core subdomains due to potential inconsistencies from duplicated logic.
Active Record Pattern:
- Encapsulates database access within objects that represent data structures, often using ORM frameworks.
- These objects provide CRUD operations and can include simple business logic like validation.
- Suitable for cases where business logic is simple but operates on complex data structures, avoiding repetitive code.
- Known as an “anemic domain model” antipattern when misapplied, it should be used judiciously to avoid unnecessary complexity.
- Active records are useful for supporting subdomains or integration tasks but not for complex core domain logic.
Pragmatic Considerations:
- In high-scale systems, it may be acceptable to relax data consistency guarantees if the impact on the business is minimal.
- Evaluate risks and business implications before deciding to “cut corners.”
Both patterns are foundational and, despite their simplicity, are prevalent in software development. They form the basis for more advanced patterns discussed in later chapters.
Exercises:
- Neither transaction script nor active record should be used for core subdomain logic due to complexity.
- Both patterns can be used for supporting subdomain logic.
- Code analysis reveals potential data consistency issues, such as increased counters without corresponding ticket assignments.
The text explores the implementation of business logic using domain-driven design (DDD) patterns, focusing on complex systems like a help desk. It contrasts simple patterns like transaction script and active record with the domain model pattern, which handles intricate business rules and state transitions.
Domain Model Pattern: Introduced by Martin Fowler and expanded by Eric Evans, the domain model pattern is suited for complex logic beyond CRUD operations. It uses tactical patterns such as aggregates, value objects, and domain services to align code with business models. This approach emphasizes business logic over technical concerns, facilitating a ubiquitous language that reflects domain experts’ mental models.
Value Objects: These are identified by their values rather than unique identifiers. They encapsulate logic and ensure immutability, making code expressive and reducing errors. For example, a Color
class is defined by its RGB values, eliminating redundant identifiers. Value objects centralize logic, such as parsing and validating data, and support operations like unit conversions.
Entities: Unlike value objects, entities have unique identifiers and mutable states. They represent distinct concepts, such as a Person
, and require an ID to differentiate instances with identical attributes.
Aggregates: An aggregate is a type of entity that maintains data consistency by acting as a boundary. It ensures that only internal logic can modify its state, preventing external processes from corrupting data. This boundary enforces business rules and protects the aggregate’s integrity.
Implementation Considerations: The domain model pattern avoids accidental complexity by using plain objects, free from infrastructural dependencies. This separation allows models to focus solely on business logic. Additionally, value objects and aggregates enhance code safety by encapsulating and validating business logic, making systems less error-prone and more intuitive.
Overall, the domain model pattern provides a robust framework for managing complex business logic, ensuring that systems remain consistent and aligned with business requirements.
In domain-driven design, aggregates encapsulate business logic and define strict boundaries for state modifications, ensuring all changes adhere to business rules and invariants. Commands, which modify an aggregate’s state, can be implemented as public methods or parameter objects. The aggregate’s public interface is responsible for input validation and enforcing business rules, centralizing logic within the aggregate.
The application layer orchestrates operations on aggregates using transaction scripts, ensuring atomic transactions where changes either fully succeed or fail. Concurrency management is crucial to maintain data consistency, often implemented with version fields to prevent overwriting changes by concurrent processes.
Aggregates act as transactional boundaries, meaning all changes must be committed atomically, without assuming multi-aggregate transactions. This requires careful design of aggregate boundaries to align with business invariants. Aggregates are composed of entities and value objects, forming a hierarchy that shares transactional consistency.
When multiple objects need simultaneous modification, the aggregate pattern supports this by aggregating entities and value objects under a single transaction boundary. The aggregate root serves as the public interface, while domain events describe significant occurrences within the business domain. These events, named in the past tense, provide necessary data and allow other processes to respond.
Aggregates should remain small, containing only information requiring strong consistency. External aggregates are referenced by ID to maintain separate transactional boundaries. If an aggregate’s logic depends on eventually consistent data, it risks invalid states, emphasizing the need for careful boundary design.
Domain services handle business logic not tied to a single aggregate, orchestrating interactions between various components without violating transactional boundaries. They are stateless and focus on calculations or analyses across multiple aggregates.
Complexity in systems is managed by reducing degrees of freedom through encapsulating invariants within aggregates and value objects. This encapsulation simplifies control and prediction of system behavior, aligning with Eliyahu M. Goldratt’s definition of system complexity. Aggregates and value objects centralize business logic, reducing complexity by protecting business invariants within their boundaries.
The domain model pattern is essential for handling complex business logic in core subdomains of software systems. It comprises three main building blocks: value objects, aggregates, and domain services. Value objects are immutable and identified by their values, modeling both data and behavior. Aggregates are hierarchies of entities that share a transactional boundary, ensuring data consistency and encapsulating business logic. Domain services host business logic that doesn’t belong to aggregates or value objects.
In contrast, the event-sourced domain model pattern persists aggregates’ states using domain events instead of storing the current state. This approach introduces the dimension of time, capturing each change in an aggregate’s lifecycle as an event. The event sourcing pattern allows for reconstructing an entity’s state by sequentially applying events, enabling time travel to any point in its lifecycle.
The pattern’s implementation involves using an event store, which is an append-only database that becomes the system’s source of truth. Events are fetched and appended with optimistic concurrency management to ensure consistency. This method is similar to financial ledgers, where each transaction is recorded as an event, allowing the current state to be deduced from past events.
Projection logic can create different state representations from events, such as search-optimized or analysis-optimized models. For example, a lead’s historical contact information can be projected for search purposes, or the number of follow-ups can be counted for analysis. These projections can be stored in a database for practical use, supporting functionalities like command-query responsibility segregation (CQRS).
Overall, the event-sourced domain model is a powerful tool for managing complex business logic, providing insights into the historical context of data, and optimizing processes based on past interactions. By capturing the complete lifecycle of entities, it enables more informed decision-making and process improvements.
The event-sourced domain model is a pattern where changes to an aggregate’s state are expressed as domain events. This involves loading an aggregate’s events, reconstituting its state, executing commands to apply business logic, and committing new events to the event store. For example, in a Ticket aggregate, events are loaded, the aggregate is rehydrated, commands are executed, and changes are persisted. The Ticket
class uses a state projector to manage state changes via the AppendEvent
method, which applies events dynamically to update the aggregate’s state.
The event-sourced model offers several advantages. It allows “time traveling,” enabling reconstitution of past states for analysis, debugging, and optimization. It provides deep insights into system behavior and serves as a consistent audit log, which is crucial for compliance in certain domains. The model also facilitates advanced optimistic concurrency management, allowing precise conflict resolution based on event history.
However, the model has challenges, such as a steep learning curve and complexity in evolving the model due to the immutability of events. The architectural complexity can be significant, making it suitable only when justified by the domain’s needs. Performance concerns arise with large numbers of events, but these can be mitigated using patterns like snapshotting, which caches state projections to improve efficiency.
Scalability is inherent, as the event store can be sharded by aggregate IDs. For data deletion, techniques like the forgettable payload pattern are used, where sensitive data is encrypted, and keys are managed externally to comply with regulations like GDPR.
Common questions about event sourcing include concerns about performance and data management. The snapshot pattern is an optimization to address performance issues, but it should be implemented only when necessary. The model’s scalability is achieved through sharding, and data deletion is managed by encrypting sensitive information.
Event sourcing is distinct from traditional state-based models, which might use logs or history tables for auditing. However, these approaches can be inconsistent and lack business context. Event sourcing provides a consistent, context-rich audit trail, which is crucial for complex business logic.
In summary, the event-sourced domain model is a robust pattern for managing complex business logic, offering flexibility and deep insights into system behavior. It is particularly useful for domains requiring detailed audit trails and compliance with legal requirements. However, its complexity and learning curve mean it should be applied judiciously, considering the specific needs of the business domain.
Architectural Patterns
Layered Architecture
The layered architecture consists of distinct layers: presentation, business logic, data access, and optionally, a service layer. The presentation layer serves as the program’s public interface, handling user interactions and external communications. The business logic layer, described by Eric Evans as the heart of software, implements business rules and decisions using patterns like active records or domain models.
The data access layer manages interactions with persistence mechanisms, including traditional databases, NoSQL databases, and cloud storage services. It also integrates with external APIs and services, ensuring the program’s functionality is supported by various data sources.
Communication between layers follows a top-down model, where each layer depends only on the one directly beneath it, promoting decoupling and reducing shared knowledge.
The service layer acts as an intermediary, coordinating operations between the presentation and business logic layers. It encapsulates orchestration logic, providing a consistent interface for different public interfaces like GUIs and APIs, enhancing modularity, decoupling, and testability.
Ports & Adapters Architecture
The ports & adapters architecture, also known as hexagonal or clean architecture, addresses the limitations of layered architecture by decoupling business logic from technological concerns. It centralizes the business logic layer, which defines “ports” that the infrastructure layer implements as “adapters” for different technologies. This inversion of dependencies aligns with the Dependency Inversion Principle, ensuring business logic remains independent of infrastructure.
The architecture includes an application layer that acts as a façade, orchestrating business logic execution. This pattern supports complex business logic implementation, such as domain models, by allowing flexible integration with external components.
Command-Query Responsibility Segregation (CQRS)
CQRS segregates command execution and read models, enabling multiple persistent models for system data representation. The command execution model handles state modifications and enforces business rules, serving as the system’s source of truth. Read models, or projections, are read-only and optimized for queries, supporting diverse data presentation needs.
Projections can be updated synchronously or asynchronously. Synchronous projections use a catch-up subscription model, querying changes in OLTP databases based on checkpoints. This approach resembles materialized views, allowing read models to be regenerated from the command execution model’s data.
CQRS supports polyglot persistence, enabling the use of different databases for specific requirements, and is closely related to event sourcing, enhancing querying capabilities for event-sourced models.
Terminology and Usage
Different terminologies exist for these architectures: the presentation layer may be called the user interface layer, and the service layer as the application layer. Understanding these terms helps avoid confusion and ensures clarity in communication.
The layered architecture suits systems with business logic implemented via transaction scripts or active records, while the ports & adapters architecture is ideal for domain models. CQRS is beneficial for systems requiring multiple data representations and flexible querying capabilities.
Overall, these architectural patterns provide structured approaches to building scalable, maintainable, and adaptable software systems, each suited to different requirements and complexities.
In the context of CQRS (Command Query Responsibility Segregation) architecture, projections can be either synchronous or asynchronous. Synchronous projections are straightforward to implement and regenerate by resetting the checkpoint, allowing the projection engine to rebuild models from scratch. Asynchronous projections, however, offer scalability but face challenges such as message ordering and duplication, which can lead to data inconsistencies. It’s recommended to implement synchronous projections as a base and optionally add asynchronous ones for scalability.
CQRS segregates system responsibilities, ensuring commands operate on a strongly consistent command execution model, while queries cannot modify the persisted state. A common misconception is that commands should not return data; however, they should provide feedback on success or failure to improve user experience and workflow efficiency.
CQRS is beneficial for applications needing multiple data models across different databases, supporting domain-driven design by leveraging the strengths of diverse storage mechanisms. It is particularly useful in event-sourced domain models, enabling querying of aggregate states through projections.
The architectural patterns discussed, such as layered architecture, ports and adapters, and CQRS, are not systemwide principles but should be applied based on specific needs and business strategy. These patterns help define logical boundaries within a system, preventing complexity and maintaining modularity.
Model translation in communication patterns involves translating models across bounded contexts, either statelessly or statefully. Stateless translation uses proxies for synchronous or asynchronous communication, while stateful translation aggregates or unifies data, requiring persistent storage. This ensures effective integration and encapsulation of bounded contexts.
In event publishing, a common mistake is publishing domain events directly from aggregates before committing state changes to the database, leading to potential inconsistencies. Instead, events should be published after successful database transactions to ensure reliability.
Overall, these patterns and practices facilitate scalable, consistent, and efficient system architectures, addressing both internal organization and external communication challenges.
The outbox pattern ensures reliable publishing of domain events by committing both the updated aggregate state and new domain events in the same atomic transaction. A message relay fetches these events from the database and publishes them to a message bus, marking them as published or deleting them afterward. This pattern is useful for maintaining consistency, especially in systems using relational databases or NoSQL databases that lack multi-document transactions.
In cases where business processes span multiple aggregates, the saga pattern can be employed. A saga is a long-running business process that listens to events and issues commands across different components. It ensures consistency by executing compensating actions if a step fails. For instance, in an advertising campaign, a saga listens to activation events and manages the publishing state based on confirmations or rejections from a publisher.
The process manager pattern, similar to a saga, handles complex business workflows. It maintains the state of the sequence and determines the next steps, unlike a saga which is event-driven. For example, booking a business trip involves multiple steps and approvals, making it suitable for a process manager implementation.
Both sagas and process managers rely on asynchronous event handling and command issuance. While sagas are implicitly triggered by events, process managers require explicit instantiation and are better suited for intricate workflows.
Consistency in these patterns is eventually achieved, as no single transaction is atomic across multiple components. This aligns with the principle that only data within an aggregate’s boundaries is strongly consistent, while everything else is eventually consistent.
In summary, the outbox pattern ensures reliable event publishing, the saga pattern handles simple cross-component processes, and the process manager pattern manages complex workflows. These patterns help maintain consistency and reliability in distributed systems by decoupling business logic from messaging infrastructure and ensuring eventual consistency across components.
When designing bounded contexts, start with wider boundaries and refine them as domain knowledge increases. This approach is crucial for core subdomains, which are more volatile than supporting or generic ones. Incorporating related subdomains within a bounded context can mitigate unforeseen changes.
Business logic can be modeled using various patterns: transaction script, active record, domain model, and event-sourced domain model. The choice depends on the complexity of the business logic and data structures. For simple logic, use transaction scripts or active records. For complex logic, domain models or event-sourced domain models are appropriate, especially when tracking monetary transactions or requiring audit logs.
Architectural patterns—layered architecture, ports & adapters, and CQRS—should align with the business logic pattern. Event-sourced domain models necessitate CQRS for effective data querying. Domain models fit well with ports & adapters, while active records suit a layered architecture with a service layer. Transaction scripts require minimal layering.
Testing strategies vary based on the implementation pattern. The testing pyramid emphasizes unit tests for domain models. The testing diamond focuses on integration tests for active records, while the reversed pyramid prioritizes end-to-end tests for transaction scripts.
Design decisions are guided by heuristics, which are adaptable based on context and experience. A decision tree can assist in selecting business logic patterns, architectural patterns, and testing strategies, but should not replace critical thinking.
Subdomains can evolve, affecting strategic design decisions. Core subdomains may become generic if commoditized, while generic subdomains can become core if they provide competitive advantage. Supporting subdomains can morph into core ones if their complexity leads to profitability. These changes necessitate reevaluating bounded context boundaries and integration patterns.
Strategic design involves choosing integration patterns that align with subdomain types. Core subdomains require protection through anticorruption layers and published languages. Changes in subdomain types may necessitate shifts in integration strategies, such as moving from separate ways to a customer-supplier relationship.
Overall, design heuristics provide a framework for making informed decisions, but flexibility and adaptation to specific contexts are essential for effective software design.
In domain-driven design, implementation strategies differ for core and supporting subdomains. Supporting subdomains can be outsourced or serve as training for new hires, while core subdomains require in-house implementation due to their proximity to domain knowledge. A shift in a subdomain’s type necessitates a corresponding change in implementation strategy. Tactical design concerns arise when existing technical designs fail to meet business needs. For instance, when a supporting subdomain becomes a core subdomain, its complexity may outgrow simple design patterns like transaction scripts or active records, signaling the need for a design reassessment.
Transitioning between design patterns involves several steps. Moving from a transaction script to an active record pattern involves encapsulating complex data structures within active record objects. When business logic becomes too complex for active records, refactoring to a domain model pattern is advisable. This involves identifying value objects, ensuring transactional boundaries are explicit, and modifying state logic within active record boundaries. Further evolution to an event-sourced domain model requires modeling domain events to represent aggregate lifecycles, a process complicated by the need to migrate existing aggregate states.
Organizational changes can affect system design, particularly in integrating bounded contexts. Changes in team communication due to geographical or structural shifts may necessitate a move from partnership to customer-supplier relationships or even separate ways. Domain knowledge is crucial for successful software design, and its acquisition is challenging, especially for core subdomains. As domain knowledge evolves, so should the design, with bounded contexts’ boundaries reflecting the level of domain understanding.
Growth in a system indicates success but can lead to complexity if not managed. Unregulated growth results in a “big ball of mud,” characterized by a sprawling and disorganized codebase. It is essential to eliminate accidental complexity while managing essential complexity using domain-driven design tools. Revisiting subdomain boundaries is crucial as business domains grow, ensuring they remain useful and coherent. Bounded contexts should be revisited to maintain focus and autonomy, avoiding “chatty” interactions that indicate ineffective models.
In summary, evolving design decisions in domain-driven design involve adapting implementation strategies, transitioning between design patterns, accommodating organizational changes, and managing growth. These adaptations ensure the system remains aligned with business needs and domain knowledge, maintaining clarity and effectiveness in its architecture.
In the context of domain-driven design (DDD), eliminating accidental complexity is crucial for simplifying business logic and bounded contexts. Refactoring can reveal hidden models, which should be explicitly defined and extracted into different bounded contexts. As businesses evolve, design decisions must align with current strategies and needs, recognizing that organizational changes affect team communication and integration. Continuous learning and leveraging domain knowledge are essential for evolving strategic and tactical design decisions. Managing software growth involves identifying finer-grained subdomain boundaries and avoiding “jack of all trades” contexts. Aggregates should have minimal boundaries, and strongly consistent data can indicate opportunities to extract business logic.
EventStorming is a low-tech, collaborative modeling process focusing on domain-driven design principles. It involves brainstorming domain events using sticky notes on a large modeling space, allowing participants to explore business processes as a series of events. The process involves several steps, starting with unstructured exploration of domain events, organizing them into timelines, identifying pain points, and marking pivotal events that indicate context changes. Commands, policies, read models, external systems, aggregates, and bounded contexts are added to enrich the model.
Participants in EventStorming should be diverse, including engineers, domain experts, and other stakeholders, without exceeding ten people for effective contribution. The process requires a large space, sticky notes, markers, and a room without chairs to facilitate active participation.
EventStorming sessions help build a ubiquitous language, model business processes, explore new requirements, and recover lost domain knowledge. The process encourages knowledge sharing, aligning mental models, and discovering conflicting models. It can also serve as a basis for implementing an event-sourced domain model, depending on the business domain’s needs.
Variants of EventStorming can be adapted to suit organizational needs, starting with a big-picture exploration and then focusing on specific business processes. The real value lies in the collaborative nature of the process, fostering understanding and alignment among stakeholders.
EventStorming is a collaborative workshop technique for modeling business processes, primarily aimed at knowledge sharing among participants. It is particularly useful for modernizing legacy systems, improving business processes, and onboarding new team members. However, it is less effective for simple, sequential processes with minimal business logic.
Facilitation Tips:
- Begin with an overview of the process, explaining the business process and modeling elements like domain events, commands, and actors.
- Maintain a visible legend of sticky notes and labels for participants.
- Monitor group dynamics and ensure active participation. If energy wanes, reignite interest with questions or move to the next stage.
- Allow breaks and ensure all participants are present before resuming.
Remote EventStorming:
- Originally designed for in-person interaction, remote sessions became necessary due to the COVID-19 pandemic. Tools like Miro facilitate remote collaboration but require patience due to less effective communication.
- Limit participants to five for remote sessions, and consider multiple sessions for larger groups.
Benefits and Applications:
- EventStorming helps synchronize participants’ understanding and fosters a ubiquitous language.
- It is beneficial for exploring new business domains, recovering lost knowledge in brownfield projects, and introducing new team members.
Domain-Driven Design (DDD) in Practice:
- DDD tools aid in analyzing business domains and making design decisions, particularly in brownfield projects with technical debt.
- It is not an all-or-nothing approach; applying selective patterns can still yield benefits.
Strategic Analysis:
- Understand the business domain by identifying core, generic, and supporting subdomains.
- Evaluate high-level components for decoupled lifecycles and assess tactical design for appropriate complexity.
- Use context maps to analyze relationships between components and identify suboptimal strategic designs.
Modernization Strategy:
- Avoid “big rewrites” and focus on incremental improvements by aligning logical boundaries with subdomains.
- Consider extracting bounded contexts for conflicting models or multiple teams working on the same codebase.
- Evaluate context integration patterns like customer-supplier relationships, anticorruption layers, and open-host services.
Tactical Modernization:
- Address mismatches between business value and implementation strategies, especially in core subdomains.
- Cultivate a ubiquitous language through EventStorming to gather domain knowledge and build effective models.
The text discusses strategies for modernizing legacy systems, focusing on the strangler pattern and refactoring, and explores domain-driven design (DDD) principles in real-world scenarios.
Strangler Pattern: This approach involves creating a new bounded context to implement new requirements while gradually migrating functionality from the legacy system. The legacy system’s development halts, except for urgent fixes, until all functionalities are transferred to the new system. This pattern often uses a façade as an interface layer to manage requests between the old and new systems. During migration, both systems may share a database to avoid complex integrations, though this contradicts the DDD principle of separate subsystems not sharing databases.
Refactoring: Instead of a complete rewrite, refactoring involves incremental changes, starting with designing state-based aggregates before moving to event-sourced aggregates. This approach emphasizes understanding business logic and transactional boundaries to ensure consistency. An anticorruption layer can protect the new codebase from legacy models.
Domain-Driven Design: DDD is about aligning software design with business needs, not strictly following specific patterns like aggregates or value objects. It involves understanding the business domain, creating effective models, and making design decisions based on business requirements. Selling DDD to management can be challenging, but its principles can be applied incrementally and without formal adoption.
Undercover DDD: Incorporating DDD into daily work involves using its patterns and practices subtly. This includes employing a ubiquitous language to facilitate communication and understanding within the team and with stakeholders. Bounded contexts help manage complexity by ensuring models are problem-oriented and not conflicting.
Microservices and DDD: Microservices architecture, often associated with DDD, involves designing services with minimal public interfaces, enhancing understanding and integration. This architecture encapsulates databases to maintain compact interfaces, promoting autonomy in development and scalability.
In summary, the text provides insights into effectively modernizing legacy systems using DDD principles and microservices architecture, emphasizing gradual changes and alignment with business strategies.
The text discusses the complexities and design considerations in microservices architecture, emphasizing the balance between local and global complexities. A naïve approach to microservices, such as limiting service interfaces to a single method, often results in increased system complexity due to the need for extensive integration-related public methods. This can lead to a “big ball of mud” scenario, where the system becomes overly complex.
Microservices aim to create a flexible system by integrating decoupled services. The challenge lies in optimizing both local complexity (individual microservices) and global complexity (the overall system). A monolithic approach minimizes global complexity but can lead to high local complexity, whereas overly fine-grained microservices can increase global complexity.
Effective microservices are described as “deep modules,” where a simple public interface encapsulates complex logic. Shallow modules, with interfaces as complex as their logic, add unnecessary complexity. The goal is to balance service granularity to minimize the cost of changes and avoid turning the system into a distributed big ball of mud.
Domain-Driven Design (DDD) principles are crucial in defining microservices boundaries. Bounded contexts and subdomains are key concepts. While microservices are bounded contexts, not all bounded contexts are microservices. Bounded contexts define the largest viable monolith, protecting the consistency of domain models. Subdomains, representing coherent business capabilities, are often aligned with microservices for optimal design.
The text also introduces patterns like the open-host service and anticorruption layer to simplify public interfaces and reduce system complexity. The open-host service decouples internal models from integration models, allowing flexibility and simpler interfaces. The anticorruption layer separates business and integration complexities, making the consuming service’s interface simpler.
Overall, the text emphasizes the importance of balancing service granularity, leveraging DDD patterns, and continuously adapting architecture to maintain an effective microservices-based system.
Microservices and bounded contexts are distinct concepts; not all bounded contexts are microservices. The “micro” aspect of a microservice should focus on the business domain knowledge exposed at the service’s boundary, not necessarily on the size of the code or team. Safe component boundaries lie between bounded contexts and microservices.
Event-driven architecture (EDA) is a common approach in distributed systems, promoting asynchronous communication through events. It is often associated with domain-driven design (DDD) but should be applied carefully to avoid turning a modular monolith into a disorganized system. EDA involves components publishing events to signal changes, with other components subscribing and reacting to these events.
EDA differs from event sourcing, which captures state changes within a service. Event-driven systems use events as the primary communication mechanism, which can be categorized into event notifications, event-carried state transfers, and domain events. Event notifications signal a change without detailed data, requiring subscribers to fetch additional information. Event-carried state transfers provide comprehensive data about a state change, allowing consumers to maintain a local cache. Domain events describe significant business occurrences, containing all necessary information but not intended for data caching.
Choosing the correct event type is crucial for system decoupling. Incorrect application can lead to a distributed “big ball of mud,” characterized by temporal, functional, and implementation coupling. Temporal coupling arises when components depend on a strict execution order. Functional coupling occurs when multiple components duplicate business logic, requiring simultaneous changes. Implementation coupling happens when changes in one component’s events necessitate adjustments in subscribers, potentially leading to inconsistencies.
To design effective event-driven systems, it’s essential to understand the boundaries and integration points, ensuring components are decoupled and resilient. Events should be modeled accurately to represent business processes without unnecessary dependencies between components.
In event-driven architecture, exposing all domain events from a CRM’s data model can lead to coupling issues, both implementation and functional. To address this, the CRM can encapsulate projection logic and follow a consumer-driven contract pattern, projecting models needed by consumers and making them part of the bounded context’s published language. This approach decouples consumers from the CRM’s internal implementation.
Temporal coupling between components like AdsOptimization and Reporting can be mitigated by using event notification messages that trigger data fetching actions. Effective event-driven design involves matching event types to tasks, which reduces coupling and enhances flexibility and fault tolerance. Key design heuristics include preparing for network slowness, server failures, and out-of-order or duplicated events. Consistent message delivery is crucial, achieved through patterns like outbox, saga, and process manager.
Public and private events should be used carefully to avoid exposing implementation details. Events should be part of the bounded context’s public interface, utilizing event-carried state transfer and notification messages to minimize interfaces. Consistency requirements guide event type selection: eventual consistency can use event-carried state transfer, while immediate reads require event notifications with subsequent queries.
Event-driven architecture involves three event types: event notification, event-carried state transfer, and domain events. Choosing inappropriate event types can lead to system issues, so it’s vital to evaluate bounded contexts’ consistency needs and define explicit public and private events.
Data mesh is an architectural approach for managing analytical data, differing from traditional OLAP methods. Operational data models (OLTP) focus on real-time transactions and entity lifecycles, while analytical models (OLAP) provide business insights through fact and dimension tables. Fact tables record business activities, and dimension tables describe these facts, supporting flexible querying.
Analytical models use star and snowflake schemas to structure data. The star schema connects facts to dimensions directly, while the snowflake schema further normalizes dimensions, reducing storage but increasing query complexity. These models support business intelligence and optimization.
Data management architectures like data warehouses and data lakes facilitate analytical data generation. Data warehouses transform operational data into analytical models using ETL processes. However, they face challenges like coupling with operational systems and the impracticality of creating enterprise-wide models. Data marts can alleviate some issues by focusing on specific analytical needs.
Data lakes store raw operational data, allowing for multiple task-oriented analytical models. While this approach offers flexibility, it increases system complexity, requiring multiple ETL versions to handle different operational model versions. Overall, both data warehouses and data lakes aim to provide actionable insights from operational data.
Data lakes and data warehouses face challenges as they scale, becoming chaotic and difficult to manage. Both architectures assume that more data leads to more insights, but they often break under the weight of big data. The transformation of operational to analytical models results in numerous unmaintainable ETL scripts. There is also friction between operational and analytical systems due to dependencies and a lack of deep business domain knowledge among data analysts and engineers.
The data mesh architecture addresses these issues by applying domain-driven design (DDD) principles to analytical data. It is based on four core principles: decomposing data around domains, treating data as a product, enabling autonomy, and building an ecosystem.
-
Decompose Data Around Domains: Instead of a monolithic analytical model, data mesh uses multiple models aligned with the data’s origin, aligning ownership boundaries with bounded contexts. This approach assigns responsibility to product teams for generating analysis data.
-
Data as a Product: Analytical data should be treated as a first-class citizen, with well-defined schemas, service-level agreements (SLAs), and versioning like public APIs. This ensures data quality and addresses consumer needs, allowing for easy integration and local transformations.
-
Enable Autonomy: Product teams should create and consume data products independently, requiring a platform to manage interoperability. A dedicated data infrastructure platform team defines blueprints, access patterns, and ensures SLAs are met.
-
Build an Ecosystem: A federated governance body ensures interoperability and adherence to rules across the enterprise. This group consists of data and product owners and the data infrastructure platform team.
The data mesh architecture leverages DDD patterns such as ubiquitous language, CQRS, and bounded context integration. These patterns support the creation of analytical models and their evolution. The architecture allows for collaboration or independent evolution of analytical models, with options for integration or duplication as needed.
Data mesh emphasizes the importance of defining boundaries and encapsulating implementation details, similar to DDD. This approach addresses traditional architecture challenges by decomposing analytical models into manageable units and ensuring reliable access through public interfaces.
In summary, data mesh combines DDD principles with modern data management needs, offering a scalable, flexible, and consumer-focused approach to analytical data. It supports the creation of fine-grained, interoperable data products that align with organizational goals and consumer requirements.
The text discusses the principles and application of Domain-Driven Design (DDD) through a case study of a fictional company, Marketnovus. The author emphasizes understanding the logic behind DDD rather than following it dogmatically. The text highlights the importance of ubiquitous language and EventStorming in DDD.
Marketnovus, a startup, implemented DDD from its inception, making several mistakes but learning from them. The case study is divided into two parts: the stories of five bounded contexts and their design decisions, and how these reflect DDD principles.
Bounded Context #1: Marketing
Initially, Marketnovus’s system was a monolithic structure with a flawed architecture, characterized by an “anemic domain model” where business logic was implemented in a large service layer. Despite this, the project was a business success due to effective communication with domain experts and a robust ubiquitous language.
Bounded Context #2: CRM
The CRM system faced challenges due to the lack of bounded contexts, leading to duplicated efforts and inconsistent models. This resulted in a project full of bugs and delayed timelines. The realization of the importance of bounded contexts and Vaughn Vernon’s “Effective Aggregate Design” paper helped in redesigning the CRM with proper domain models and transactional boundaries.
Bounded Context #3: Event Crunchers
The event crunchers context was initially designed as a supporting subdomain with simple transaction scripts. As business logic complexity increased, the design became inadequate, leading to a refactor using event sourcing to handle complex rules and invariants.
Bounded Context #4: Bonuses
The bonuses context started as a supporting subdomain with active record objects. As the business evolved, the complexity of the domain required a redesign using event-sourced domain models. The presence of a ubiquitous language helped identify the need for design changes earlier, saving time and effort.
Bounded Context #5: The Marketing Hub
The management sought new profit opportunities, leading to the development of the marketing hub project. This context aimed to leverage Marketnovus’s ability to generate leads and sell them to smaller clients.
Overall, the case study illustrates the evolution of Marketnovus’s understanding of DDD, emphasizing the significance of ubiquitous language, bounded contexts, and strategic design concepts. The text underscores the necessity of adapting design approaches based on the complexity and core nature of subdomains, ultimately leading to a classic understanding of DDD principles.
The text discusses the challenges and lessons learned from implementing domain-driven design (DDD) principles, particularly in the context of microservices and bounded contexts. Initially, a microservices-based architecture was adopted, with each service having its own database and communication methods. However, this led to a distributed monolith due to excessive inter-service communication, highlighting the pitfalls of misapplying microservices and overengineering simple business logic.
A key issue was the mismatch between technical and business complexity, termed “accidental complexity,” where the system was overengineered for a simple business logic that could have been implemented with basic active records. This emphasizes the importance of aligning technical solutions with actual business needs.
The text underscores the critical role of ubiquitous language in DDD, which facilitates effective communication among stakeholders and helps avoid architectural issues. Successful projects maintained a strong ubiquitous language, whereas failures often involved communication breakdowns and conflicting terminologies.
Identifying subdomains—core, supporting, and generic—is crucial for effective design. Misidentifying subdomain types can lead to costly mistakes. For instance, some subdomains evolved from supporting to core as new monetization opportunities were discovered. The text suggests reversing the relationship between subdomains and tactical design decisions to better align technical implementations with business goals.
Bounded contexts should be defined carefully, with initial boundaries set wide enough to accommodate unknowns in the business domain. The text advises starting with larger boundaries and decomposing them as more domain knowledge is acquired. This approach helps avoid premature optimization and ensures that bounded contexts align with business realities.
Overall, the text highlights the importance of adapting DDD principles to the specific context, maintaining flexibility, and continuously refining the design as business understanding evolves. The experience at Marketnovus demonstrates how DDD can facilitate handling complex, dynamic business environments, ultimately contributing to the company’s success and acquisition.
The text explores various patterns and architectures in software design, with a focus on domain-driven design (DDD). Key concepts include:
Layered Architecture
- Supports the active record pattern and CQRS (Command-Query Responsibility Segregation) for abstracting infrastructural concerns from business logic.
- Infrastructural components should reside in the infrastructure layer.
- Asynchronous projections are scalable, and commands can return consistent information.
Bounded Contexts and Subdomains
- Bounded contexts help manage domain complexity by encapsulating business logic and facilitating integration.
- Patterns like anticorruption layers and open-host services transform models for upstream and downstream contexts.
- Bounded contexts can evolve from partnerships to customer-supplier relationships, impacting integration strategies.
CQRS and Event Sourcing
- CQRS allows for multiple models, separating read and write concerns, and is useful for complex queries.
- Event sourcing is optimal for domains requiring deep analytics, enabling flexible querying and behavior testing.
- Sagas and process managers handle domain events, with sagas being suitable for simpler processes.
Microservices and Data Management
- Microservices encapsulate bounded contexts but require careful design to manage complexity.
- Data mesh architecture aligns with domain-driven design by decomposing data around domains, promoting autonomy and governance.
- OLAP (Online Analytical Processing) models optimize for reading data, while OLTP (Online Transaction Processing) focuses on writing.
Patterns and Integration
- The outbox pattern ensures reliable message publishing and can facilitate asynchronous execution.
- Event-driven architectures use patterns like event notification and event-carried state transfer for communication.
- Sharing databases across bounded contexts contradicts DDD principles but can be part of a temporary migration strategy.
Design and Testing
- Active records and transaction scripts provide different approaches to business logic implementation, with testing strategies focusing on integration and end-to-end tests.
- Domain services coordinate business logic, ensuring consistency and reducing complexity.
EventStorming
- A collaborative modeling technique used to explore and understand business domains, facilitating communication among stakeholders.
Overall, the text emphasizes the importance of aligning software architecture with business domains to manage complexity and support business goals effectively.
Domain-Driven Design (DDD) is a strategic approach to software development that emphasizes collaboration between technical and domain experts to create a shared understanding of the business domain. Key concepts include bounded contexts, aggregates, domain events, and ubiquitous language, which are essential for managing complexity and ensuring effective communication.
Bounded Contexts and Subdomains: Bounded contexts define clear boundaries within a domain, allowing for distinct models and reducing complexity. Subdomains can be core, generic, or supporting, each with varying levels of complexity and strategic importance. Core subdomains provide competitive advantage, while generic subdomains do not.
Domain Models and Aggregates: Domain models encapsulate business logic and are composed of entities, value objects, and aggregates. Aggregates ensure consistency within a transaction boundary and communicate through domain events. The event-sourced domain model captures state changes as a sequence of events, providing benefits like retroactive debugging and scalability.
Event-Driven Architecture: Event-driven architecture (EDA) decouples systems through events, enhancing scalability and flexibility. Patterns like event sourcing and command-query responsibility segregation (CQRS) help manage state and separate read and write operations. Event sourcing stores all changes as events, allowing reconstruction of past states.
Strategic and Tactical Design: DDD distinguishes between strategic and tactical design. Strategic design focuses on high-level decisions like identifying bounded contexts and subdomains, while tactical design involves implementing business logic using patterns like active record, domain model, and transaction scripts.
Microservices and Deep Modules: Microservices architecture aligns with DDD principles by treating services as deep modules, each with its own bounded context. This approach reduces system complexity and enhances modularity, allowing teams to work independently on different parts of the system.
Communication and Ubiquitous Language: Effective communication is crucial in DDD. Ubiquitous language ensures that all stakeholders use the same terminology, reducing misunderstandings. EventStorming is a workshop format that facilitates collaborative exploration of domain events and processes.
Design Patterns and Architecture: DDD leverages patterns like layered architecture, ports and adapters, and the façade pattern to structure applications. The layered architecture separates concerns into presentation, business logic, and data access layers, promoting maintainability and flexibility.
Challenges and Modernization: Implementing DDD can be challenging due to domain complexity and organizational changes. Modernization strategies like the strangler pattern help transition legacy systems to modern architectures incrementally.
Conclusion: DDD offers a comprehensive framework for tackling complex software projects by aligning technical and business perspectives. It emphasizes continuous learning, adaptation, and collaboration to create systems that truly reflect the underlying business domain.
Mona monkeys are characterized by their white legs, with females averaging 16 inches in length and males 20 inches, plus tails that add over 26 inches. Their cheek fur can be yellow or gray, with light pink noses. Cheeks function as pouches for storing food such as fruit, seeds, insects, and leaves, which they forage multiple times daily in groups, sometimes larger than 40. Typically, a dominant male mates with multiple females and defends against other males, creating a noisy environment. Mona monkeys are classified as Near Threatened due to human activities.
The cover illustration by Karen Montgomery is based on an engraving from Lydekker’s Royal Natural History. The cover fonts used are Gilroy Semibold and Guardian Sans, with Adobe Minion Pro for text, Adobe Myriad Condensed for headings, and Dalton Maag’s Ubuntu Mono for code. O’Reilly Media, known for its educational resources, offers books, live online courses, instant answers, virtual events, videos, and interactive learning at oreilly.com. ©2023 O’Reilly Media, Inc. O’Reilly is a registered trademark of O’Reilly Media, Inc.