

Feature sliced design vs clean architecture
Small overview of differences between FSD and Clean architecture
Introduction
This post is an extension of my previous article about clean architecture implementation in React. In this post, I’m going to compare Feature-Sliced Design with Clean Architecture to better understand the topic myself.
Feature-Sliced Design Summary
FSD is a modern architectural methodology for frontend which claims to be more practice-oriented in contrast to “theoretical” (as some say) Clean Architecture or Domain-Driven Design. I personally see FSD as an attempt to combine technical-based layers with domain-based slicing and focus on a front-end part, rather than a whole system.
According to FSD, an app is divided by 6 technical (I see them more as technical) Layers
, then each Layer
is divided by domain-based Slices
, and finally, each Slice
is divided by technical-based Segments
.
Source: feature-sliced.github.io
Similar to traditional layered architectures, FSD enforces its layer dependency rules.
The trick with layers is that modules on one layer can only know about and import from modules from the layers strictly below.
Slices cannot use other slices on the same layer, and that helps with high cohesion and low coupling.
I think that the trickiest part is to decide in which layer, slice, and segment specific code should belong. Even official FSD examples had multiple iterations with slightly different slicing logic. But it also has benefits by providing such flexibility.
Flow of Development
I think FSD and Clean Architecture have opposite development flows. Clean Architecture is usually explained as business-domain oriented, meaning that development starts with defining domain model entities, followed by application use cases, and ending up at the UI part on the framework layer. In contrast, FSD, being front-end oriented, follows a “pages-first” philosophy, meaning that development starts from application pages and ends up in a shared core. I guess FSD can be seen as a part of Clean Architecture. And the whole system will have 2 development flows (top-down, bottom-up) pointing to each other:
Layer Isolation
As far as my research goes, Clean Architecture explicitly describes the necessity
of dependency injection to adhere to the Dependency Inversion Principle of SOLID.
In my Clean Architecture implementation
I used composition root
to prevent direct dependencies between clean architecture layers.
On the other hand, FSD does not address the Dependency Inversion Principle. FSD does not restrict direct imports (if imported modules are on underlying layers), which in theory might increase coupling.
Implementation
When I started this article, I planned to create a “hello world” example, like I did with Clean Architecture previously. However, I realized that it isn’t worth it for the following reasons:
- There is a large collection of FSD example projects, which are better than my simple “hello world.”
- According to FSD’s page-first philosophy, the best way to implement my simple example (explained in my Clean Architecture post) is
to place everything in the
Pages
layer, because my example does not have shared logic and has only a single page. Also, there are a lot of questions regarding the slicing logic of an app, which is not strictly defined by FSD. Thus, I’m just going to briefly explain how I would implement my feature:
└── 📁src └── 📁app ├── app.ts └── 📁pages └── 📁homepage └── 📁ui ├── Homepage.tsx └── 📁model ├── Notebook.ts └── 📁api ├── notebookTauriRepository.ts └── index.ts └── vite-env.d.ts
app/App.tsx
: Entry point to the application. Traditional React’sApp.tsx
.homepage/ui/Homepage.tsx
: Self-explanatory. Here will be the page which should display the notebook’s name. It directly imports methods from themodel
segment to get the notebook’s name.homepage/model/Notebook.ts
: Here might be what I placed in an application layer in FSD: methods for getting notebook’s data, for example,Notebook.getNotebookName()
.homepage/api/notebookTauriRepository.ts
: Is a kind of adapter to the Tauri file reader mechanism. It will read the file, pass the result toNotebook.getNotebookName()
, which will extract the notebook’s name and pass it to the homepage.
So, a primitive feature requires only a single layer. Separation will be needed if there is
shared code. For example: if an app has 3 pages and every page requires access to notebook data, then
notebookTauriRepository.ts
might be moved to the widgets
layer. If there are multiple widgets which require
notebookTauriRepository.ts
, then it might be moved to the features
layer and so on, up to the shared
layer.
Afterword
FSD is quite an unconstrained framework, giving very few strict rules and varying pretty much even in simple examples. FSD tries to adhere to the natural front-end development flow. The main worry I have is that it does not address the Dependency Inversion Principle, which in theory creates tight coupling between layers. If shared UI is changed, then all the imports in upper layers should be fixed, making it look like shotgun surgery.
References
Happy coding! Feedback is appreciated.
← Back to home