Business requirements can be different and change frequently in the process, so it’s important to create an architecture that is flexible, scalable and maintainable. It’s also key that everyone, like team members and clients, understands the project clearly. To avoid extensive documentation, frequent meetings, and continuous refinements, we employ the following architectural approaches. This post explores the advantages of popular architectures and helps you choose the best solution to meet your unique requirements.
A lot of people think that the architectures listed below are just “folder structures”, which is only kind of true. In fact, if you look deeper into them, there are several important aspects:
Take a look at the following diagram — we’re focusing on the area in the bottom right corner, where these principles come together most effectively:
We want to realize these aspects with the help of architectures. Now let’s analyze each of them in detail and choose the most relevant one for a specific situation.
Classic architecture — is an approach that many of you already use. We usually focus on basic concepts, dividing the project into “pages”, “components”, “ helpers” and so on. However, the problem is that as the application grows, the structure starts to break down and it becomes much harder to find the right component or its business logic. Let’s look at this with an example:
In this example, we have 3 pages that are obviously not overused. However, they may include all the components shown below. If we look at these components, we see a real “chaos” 🤯: each component actively uses the others, creating dependencies between them. This makes them difficult to scale and difficult to reuse.
Here’s another example using Redux state manager:
In our setup, each component is managed by a designated “reducer” which handles its specific logic. However, some component logic was mistakenly placed in incorrect reducers. This might have happened because a developer was unaware of an existing file or didn’t consider creating a new one due to the limited amount of logic needed at the time. As a result, the logic for some components is now scattered across the project, making it less clear and harder to maintain.
we suggest reviewing the following diagram, which illustrates a missing architecture:
This approach — (or perhaps more correctly, the lack of architecture 🥲) often causes a chaotic environment in which dependencies are difficult to track, leading to confusion and making it difficult to support the project. However, it may be useful for specific cases such as:
Modular architecture — is an approach where the application is divided into layers (pages
, modules
, components
, UI
etc.) in which there are already independent modules with their own logic and area of responsibility.
In this example, you can see that the application layers are lined up in the same direction: pages
→ modules
→ components
→ ui
(or vice versa, if you look at it from the other side). This means that the higher the layer (e.g. pages
), the fewer layers from the lower layer it can use - components can’t use modules, but can use everything from the UI layer, while modules use components but can’t use pages. And pages already only use modules.
As we have already mentioned, each module has its own area of responsibility. It is also important to say that each module should have its own public API (index.ts
file), which encapsulates all the inner logic of the module and makes available only what is needed from the outside. (This is very similar to OOP principles: when a class has a lot of private methods that cannot be accessed from the outside, but can be used inside the class itself) When it comes to pages
, it is quite simple: ideally it should be just an encapsulation of modules and components, and all the business logic of the application should be placed at the level of modules and components.
(⚠️) IMPORTANT: a module shouldn’t use another module, and a component should’t contain complex logic. If logic is still needed, it should be as simple and easily maintainable as possible, otherwise — it’s a module!
Take a look at the following diagram:
There is one “but …” — we still have global directories like components/
and ui/
that can be overused. In some cases, the logic can grow and it is no longer always clear what is a component and what is a module. In addition, it is often observed that as the application grows, developers start using modules within other modules, which breaks the principles of this architecture and causes unnecessary dependencies. However, our architecture provides:
Feature Sliced Design (FSD) architecture — is a lot similar to modular architecture, but it also avoids the same “but…” we discussed above. This approach structures the project by functional areas (features) instead of just layers. This way of organizing helps to avoid global directory growth (like components
, UI
in modular architecture) and provides a clear separation of responsibilities between components, modules and layers.
The architecture is built in such a way that the top level pages
integrates and organizes the work of all submodules and components, and each next level provides more detailed and specific functions and elements. And yeah, we follow the same rule here — the higher the level (e.g. pages
), the fewer layers from the lower level it can use:
pages
that are displayed in the application.pages
are the main features
blocks that structure the core functionality of the page, making it managed and independent.entities
compiled from simpler UI components available in the “Shared” layer .(⚠️) IMPORTANT: as in the case of modular architecture, the layers don’t overuse each other.
The FSD architecture features modular elements known as “slices” and “segments”. “Slices” refer to modules within each layer, each representing a distinct business entity. Meanwhile, “segments” encompass various structural components like api/
, components/
, config/
, constants/
, and others, organizing the architecture into clearer, more manageable sections.
Finally, we’ve reached the target result:
This approach is not easy and fast to integrate into a project. It would require at least a minimum knowledge of architectures, and you have to keep in mind that it can take time. But mastering the use of FSD provides:
By the way, this architecture promotes “kebab case” in file naming 👀
product-description.vue
/shopping-cart.tsx
/get-base-url.ts
/ etc.
In this article, we have explored the differences between classical, modular, and FSD architectures, and discussed their usages.
If this article has been interesting to you, read my next post, consider joining us live for our web developer conference Webstack in Bratislava, Slovakia where many other interesting themes are coming up, and not just about PHP and Laravel!
Have a project in mind or just want to say hello? Feel free to reach out—I’d love to hear from you!