When I started my first job I worked with PHP. We built WordPress projects - sites, ecommerce and the occasional plugin. After I did that for some time I had the chance to jump on a Laravel project and the difference in architecture, structure and conventions fascinated me.
Frameworks like Laravel can get you spoiled, because they do a lot of things under the hood for you. Pre-built mechanisms implement most of the things you'd need in an application so you can focus on the business logic. Such opinionated frameworks make your life easier by "holding your hand".
And then I got to work on an Express project. For those that don't know, Express is a minimalistic Node.js web application framework. My first encounter with it after working with Laravel left me culture shocked.
There are many libraries that you can piece up together to suit your needs. Github hosts countless Express scaffolding projects that come pre-configured. However, the question about The Right Way™ to structure an app was still bothering me.
Structure by Technical Role
Since Express is a minimalistic tool it doesn't come batteries included. Many examples take the MVC approach and structure apps by technical role. This means separating controllers, models, views, tests and other utilities in separate folders.
Especially for smaller applications this is an excellent approach. It makes it easy to find to find your way around a project. For example, if you need to debug a certain route handler you know that you need to look in the controllers.
Where this grows short is when you are dealing with a larger app. A project with more complex business logic also imposes a challenge. Sticking to the traditional architecture makes it hard to understand the domain that it represents.
Folders get bloated, the count of models rises and proper encapsulation becomes harder. A newcomer to the project would find it hard to change code since he won't be sure what parts of the app it can affect. People working on different features may have to modify the same utility functions.
At one point, the sensible thing to do is to further split folders by domain. We can move all controllers that contain product specific logic in a subfolder. Then we can do the same for models, views and other utilities. Afterwards, we need to make sure that these new "modules" do not depend tightly on each other.
This will slowly evolve into the structure that we will discuss bellow.
Structure by Components
There are at least two ways to structure any application. One is by grouping files by their technical role. The other is by splitting them depending on the part of the domain that they serve.
An application's domain is the area of expertise in which it works. Put in simpler words - the real-life problem that it solves. If you are building an accounting app articles probably won't be a part of your domain. Invoices on the other hand will.
I'm becoming more and more fond of domain-centric application structure. We will no longer need top level folders for controllers, models and views. Instead of that we will group them by domain - users, comments, products. Each of those folders will contain all controllers, models and utilities for that particular part of the app.
Most of the time we won't be working on many parts of an application at once. Modularizing a project in components makes it easy to work in isolation. This structure gives us confidence that everything related to a part of the domain is in the same place.
Personally, I'm fond of this structure because it gives a good bird's eye view of the project. You can easily tell what it is about and what components it consists of.
What Does Your Architecture Scream?
Earlier this year I finally decided to read Clean Architecture. I wanted to get a better understanding of the core principles behind designing applications. A particular quote from the book stood out to me:
So what does the architecture of your application scream? When you look at the top level directory structure, and the source files in the highest level package; do they scream: Health Care System, or Accounting System, or Inventory Management System? Or do they scream: Rails, or Spring/Hibernate, or ASP?
The idea of grouping code by business logic is covered in the book Domain-Driven Design. It emphasizes on the idea that applications used in different domains should not be designed the same.
An app that operates in the financial industry and one that operates in the medical one should be structured differently. The differences in how their domains operate should be visible in the codebase.
We need to structure depending on the real-world problems that our software solves. Every business domain faces different challenges, thus we shouldn't design applications the same.
In Building Microservices we can find similar ideas. The author talks about finding the boundaries (seams) in our application. The places where we can split our project in smaller modules.
Structuring our project early makes it easy to split it in microservices in the future if we have to.
I've been using the same approach in React applications as well. Instead of grouping all components together I place them in modules. Each module contains all components, state management and utilities that relate to it.
When you are looking in a module you should be confident that everything you need is there.
- Express apps give us the liberty to chose our own app structure. Thiscan be overwhelming for beginners or people that have worked on an MVC framework.
- Splitting the app in technological related parts is the most intuitive approach. While there is nothing wrong with that, it can be hard to manage, grow and modify if the app grows or it's of higher complexity.
- Structuring the app in component related to the part of the domain is a better approach. We encapsulate the different pieces of the app making it easier to build services in the future.
- The component architecture helps the developers get a better grasp of how the app works and the building blocks of which it consists.