Modular architecture for medium frontend apps

created: 9 hours ago
updated: 9 hours ago
new
  • architecture
  • design

Intro

An entry that will try to dispel any doubts about the use of modular architecture in frontend applications.

There is nothing more beautiful in a developer's career than
well-designed
architecture.
Good architecture is easy to recognize. Ask yourself simple questions. Is what I'm doing obvious? Is it repeatable? Doesn't my work block others?
If all the answers are true, then you are working with a
well-designed
architecture.
What it means in practice? Together we will find the answer.
1. The problem
Let's say you have a simple feature to implement. It will be loading users and displaying loader/error based on API response.
Your code may look like the code you are about to see. If that's the case, you'll have problems that show up late, or to be more precise, as new functionality is added.
The issue will grow with the amount of functionality and the number of developers in the project.
Such an approach is vulnerable to changes in requirements and it violates the
open/closed
and
single responsibility principle
from
SOLID
.
Let's refactor this code and design real architecture step by step.
If you write an entire application like this, you have to reckon with the fact that it will not be stable and friendly to changes in business requirements.
2. Logic/presentation
Each application should have layers with clearly defined relations. When I say layers, I mean code fragments that are logically related to each other and recur across different functionalities.
An example of such a layer could be logic for
API requests
or
business logic
. In addition to identifying it, you still need to name it somehow, create abstractions and design relations.
In frontend applications, we can easily separate two layers -
logic and presentation
.
To be honest - it's not enough. The presentation can be split into additional layers and the same for logic. You should achieve this because if not - you are not able to reuse code.
3. Design phase
Firstly, check this diagram.
These blue boxes represent features. Each functionality should take advantage of this structure and relations. These rules are crucial when the solution is growing 🐍.
This approach gives us a division of application code and generic code that can be used in other solutions. Also, you can easily publish libraries via
npm
or migrate to
monorepo
later.
4. Libraries
First, let's create some components and place them in the
ui
directory. This library will store purely presentation components, you can treat it as a
design system
implementation.
Next, we will create a
development-kit
library that will store generic logic to handle API requests, rendering and anything that will be useful in various applications.
Now when you create another app, you already have some base - design system and a set of useful features that speed up the work.
If you are interested about
useFetch
hook you can check
useFetch hook
article.
5. Features
Let's start with the
service
layer that will be responsible for encapsulating the logic, performing API requests and carrying out the necessary data mapping.
Now, look at the next layer -
logic
. We will use
useFetch
generic hook to implement users fetch.
Logic done. Now it's time for the next layer -
components
, which can be used only inside this application. This layer can use models from the application domain.
It's time to glue everything with
container
. The layer that is supposed to bind the
logic
to the
component
.
Last layer will be a
module
. A starting point for using the functionality. Here you can add things like
lazy loading
or
authorization
logic.
For easier recognition of individual layers, you can add a post fix to the file name -
Angular
is doing that and me too.
6. A few doubts
What about the situation when module
A
should consume something available in module
B
? It will probably happen when you would like to implement an authorization mechanism in the app or something similar.
You can use the same approach as in
libs
directory. Just create
shared
folder and put there all reusable stuff connected with application.
7. Adding authorization
Let's imagine a situation when
UsersModule
is a protected view. Only authorized users can access it. Check the gif below to understand how it should work.
To access authorization state in different modules and be able to trigger authorization we need
Context API
from
React
and
AuthModule
itself. This stuff will be added to
shared
directory.
As before we will split it into layers to achieve goals. Firstly, we add service.
Next, we need authorization logic.
It's time for
module
. It groups the whole feature.
Now we need to wrap our entire application with
AuthModule
. Now you can use authorization!
Last step - just consume authorization API via
useAuthContext
hook and decide what should be displayed.
8. When to use?
Consider using this architecture when at least 2 or 3 of these points are truthy.
Full example
Fork
repository
to check the full solution or play with it on
Codesandbox
platform.
I hope you enjoyed my architectural proposal. It's mostly based on
Angular's
one.
It's not a perfect solution - as always it depends on the project. However, you have the next skill in your spellbook and you should be able to use this kind of approach.
Feel free to contact me if you have any questions/proposals. Have a nice day and good health!