This repository contains a simple, clean architecture-inspired TODO React application. Use this repository as
inspiration, browse it, and refer to it when setting up your next React application with clean architecture in mind.
You can use it as a starter template for your next app.
You don't want to read all the following stuff to understand clean architecture, its benefits, or why you should choose it to set up your next React application? Also, where and why does MVVM come into play, and what is this anyway?
We've got you covered!
Our colleague Marc used a prior and more basic version of this repository during
his talk at the code.talks 2023.
In his talk Developing a clean architecture-inspired React application with MVVM
, he explained the benefits of using
clean architecture, what MVVM is, and why it's a good choice to set up a clean architecture-inspired React application
and explained the code.
Here is a recording of the conference talk:
The slides are available here.
We also plan to release a blog post based on the conference talk. Once it's published, we will provide the link here.
The application in the repository was set up using vite, and the tests were written with vitest.
We chose Vite over Create React App (CRA) for our React setup due to performance gains, React team recommendations, and the ecosystem's evolution. CRA's development has ceased, prompting a shift to tools that better handle customization, optimization, and large projects. Vite excels with faster builds, modern JavaScript support, and a growing community, making it a fitting choice for us, though your project's requirements may warrant different tools.
Software development can be complex, with challenges such as code duplication, inconsistencies, and tight coupling. These can hinder scalability, maintainability, and flexibility. However, adopting an architectural pattern like clean architecture can mitigate these issues. Clean architecture promotes separation of concerns, modularity, and abstraction, leading to scalable, maintainable applications with easier migrations and component replacement. The following will introduce the clean architecture and Model-View-ViewModel (MVVM) patterns and their application in developing a React application.
Clean architecture is a software architecture pattern proposed by Robert C. Martin (aka. Uncle Bob) that was derived from other architectural guidelines like hexagonal architecture, onion architecture, etc.
[Source: Robert C. Martin (Uncle Bob) - The Clean Code Blog]
Clean Architecture is a design principle that enhances software maintainability, testability, and adaptability by separating core functionality from external dependencies. At its heart are business rules, the essential logic defining system behavior. This architecture promotes maintainability through clear layer separation, allowing changes to specific layers without affecting the whole system. It improves testability by isolating core logic from external dependencies and simplifying unit and integration testing. Lastly, it enhances adaptability, making it easier to meet new requirements or adopt new technologies. Thus, Clean Architecture is a powerful tool for creating robust, flexible, and testable software systems.
As you can see in the diagram, the pattern consists of different layers. Let's take a look at these layers in the following.
The Entities layer is the heart of Clean Architecture, encapsulating the core business logic and rules through domain objects. Its isolation ensures maintainability and testability, allowing changes in other layers without impacting the business logic. This modularity enhances flexibility and reusability across different systems.
The Use Cases layer defines the application's business actions and rules, detailing system behavior in response to external actor interactions.
The Interface Adapters layer serves as a bridge between the system's core logic and external environments, converting data formats and implementing interfaces for triggering use cases. It houses presenters, views, and controllers and is subject to change with evolving external requirements or technologies.
The Frameworks & Drivers layer, housing external frameworks, and libraries, serves as the glue code for the Clean Architecture pattern. It isolates infrastructure details, allowing easy component replacement without affecting the application. This layer, providing system access to external resources, is volatile and subject to frequent changes due to updates in these resources.
The Entities layer is the most general or abstract. The more you move outwards, the more concrete or specific the layers will get. The clean architecture cone, which you can see in the following image, illustrates this well.
[Source: Michael Outlaw - Clean Architecture – Make Your Architecture Scream]
The Clean Architecture cone illustrates the separation from the abstract, stable Entities layer at the core to the more concrete, frequently changing outer layers like Frameworks & Drivers. This structure enhances modularity and reusability, as the core business logic is independent of external technologies and can be applied across different systems. It also improves maintainability, as changes in one layer seldom impact others, simplifying system modifications. Testability benefits from this separation, allowing the core logic to be tested in isolation from external dependencies. The architecture's adaptability also facilitates easy updates or technology shifts without altering the core logic. Finally, the clear delineation of responsibilities across layers aids in understanding and modifying the system, making it more understandable and manageable.
As you have noticed while looking at the clean architecture diagram, arrows point from the outside to the inside.
In Clean Architecture, control flows inward, with outer layers aware of inner layers but not vice versa. For instance, use cases are aware of entities but not vice versa. Interfaces are used to facilitate interaction across layers, adhering to the Dependency Inversion Principle. For example, an email-sending interface in the Interface Adapters layer allows the Use Cases layer to send emails without knowing the implementation details. This enables the same Use Cases layer to be used with different email providers in other systems, showcasing the architecture's modularity and reusability. Dependency injection is employed to maintain this separation and ease maintenance.
We previously learned that using clean architecture as our architectural pattern of choice is beneficial when developing an application since it separates our system's core business logic from its external dependencies and makes it more maintainable, testable, and adaptable to change. To create a clean architecture-inspired React application, we utilize the MVVM pattern. The MVVM pattern aligns with the clean architecture principles by separating the user interface from the business logic. This makes our code more modular and reusable. Also, it will be easier to test our components in isolation.
The MVVM pattern is part of the well-known MV* patterns. The MV* patterns are a group of software design patterns that promote a clear separation of concerns. The group's most popular patterns are Model-View-Controller (MVC), Model-View-ViewModel (MVVM), and Model-View-Presenter (MVP).
The group's oldest and most widely used pattern is the MVC pattern, which was introduced in the 1970s. It separates a system into the following three components:
- Model: The Model represents the data of the system.
- View: The View displays the data from the Model to the user.
- Controller: The Controller handles the user input and updates the Model and view accordingly.
The other MV* patterns are structured similarly but have peculiarities. In general, they share the following characteristics:
- They all promote a clear separation of concerns, which makes the code more modular and reusable. It also eases testing of the different components of the system by enabling us to test them in isolation.
- They all use a Model-View separation, meaning that the system's data is kept separate from the code that displays it.
- They all use a Controller or Presenter to handle user input and update the Model and View accordingly.
The overall goals of the MV* patterns are:
- A clear separation of concerns
- Make the code more modular and reusable
- Make the code easier to test
- Make the code more maintainable
- Make the code more adaptable to changes
The MVVM pattern is a newer addition to the MV* patterns and was introduced by Microsoft in the early 2000s. It is a more modern take on the MVC pattern that replaced the Controller with a new ViewModel component. The pattern consists of the following components:
[Source: T. Drilling & S. Augsten - Durchblick im JavaScript-Ökosystem]
- Model: The Model represents the system's data and business logic. It is responsible for storing and managing the data.
- View: The View is responsible for displaying the data from the Model to the user and sending the user input back to the ViewModel.
- ViewModel: The ViewModel bridges the Model and the View. It exposes the data from the Model to the View, and it handles user interactions and processes them into actions on the Model.
The MVVM pattern is well-suited for a clean architecture-inspired React application as it enforces separation of concerns, dividing UI (View), presentation logic (ViewModel), and data (Model). This separation enhances code modularity and testability, allowing for isolated component testing. MVVM's ViewModel abstracts the UI from business logic, aligning with clean architecture's principles by keeping UI and business concerns distinct. It also ensures a controlled data flow from ViewModel to View, centralizing presentation logic and business rules. Furthermore, MVVM supports dependency injection, a critical clean architecture principle that enables the decoupling of components for greater maintainability and adaptability and upholds the Dependency Inversion Principle.
This template consists of a simple TODO application. Since this is a relatively simple example, some parts or components of the application may be fairly basic. For instance, the use cases are very simple and will only call corresponding repository methods and return the result of these method calls. Of course, Use Cases can be more complex. For example, they may call different repositories and may map data. The example application is only a basic template for creating your clean architecture-inspired React application.
The TODO application's features are:
- Get and display a list of Todos
- Create a new Todo
- Update an existing Todo
- Delete an existing Todo
Since this template's TODO application is a clean architecture-inspired React application, let us look at which part of the app belongs to which layer of clean architecture. As previously mentioned, clean architecture consists of four different layers. The inner one is the most abstract or general, and the more we move outward, the more specific the layers get and the more frequently they change.
The following list will give us an overview of which part of our application belongs to what layer of clean architecture:
Entities (Enterprise Business Rules):
- Todo
Use Cases (Application Business Rules):
- Get Todos Use Case
- Get Todo Use Case
- Create Todo Use Case
- Update Todo Use Case
- Delete Todo Use Case
Interface Adapters:
- Presenters
- Todo List View & ViewModel
- Create Todo View & ViewModel
- Todo Details View & ViewModel
- Todo Repository
Frameworks & Drivers:
- Todo Database / Store
As you can see, the application is clearly inspired by clean architecture and follows its structure and principles.
If you look at this template's code and structure, you will see that its structure reflects the clean architecture pattern.
As you can see, the src
directory contains an adapter
sub-folder that contains the repository todoRepository
. The
sub-folder data
includes the data source todoLocalStorageDataSource
, in this case, the browser's local storage.
Below data,
you can see a folder di
. This folder contains the container used for dependency injection.
Furthermore, you can see a domain
folder. In this folder, you can find the Todo
entities, the interface of the
repository, and the use cases, for example, the Create Todo Use Case createTodoUseCase.
Finally, the folder, presenter
, contains the presenter. You can see a sub-folder pages
here. This folder includes
all the application's pages, namely the CreateTodo
page, the TodoDetails
page, and the TodoList
page. Each
sub-folder of pages
contains a View and a ViewModel. For example, the TodoList
folder contains the
View TodoList
and the ViewModel TodoListViewModel
.
The folder presenter
also contains a sub-folder components
. This folder is structured by following the Atomic Design
principles. You can learn more about Atomic Design here. In our case,
the components
folder contains the folders atoms
and molecules
. atoms
are the smallest parts and simplest
components of an application. For example, a button is an atom
. molecules
are more complex components that consist
of multiple atoms
. In our case, we have a List
component. This list consists of multiple list items. The list items
are atoms
. Depending on how complex the list items are, they could be themself molecules
, and the list would then be
an organism
. Atomic Design furthermore knows templates
and pages
. But in the case of this TODO app, we don't need
such complex components.
Software development faces challenges like code duplication, tight coupling, and maintenance difficulties. Adopting architectural patterns like Clean Architecture and the MVVM pattern is beneficial to address these. Clean Architecture separates business logic from external dependencies, enhancing maintainability, testability, and adaptability. It allows for easier code maintenance, effective layer testing without external dependencies, and flexible design for new requirements. The MVVM pattern, complementing Clean Architecture, divides UI (View) from presentation logic (ViewModel) and data (Model), promoting modularity and simplifying testing. This approach enables independent ViewModel testing, facilitates UI design changes without affecting business logic, and supports modular, maintainable, and adaptable software development. Clean Architecture and MVVM offer a robust framework for creating high-quality applications.
For further inquiries, feel free to contact us at [email protected].