Building new, frontend applications presents some key challenges that must be overcome to ensure they deliver what the user expects from them. They need the flexibility to add new features, the scalability to grow the project or add new developers to it, and the intuitive capability for developers to locate and implement fixes and incorporate new elements.
We can define a few architectural characteristics (-ilities) for these requirements, for example:
Each characteristic matches with a requirement discussed above. For instance, scalability matches with growing the project or bringing new developers to it. Flexibility, readability and simplicity match with adding new features or bug fixes. Modularity matches with giving semantic information to developers to know where they need to go and what are the software boundaries.
Now we're able to start designing the project and architecture style.
Check out the react-example to see a simple demo project using the following principles and ideas addressed in the post.
For scalability, flexibility and modularity, we can use a modular architecture to define the boundaries of the subdomains in the project. By using a modular architecture we have a domain-partitioned project structure where we can see the boundaries between each subdomain.
Next, still focusing on flexibility, we should put some logical layers into the project — each layer will carry a single responsibility and make the code evolution simpler. In this case, we'll define these three layers:
In the application layer (a.k.a subdomains) will reside all logic related to the application itself. For example:
The container nested layer is related to dependency injection. Its responsibility is to provide concrete implementations for the abstractions and each business capability should have a single container.
All the code examples will be written using
React , as it’s the most popular frontend library as of now (you can use any other library or framework, though).
The interface nested layer is related to defining the markup and the UI for the application pages — it’s where all the HTML (JSX/TSX) code goes. Each page needs to have a single container attached to it, as it's a one-to-one relationship.
In the infrastructure layer, we'll include all the necessary details on how abstractions should work and perform their actions. For example, handling API requests (using REST or GraphQL), store management, application theme logic, navigation handling and other things.
Finally, the shared layer will contain elements that are used across the entire application, usually global abstractions such as UI/business components, utils, global types and interfaces, helpers, translations, enums, layouts, and more.
We should create ADR (Architecture Decision Record) document files to specify the reason for each definition, but this is a topic for another blog post — in the meantime, if you want information on creating ADR document files, I recommend you read this article . Below we can see a landscape of all these requirements being resolved by each characteristic.
Although the code examples were written using
React , this architecture style is technology agnostic — by this I mean, libraries and frameworks. The style can be used by
Angular Universal , etc…
There's no silver bullet to resolve all the problems and challenges we face when we're building and shipping software. Instead, we need to focus on decreasing the complexity to better handle problems like the ones I’ve covered in this blog post.
By making the project semantic, using a nice folder structure with a separation of responsibilities, incorporating well-defined context boundaries, applying modularization and adding logic layers to handle different concerns we're able to scale any software project.