Domain-Driven Design in PHP: Book Summary

Domain-Driven Design in PHP teaches you how to correctly design Entities, Value Objects, Services, Domain Events, Aggregates, Factories, Repositories and Application Services with PHP. It also explores applying the Hexagonal Architecture within your application, whether within an open source framework or your own bespoke system. Finally, it looks into integrating Bounded Contexts, using REST and Messaging approaches.

Chapter 1: Getting started with Domain-Driven Design

  • Software is not just about code. If you think about it, code is rarely the end goal of our profession.
  • Domain-Driven Design emphasizes making sure businesses and software speak the same language.
  • Three Pillars of domain driven Design
    • Ubiquitous language
      • Domain Experts and software developers work together to build a common language for the business areas being developed.
    • Strategic Design
      • DDD addresses the strategy behind the direction of the business and not just the technical aspects.
    • Tactical Design:
      • DDD provides the tools and building blocks for iterative software deliverables

Chapter 2: Architectural Styles

  • In order to be able to build complex applications, one of the key requirements is having an architectural design that fits the application’s needs.
  • Application services are the ones that make things happen, and they’re the direct clients of a Domain Model.
  • The Dependency Inversion Principle
    • High-level modules should not depend on low-level modules. Both should depend on abstractions.
  • Command Query Separation
    • This design principle states that “every method should be either a command that performs an action, or a query that returns data to the caller, but not both”

Chapter 3: Value Objects

  • A value object is a measure or description of something.
  • A concept can be modelled as a Value Object if:
    • It measures, quantifies, or describes a thing in the Domain.
    • It can be kept immutable.
      • They should not be modified after creation. Modify them by creating a new instance
    • It models a conceptual whole by composing related attributes as an integral unit.
    • It can be compared with others through value equality.
    • It is completely replaceable when the measurement or description changes.
    • It supplies its collaborators with side-effect-free behaviour.
  • Using static over self can result in undesirable issues when a Value Object inherits from another Value Object.

Chapter 4: Entities

  • Entities are concepts which have an identity which endures over time no matter how many times the data in the concepts changes, their identities remain the same.

Chapter 5: Services

  • When there are operations that need to be represented but entities and value objects aren't the best place, you should consider modeling this operations as Services.
  • Application Services
    • This are the middleware between the outside world and the omain logic. They transform commands from the outside world into meaningfull domain instructions.
  • Domain Services
    • This ia an operation that fulfills a Domain task and naturally doesn't fit into either an Entiry or Value Object.
    • Domain Services are stateless operations
    • When we encounter infrastructural dependencies when modeling a Domain SErvice, we can create abstract the domain service as an interface.
    • Typically, when starting a new project or feature, it’s easy to fall into the trap of modeling the data first. This commonly includes thinking that each database table has a direct one-to-one object form representation. However, this thinking may or may not be the exact case all the time.
    • When starting a new project or feature, think of the behavior first. Databases, ORMs, and so on are just implementation details, and we should strive to push the decision to use these tools as late in the development process as we can.
    • You shoudl consider all your options before deciding on creating a domain service.
  • Infrastructure service operate over instructure doing things like sending emails or logging information.

Chapter 6: Domain Events

  • Software Events are things that happen in the syxtem that other components might be interested in knowing about.
  • Domain events are one specific type of event used for notifying local or remote bounded contexts of domain changes.
  • Domain events are ordinarily immutable. As they are a record of something in the past.
  • All events should be represented as verbs in the past tense.
  • Domain events are not just for doing batch jobs such as sending emails or communicating with other bounded contexts; Thery're also interesting for performance and scalability improvements.
  • We store domain events in an Event store.
  • An event store is a domain event repository that lives in our domain space as an abstraction. It's responsibility is to append Domain Events and query them.
  • It's important to persist a domain event with an ID s that it's easy to track which domain events have been published and which are missing
  • Constructing an object from plain data such as an array is called de-serialisation.
  • You can publish domain events out of a Bounded context by using a service such as RabbitMQ or using a REST API.

Chapter 7: Modules

  • When you place some classes together in a Module, you are telling the next developer who looks at your design to think about them together.
  • Modules should not be treated as a way to separate code but as a way to separate meaningful concepts in the model.
  • Use PSR-4 namespacing standard
  • Take the example of a fictional company called BuyIt, which deals with an e-commerce Domain, create a different application for each of the different Bounded Contexts eg Order Management, Payment Management, Catalog Management, and Inventory Management.
  • In the case of an application implementing two Bounded Contexts, the deployment process, the time for running the tests, and merging issues can slow down the development
  • Namespaces should be named in terms of Ubiquitous Language
  • Modules don’t separate code but instead separate meaningful concepts.
  • Frameworks should obey you, and not the other way around.
  • Modules are a way of grouping and separating concepts in our application.

Chapter 7: Aggregates

  • Data inconsistencies occur when we deal with our persistence mechanism in a non-atomic way. This can be fixed with transactions.
  • Keeping data consistent is a challenge. Not leaking infrastructure issues into the Domain is a bigger challenge. Aggregates aim to help you with both of these things.
  • ACID = atomicity, consistency, isolation and durability
  • The essential point of a transaction is that it bundles multiple steps into a single all-or-nothing operation.
  • Data can be kept consistent by the use of foreign keys and constraints
  • Locking is the system of protecting a transaction from seeing or changing data that is being queried or changed by other transactions.
  • Perssimistic concurrency control blocks aceess to a resource in order to prevent conflicts
  • In Optimistic concurrency Control, Before commiting each transaction, it verifies that no other transactions has modified the data is has read. If the check reveals conflicting modifications, the commiting transaction rolls back. This doesn't do locking
  • Optimistic concurrency control is generally used in environments with low data contention.
  • Aggregates are carefully crafted consistency boundaries that cluster Entities and Value Objects.
  • In Domain-Driven Design, an aggregate is a collection of related objects that we wish to treat as a unit. In particular, it is a unit for data manipulation and management of consistency
  • The object-relational impedance mismatch is a set of conceptual and technical difficulties that are often encountered when a relational database management system (RDBMS) is being used by a program written in an object-oriented programming language or style, particularly when objects or class definitions are mapped in a straightforward way to database tables or relational schemata.
  • Relational databases are easy to scale up, but the Relational Model doesn’t scale horizontally.
  • The main goal of an Aggregate is to keep your Domain Model consistent. Aggregates centralize most of the business rules. Aggregates are persisted atomically in your persistence mechanism.
  • Tell-Don’t-Ask is a principle that helps people remember that object-orientation is about bundling data with the functions that operate on that data. It reminds us that rather than asking an object for data and acting on that data, we should instead tell an object what to do. This encourages to move behavior into an object to go with the data.
  • A true business invariant is a business rule that must always be true and transactionally consistent within an Aggregate.
  • Making all operations go through the root help us keep the Aggregate consistent. In this way, it’s more difficult to break any invariant.
  • When designing Aggregates, you should strive to create them small. If there’s no true invariant to protect, that means all single Entities form an Aggregate by themselves
  • Only map the relations between Entities inside an Aggregate in your ORM if two Entities form an Aggregate.
  • If, in a single request, you need to update two Aggregates, it may just be that those two Aggregates are a single one and they need to both be updated in the same transaction.
  • If two agreegates need to be updated, update 1 in a transaction and fire a domain event for updating the second one.
  • You should never return Domain Entities, as this will prevent code outside of your Application Services — such as Controllers or your UI — from unexpectedly modifying them.
  • Rules to design proper aggregates
    • Make them small, find true business invariants, push for eventual consistency using Domain Events, reference other Aggregates by Identity, and modify one Aggregate per request.

Chapter 8: Factories

  • Factories are a powerful abstraction. They help decouple the client from the details of how to interact with the Domain.
  • The client doesn’t need to know how to build complex objects and Aggregates, so you can use Factories to create whole Aggregates, thereby enforcing their invariants.
  • The factory method pattern Defines an interface for creating an object, but leaves the choice of its type to the subclasses, creation being deferred at run-time.
  • It's good to put factory methods in aggregate roots.
  • Abstract Factory offers the interface for creating a family of related objects, without explicitly specifying their classes.
  • Extracting the building logic for entities, value objects and aggregates out of tests comes in handy and prevents duplication.
  • Factories are a powerful tool for decoupling construction logic from our business logic.
  • The Factory Method pattern not only helps by moving creation responsibility to the Aggregate Root, but it could also force Domain invariants.
  • While we could extract building logic into Object Mother Factories, Test Data Builders provide more flexibility for our tests.

Chapter 9: Repositories

  • Repositories act as storage locations, where a retrieved object is returned in the exact same state it was persisted in.
  • Martin Fowler defines a Repository as: The mechanism between the domain and data mapping layers, acting like an in-memory domain object collection.
  • The significant difference is that Repositories represent collections, while DAOs (Data Access Objects) are closer to the database and are often far more table-centric.
  • A set is a data structure an invariant that doesn't contain duplicate entries.
  • The first step to design a Repository is to define a collection-like interface for it.
  • The best place to generate a new Identity for an Aggregate is its Repository.
  • A good software architecture allows decisions about frameworks, databases, web- servers, and other environmental issues and tools, to be deferred and delayed.
  • The mapping between your Domain objects and the database can be considered an implementation detail. The Domain lifecycle shouldn’t be aware of these persistence details.
  • The EntityManager is the central access point for the ORM functionality.
  • Repositories should make sure to only access data that could be retrieved by navigating through the Aggregate Root.
  • If you find yourself creating many use case optimal finder methods, you may be introducing a common code smell. This could be an indication of a misjudged Aggregate boundary. If, however, you’re confident that the boundaries are correct, it could be time to explore CQRS.
  • A specification is a simple predicate that takes a Domain object and returns a boolean. Given a Domain object, it will return true if it specifies the specification, and false otherwise.
  • The Domain Model isn’t the place to manage transactions. The operations applied over the Domain Model should be agnostic to the persistence mechanism.
  • If we unify the way we execute use cases, we could wrap them in a transaction using the Decorator pattern
  • We have 2 types of repository approaches.
    • Collection-oriented Repositories tend to be purer to the Domain model, even if they persist Entities. From the client’s point of view, a collection-oriented Repository looks like a collection
    • Persistence-oriented Repositories require explicit persistence calls, as they don’t track object changes.

Chapter 10: Application.

  • The Application layer is the area that separates the Domain Model from the clients that query or change its state.
  • A DTO is a data structure that carries information between processes.
  • A DTO doesn’t have any behavior except for storage and retrieval of its own data (accessors and mutators).
  • DTOs are simple objects that shouldn’t contain any business logic that would require testing.
  • When designing your request objects, you should always follow these principles:
    • use primitives
    • design for serialization
    • and don’t include business logic inside.
  • Application requests are data structures, not objects. Unit testing data structures is like testing getters and setters. There’s no behavior to test, so there isn’t much value in trying to unit test request objects and DTOs.
  • Keep Application Services thin, using them only to coordinate tasks on the model.
  • Exceptions raised by Application Services are a way of communicating unusual cases and flows to the client. Exceptions on this layer are related to business logic (like not finding a user), and not implementation details (Like PDOException)
  • Dependency Injection Containers help by abstracting away the complexities of building your dependencies. They come in handy for building Infrastructure artifacts.
  • Passing the entire Dependency injection container as a whole to one of the Services is a bad practice.
  • The main way to customize your Application Service is by choosing which dependencies you’re passing in
  • There are two different approaches for invoking Application Services:
    • A dedicated class per use case with a single execution method
      • This is more robust and it follows the Single Responsibility Principle
    • Multiple Application Services and use cases inside the same class.
      • This is not recommended as not all Application Services are 100 percent cohesive.
  • Services should return sterile data structures with the information the presentation layer needs. This ensures that domain entities aren't changed in the controller/views.
  • One case where it is useful to use something like a DTO is when you have a significant mismatch between the model in your presentation layer and the underlying domain model
  • Unless it's the responsibility of your Domain, we Recommend letting the framework handle user credentials and security in general.
  • An interesting way of executing Application Services is through a Command Bus library.
    • The term is mostly used when we combine the Command pattern with a service layer. Its job is to take a Command object (which describes what the user wants to do) and match it to a Handler (which executes it). This can help structure your code neatly.

Chapter 11: Integrating Bounded Contexts.

  • Dealing with distributed systems is hard. Breaking a system into independent autonomous parts has its benefits, but it also increases complexity.
  • Designate some subset of the domain model that the teams agree to share. Of course this includes, along with this subset of the model, the subset of code or of the database design associated with that part of the model.
  • Integration Relationships
    • Consumer - Supplier
      • When there’s a unidirectional integration between two Bounded Contexts, where one acts as a provider (upstream) and the other as a client (downstream), we’ll end up with Customer - Supplier Development Teams.
      • Jointly develop automated acceptance tests that will validate the interface expected. Add these tests to the upstream team’s test suite, to be run as part of its’ continuous integration.
  • A Bounded Context reveals a clear interface to interact with to the outside world. It exposes resources that could be manipulated through HTTP verbs.
  • Create an isolating layer to provide clients with functionality in terms of their own domain model. The layer talks to the other system through its existing interface, requiring little or no modification to the other system.
  • Most of the time, Services will be interacting with a combination of Adapters and Facades.
  • The Services encapsulate and hide the low-level complexities behind these transformations.
  • Facades aid in hiding and encapsulating access details required for fetching data from the Gamification model.
  • Adapters translate between models, often using specialized Translators.
  • Messaging queues
    • messaging middleware enables decoupled integrations between different Contexts.
    • The most convenient RabbitMQ configuration is probably the Publish / Subscribe pattern.