The flow of control in Domain-driven PHP Architecture

In the previous article, we went over the Domain-driven PHP architecture. In this article, we will go over how the flow of control should be within your architecture. The flow of control is a way to define the order in which individual function calls of a program are executed. The flow goes from the user interface to the application core, it will touch the infrastructure at some places and then move from the application core back to the user interface.

But how does this flow look from the inside? What path should we follow, what depends on what?

The flow with services and query objects

First, let’s go over what the flow should look like when having application services and query objects. This basically means that the controller, which is part of the User Interface layer, will be either dependent on an Application Service or a Query object.

Use case flow

The controller will use application services to trigger a use case in the system. This application service will then use a repository to get one or more entities. It can also use a domain service to coordinate domain processes over multiple entities, you will not always need a domain service. But it’s important to use one when you otherwise risk leaking domain logic in the application services. When the application service is done, it’s also in charge of saving the changes made. It will notify other parts of the system that a use case is done, by dispatching events.

The repository is something specific per root entity. It will always have an interface. This interface can be part of your domain or your application. The repository implementation itself is different based on the database engine you are using. The persistence interface is to abstract your database engine implementation from your application. You can implement that in different ways. But it’s when the commit happens in the persistence driver that any domain events are dispatched. This is to ensure we only dispatch domain changes when that change actually is being persisted.

Read flow

Instead of directly getting entities from an application service and using that directly in your view (something like Twig). A Query object can be used. This will contain an optimized query that will simply return raw data to be shown to the user. That data will be hydrated in the form of a DTO. Next, you will have a View Model which represents the data you want to display in your API or on a view. This will be constructed using the DTO you got from the Query.

The key thing to remember with a view model is that this only represents the data you want to use. If you have a very big Domain Model, and you only need a subset of that data. Then your View Model only contains exactly the data you need for a given view. This also means your view model can consist of data from other domain models. In short, it’s the model you need for viewing.

The flow with a Command/Query Bus

When implementing a domain-driven PHP architecture I prefer to use the CQRS pattern. It will allow you to decouple the application layer from the User Interface. The Controller will now depend on a Command or Query Bus instead of an application service or Query.

The controller will send commands that will be handled by the command handler. The command handler can then use an application service to process the use case. In certain situations, the handler itself can also act as the application service.

As you can see in the diagram. There are no dependencies between the command/query the handlers and the bus. This means they are unaware of each other. It’s only through configuration that the bus knows which handler can handle what command. This makes the User Interface decoupled from the Application Core.

The flow of control

Now let’s check the flow at a high level. You can see that the Application Core has no dependencies. The dependencies go inward from User Interface to the Application Core, and from the Infrastructure to the Application Core.

Conclusion

The flow of control is a way to define the dependencies in your application. A good flow of control ensures that the dependencies are moving inside the Application Core. Anything outside it should depend on the ports and components of this application core. This is a way to ensure loose coupling between UI, the application core and external tools. And as a result, make it easy and fast to make changes to your application.