You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
title: How did I organize my last Symfony projects?
2
+
title: Symfony, Hexagonal architecture and CQRS
3
3
date: 2021-03-30
4
4
image_credit: alexkixa
5
5
url: hexgonal-architecture-and-cqrs-with-symfony
@@ -11,9 +11,9 @@ keywords: "software,software architecture,symfony,cqrs,cqrs with symfony,php,hex
11
11
tags: [software-architecture, symfony]
12
12
---
13
13
14
-
In this blog post, I will explain how I organized my last Symfony projects. They are mainly inspired by Hexagonal and CQRS architecture. Keep in mind that I did not try to implement these architectures by the book, I only took some concepts that helped me to have a simple and clear codebase organization.
14
+
In this blog post, I will explain how I organized my last Symfony projects.I mainly use Hexagonal Architecture and CQRS. Keep in mind that I did not aim to implement these architectures strictly by the book. I only took concepts that helped me to create a straightforward and well-organized codebase.
15
15
16
-
If we have a look at the project’s root, nothing special happens, I kept all folders and files created during Symfony installation.
16
+
Looking at the project’s root, there’s nothing particularly unusual. I kept all the folders and files generated during the Symfony installation.
17
17
18
18
```bash
19
19
tree . -L 1
@@ -31,43 +31,52 @@ tree . -L 1
31
31
└── vendor
32
32
```
33
33
34
-
In the next sections, we are going to see how I organized the src folder.
34
+
In the following sections, we will explore how I organized the application sources using Hexagonal Architecture and how CQRS helped me simplify the modeling write and read usecase.
35
+
## My Approach to hexagonal architecture
35
36
36
-
## Hexagonal architecture
37
+
> The hexagonal architecture, or ports and adapters architecture, is an architectural pattern used in software design. It aims at creating loosely coupled application components that can be easily connected to their software environment by means of ports and adapters. This makes components exchangeable at any level and facilitates test automation.
The foundation of the hexagonal architecture is the explicit separation between the domain (inside) and the infrastructure (outside). All dependencies are going from Infrastructure to the Domain.
41
+
The main advantage of Hexagonal Architecture is that it decouples the heart of your application from [Input/Output](https://press.rebus.community/programmingfundamentals/chapter/input-and-output/).
42
+
I call the heart of the application the `Domain`. This is the area of the app where all the pieces of code represent the problem we are solving. This part must be side-effect-free, it must not rely on any tools, frameworks, or technologies.
43
+
`Output` refers to the tools the application needs to work, such as network calls, database queries, filesystem operations, actual timestamps, or randomness. All `Output` is moved to the infrastructure. `Input` refers to how the domain is exposed to the outside world, for example, it can be a web controller or a CLI command. These pieces of code are moved to the `UserInterface`.
39
44
40
-
The domain is the part of the application that contains your business logic. It must reflect as much as possible the problem your application has to solve. This part of the application must not use IO, the infrastructure contains them all. For instance, IO are side effects like network calls, database queries, filesystem operations, actual timestamps or randomness..
45
+
**Note:** Check out my blog post about Hexagonal Architecture to dive deeper into the subject:
Based on this approach, my first decision was to split the `src` directory into three areas: `Domain`, `Infrastructure`, and `UserInterface`.
41
50
42
-
Based on that information my first decision was to split src into two areas: `Domain` and `Infrastructure`.
43
51
44
52
```bash
45
53
tree src/Domain/ -L 1
46
54
api/src/Domain/
47
55
├── Domain
48
-
└── Infrastructure
56
+
├── Infrastructure
57
+
└── UserInterface
49
58
```
50
59
51
-
**Coupling rules:**
52
-
* Domain must not depend on the Infrastructure.
53
-
*Domain must not use IO
60
+
**Coupling rule:**
61
+
*`Domain` must **not** depend on the `Infrastructure` and `UserInterface`.
62
+
*`Infrastructure` and `UserInterface` can depend on the `Domain`.
54
63
55
-
I am not a big fan of the onion architecture because I want to keep my projects as simple as possible. Having a lot of layers can be really hard to maintain because you need to align the whole team on the coupling rules. Agreeing with yourself is not easy, so getting several people to agree may be really hard. Here, we only have a single rule.
64
+
I am not a big fan of Onion Architecture because I prefer to keep my projects as simple as possible. Having many layers can make maintenance challenging, as it requires aligning the entire team on coupling rules. Even agreeing with yourself can be difficult, so getting several people to agree is often much harder. Here, we follow just one simple rule : `Domain` must **not** use IO
56
65
57
-
Sometimes, I needed to write libraries because I could not find any opensource libraries that match my expectations. To avoid coding in the vendor directory, I introduced a third area called `Libraries` (this new area is optional). Those libraries may be used in the domain and the infrastructure but their usage should not break the coupling rules that are defined for those areas.
66
+
At times, I needed to create custom libraries because I couldn’t find any open-source libraries that met my expectations. To avoid coding directly in the `vendor` directory, I introduced a third area called `Libraries` (this area is optional). These libraries can be used in both the `Domain` and `Infrastructure` layers, but their usage must not violate the coupling rules defined for those areas.
58
67
59
68
```bash
60
69
tree src/Domain/ -L 1
61
70
api/src/Domain/
62
71
├── Domain
63
72
├── Infrastructure
64
-
└── Librairies
73
+
├── Librairies
74
+
└── UserInterface
65
75
```
66
76
67
-
**Coupling rules:**
68
-
* Libraries must not depend on Domain and Infrastructure
77
+
**Coupling rules:**`Libraries` must **not** depend on `Domain` and `Infrastructure`
69
78
70
-
Finally, I created a “sub” area called `Application` in the infrastructure that contains all pieces of code needed to have an application up and running: framework code (Symfony kernel, framework customizations), data fixtures, and migration.
79
+
Finally, I created a sub-area called Application within the Infrastructure layer. It contains all the code needed to have the application up and running, such as framework code (Symfony kernel and framework customizations), data fixtures, and migrations. In the following example, `Exception` and `Security` folders contain framework customizations.
In this example, `Exception` and `Security` folders contain framework customizations.
83
-
84
-
{{< training-link >}}
85
91
86
-
## Business first
92
+
**Note :** Looking back, I won't keep the folder in infra. All code related to framework customization should go into a dedicated folder called framework in the `Libraries` folder, whereas `Fixtures` and `Migrations` can remain at the root of the infrastructure folder.## Focus on the business
87
93
88
-
A really important thing for me is to drive codebase organization by business concepts. I don’t want to name folders and classes with technical patterns like factory or repository for instance. Non-tech people should be able to understand what a class does thanks to its name.
94
+
A really important aspect for me is organizing the codebase around business concepts. I avoid naming folders and classes based on technical patterns like Entity, ValueObject, or Repository, and especially not Provider, DataMapper, or Form. Non-technical people should be able to understand the purpose of a class simply by its name.
89
95
90
96
### Domain
91
97
@@ -96,7 +102,7 @@ api/src/Domain
96
102
└── Map
97
103
```
98
104
99
-
Because I did not use any technical words to name folders we can easily imagine the project is about making maps. Now, let’s have a look inside the`Map` directory:
105
+
Since I avoided using technical terms to name folders, it's easy to imagine that the project is about creating maps. Now, let’s take a look inside the
100
106
101
107
```bash
102
108
tree src/Domain/Map -L 1
@@ -115,65 +121,70 @@ tree src/Domain/Map -L 1
115
121
└── UseCase // Use cases orchestration
116
122
```
117
123
118
-
In this folder, we have all the pieces of code needed to design the `Map` aggregate. As you can see, I did not organize it by design patterns like `ValueObject`, `Event` or `Exception`.
124
+
In this folder, we have all the code necessary to design the `Map` aggregate. As you can see, I didn’t organize it by design patterns like `ValueObject`, `Entity`, or something else.
119
125
120
-
As you might have understood the `Map` entity has a one-to-many relationship with the Marker entity. All classes needed to modelize this entity are in the Marker folder and they are organized the same way as the `Map` directory.
126
+
As you might have noticed, the `Map` entity has a one-to-many relationship with the `Marker` entity. All classes required to model this entity are located in the `Marker` folder, which is organized in the same way as the `Map` directory.
121
127
122
-
The `UseCase` folder gathers all pieces of code needed to orchestrate use cases like command, their handler and business validation.
128
+
The `UseCase` folder contains all the code needed to orchestrate use cases, such as commands, their handlers, and business validations.
123
129
124
-
**Tip:** I don’t suffix repositories by ‘Repository’ but I try to use a business concept to name them like`ProductCatalog` for a `Product` aggregate. If I can find a business concept to name it I use the plural of the aggregate because a repository is a collection of objects.
130
+
**Tip:** I don’t suffix repositories with "Repository." Instead, I try to use a business concept for the name, such as`ProductCatalog` for a `Product` aggregate. If I can’t find a suitable business concept, I use the plural form of the aggregate name, since a repository represents a collection of objects.
125
131
126
132
### Infrastructure
127
133
128
-
I organize the root of the `Infrastructure` folder the same way as the `Domain` one.
134
+
I organize the root of the `Infrastructure`and `UserInterface`folder in the same way as the `Domain` one.
129
135
130
136
```bash
131
137
tree src/Infrastructure -L 1
132
138
api/src/Infrastructure
133
-
├── Application
139
+
├── …
134
140
├── Cartographer
135
141
└── Map
142
+
└── InMemoryMaps.php
143
+
└── PostgreSqlMaps.php
136
144
```
137
145
138
-
Now, let’s have a look at the `Map` directory:
139
-
140
146
```bash
141
-
tree src/Infrastructure/Map -L 1
142
-
api/src/Infrastructure/Map
143
-
├── Storage
144
-
└── UserInterface
145
-
└── Web
146
-
└── Cli
147
+
tree src/UserInterface -L 1
148
+
api/src/UserInterface
149
+
├── …
150
+
├── Cartographer
151
+
└── Map
152
+
└── WebAddMarkerToMap.php
153
+
└── CliAddMarkerToMap.php
147
154
```
148
155
149
-
The `Storage` namespace gathers everything related to data storage like repositories, queries. The `UserInterface` namespace gathers everything related to ways to interact with the application like the WEB API (controllers) called by the front application or CLI (Symfony commands).
156
+
## My Approach to CQRS
157
+
158
+
> Starting with Command Query Responsibility Segregation, CQRS is simply the creation of two objects where there was previously only one. The separation occurs based upon whether the methods are a command or a query (the same definition that is used by Meyer in Command and Query Separation, a command is any method that mutates state and a query is any method that returns a value).
The main idea of CQRS is the separation of the read and write sides. You can use different models for writing (commands) and reading (queries). I appreciate the concept of having two small, simple models dedicated to specific : purposes reading or writing instead of relying on one huge model. This approach helps prevent your aggregate from becoming a "god object," which can happen as the system grows and more read and write use cases need to be handled.
150
163
164
+
Additionally, when you link aggregates by their IDs instead of direct references, complex read use cases can become challenging. How do you retrieve information from several aggregates? It’s simpler to query the database directly rather than merging data from multiple aggregates.
151
165
152
-
## CQRS
166
+
**Note:** Check out my blog post to understand the difference between CQS and CQRS:
153
167
154
-
CQRS is the acronym for Command Query Responsibility Segregation. The main idea of CQRS is that you can use different models for writing (command) or reading (query) information. I like the idea of having two small and simple models dedicated to a precise purpose: reading or writing instead of having one big model. It can prevent your aggregate from becoming a god object because as things progress you can have many write and read use cases to handle.
From this pattern, I decided to split the domain into two areas, the first one: `Command` and the second one: `Query`. It allows me to design a model with the same name for these reading or writing purposes.
170
+
To manage having two models with the same name, I decided to split each subfolder of the domain into two areas: `Command` and `Query`. This structure allows me to design models with the same name, tailored to either reading or writing purposes.
157
171
158
172
```bash
159
173
tree src/Domain/ -L 2
160
174
api/src/Domain/
161
-
├── Command
162
-
│ ├── Cartographer
163
-
│ └── Map
164
-
└── Query
165
-
├── Cartographer
166
-
└── Map
175
+
├── Cartographer
176
+
│ ├── Command
177
+
│ └── Query
178
+
└── Map
179
+
├── Command
180
+
└── Query
167
181
```
168
182
169
-
**Coupling rule:**
170
-
*`Command` area must not depend on the `Query` area and the other way around.
183
+
**Coupling rule:**`Command` area must not depend on the `Query` area and vice versa.
171
184
172
-
**Note:** I did not make major changes in the infrastructure, the only change I made is to split the storage into two areas like the domain.
173
-
174
-
**Caution:** For those projects, I did not make any projections because my database schema remained simple so I did not need them. I only decided to split my models because my codebase was simple and clearer this way.
185
+
**Caution:** Using CQRS, as defined by Greg Young, doesn’t mean introducing unnecessary complexity into your application. You don’t need a command and query bus, an event-sourcing architecture, or multiple databases to apply it. I chose to separate write and read use cases because it made my codebase simpler and clearer.
175
186
176
187
## Last word
177
-
I tried for the last few years to find the perfect architecture but it does not exist. I just tried to use some architectural concepts that make me and my teammates comfortable to work on a daily basis. This project organization has been used for two projects that are in production. One of these projects is a side project I made for fun to create maps without Google Maps. The second was a professional project, real people use it on a daily basis.
178
188
179
-
Thanks to my proofreader [@LaureBrosseau](https://www.linkedin.com/in/laurebrosseau).
189
+
I’ve spent the last few years trying to find the perfect architecture, but I’ve realized it doesn’t exist. Instead, I’ve focused on using architectural concepts that make me and my teammates comfortable working on a daily basis. This project organization has been applied to multiple production projects. One of them is a [side project](https://mymaps.world) I created for fun to build maps without relying on Google Maps. The others are a professional project that real people use daily.
0 commit comments