if you continue to use this site, you consent to our use of cookies
OK
Knowledge Sharing
2.6.2024

Elevating React Development: A Blueprint for MVVM and Clean Architecture Harmony

Elevating React Development: A Blueprint for MVVM and Clean Architecture Harmony

Let’s start with a must-read book for software developers — Clean Architecture by “Uncle Bob” (Robert C. Martin). This work advocates creating software systems with a clear and modular architecture, emphasizing the separation of concerns and the independence of different architectural components. It has profoundly impacted the industry, influencing coding standards and practices worldwide.

Yeah, we all know this and understand the “rules.” But do we use them? If so, can we analyze the pros and cons of our architecture, especially in front-end development, where almost everything is dictated by companies whose frameworks and libraries we use? Today, the significant trustworthy open source is not so open anymore. Therefore, all our front-end applications are increasingly tied to some ecosystem, and it seems like a Swiss knife to all our problems. 

But is it so? Where is the application’s architecture from Clean Architecture, which is the opposite of what frameworks and libraries provide us? What if React is not in the arena anymore next year? Or will a new game changer, “10x faster and so on,” appear? How can we replace React or any other technology in our front-end application with minimum or no impact on the whole codebase and business logic unrelated to rendering our interfaces? Let’s find an answer.

MVVM with React

In this article, we`ll assume you’re an advanced engineer and know what MVVM or any other MV* patterns are, and your only question is how to use it on the front end within the React application.

Clean Architecture reveals the concept of layers of architecture by advocating for a separation of concerns and a modular design. Each layer focuses on a specific aspect of the application, making it easier to understand, modify, and extend the software.

The layers typically include:

  1. Presentation Layer. This layer is responsible for handling user interface and input. It should focus on presenting information to the user and capturing user input. According to Clean Architecture, this layer should be independent of the business logic.
  1. Business Logic Layer. The core of the application resides in this layer. It contains the business rules, algorithms, and domain logic that drive the application's functionality. The book emphasizes keeping this layer independent of the presentation and data access layers.
  1. Data Access Layer. This layer is responsible for interacting with databases or external data sources. It encapsulates the code that deals with storing and retrieving data. Like the other layers, Clean Architecture suggests isolating the data access code to maintain a clear separation of concerns.

The layers in Clean Architecture, from the innermost to the outermost, are:

  1. Entities:
  1. Are located at the core or center of the architecture.
  2. Contains business entities and rules representing the application's core business logic.
  3. Independent of any external frameworks or databases.
  1. Use Cases (Interactors):
  1. Surrounds the Entities layer and represents application-specific business rules.
  2. Orchestrates the flow of data between entities and executes business rules.
  3. Application-specific use cases encapsulate the behavior of the system.
  1. Interface Adapters:
  1. Converts data from the use cases into a format suitable for the outer layers.
  2. Adapts the data for presentation in the UI or for external frameworks.
  3. Includes presenters and controllers in the context of user interfaces.
  1. Frameworks and Drivers:
  1. The outermost layer is responsible for external details and mechanisms.
  2. Contains all the frameworks, tools, and external systems such as databases, web frameworks, etc.
  3. This layer is most volatile and changes frequently without affecting the inner layers.

The key idea behind Clean Architecture is the dependency rule — dependencies should always point toward the center. In other words, the inner layers (Entities and Use Cases) should not depend on the outer layers. This design enables the core business logic to be independent of the user interface, databases, and other external factors, making the system more adaptable to change.

Now, we need to map our MVVM pattern to the layered architecture concept and understand which layer stands for every part of MVVM.

The layers in MVVM might be organized as follows:

View:

  • Corresponds to the "Presentation Layer".
  • Responsible for displaying information to the user and capturing user input.
  • Focuses on the UI components and their layout.
  • Ideally kept as thin as possible, with minimal logic, delegating most functionality to the ViewModel.

ViewModel:

  • Resembles the "Business Logic Layer".
  • Acts as an intermediary between the View and the inner layers.
  • Contains presentation logic and orchestrates interactions between the View and the underlying layers.
  • Maps user input from the View to use cases in the application layer.
  • Strives for testability and maintainability by keeping business logic separate from the UI.

Model:

  • Equivalent to the entities and use cases within the application layer of clean architecture.
  • Represents the core business logic and data of the application.
  • Managed independently of the user interface concerns.
  • Independent of the user interface and external frameworks.
  • The Model in MVVM often includes the actual data (Model) and the business logic (partly), while the complete business logic is typically found in the application layer of clean architecture.

By adhering to the MVVM pattern, we aim to achieve a clear separation of concerns, focusing each layer on its specific responsibilities. This modular approach facilitates code readability, maintainability, and testability, which is precisely what we need.

There's always a “but”...

The fact is we developers are lazy. That’s why we love all code-generation tools and AI helpers in our IDEs; we don’t want to write many lines of code. It is why many modern React applications have a strict binding to everything in their ecosystem, and almost everything is done on the “View Layer” — the React level. We love react-query (now it’s TanStack Query, not so “open” open-source, remember?) and all other react libraries that help us fetch, store, and update the data in addition to simply rendering this data. 

But this approach has a solid binding for React. We render with React, fetching in the React layer, storing in the React layer, and manipulating data in the React level. It’s fast and easy, which is excellent, but one day, we just can’t get rid of React because our React-related codebase is not only about rendering the UI. It has an ecosystem for every process in our tool that just doesn’t work without React. I assume many of you have heard about or even had an “amazing” experience on the project with the codename “Transfer Framework X to Framework Y.” In most cases, it’s not an ordinary task. That’s what layered architecture is for, to make it possible without changing the ~85% of the codebase.

Implementation example

Let’s add some specific details to each layer.

So, View is a React layer. Here, we have only React-related codebase and React ecosystem — some 3rd-party components, hooks, etc. This layer only receives data from ViewModel and doesn’t change that data. This layer doesn’t work with API or any services. All data changes and preparations for UI are done within the ViewModel layer.

ViewModel is a UI business logic layer. Here, we work with user inputs, map data to the needed structure for rendering, and receive that data via API Services or any other Services, Stores, or Use Cases. ViewModel is an entry point for our UI to all other application levels. This can be implemented in functional or object-oriented programming styles. I prefer the OOP way. With OOP and TypeScript as a SOLID guard, all our layers can rely on interfaces instead of implementations, use handy Dependency Injection techniques, and much more.

The model is a data layer of our application. It works with our Entities and is rarely a part of their business logic. Entity encapsulates the application's most critical and general-purpose business rules. Key characteristics of Entities in Clean Architecture include:

  • Business Logic
  • Independence from Frameworks
  • Persistence Ignorance
  • Long-Term Stability

Examples of entities might include objects representing concepts such as customers, orders, products, or other domain-specific entities crucial to the application's functionality.

Now, we need a way to distribute our entities through layers and even connect their state to our UI state. Here, the state management libraries come in handy. Such as Redux, Recoil, MobX, Xoid, etc. Anything where we can organize the data storage, react to changes in that storage, and update our UI or other application Layers. It’s better to be framework agnostic and have other libraries that can help connect the store state to React or any front-end UI ecosystem. 

Summary of the workflow of the application described above:

  • React renders the data that it gets from ViewModel.
  • React captures user inputs and provides them to the ViewModel.
  • React knows nothing about storing the data, what communication protocols we use for API calls, and how we should change the data after receiving it from a database or any other provider.
  • With the help of Dependency Injection, ViewModel has access to any service, store, API service, or Use Case and can prepare the data for UI and vice-versa prepare data to make an API call, for example, HTML Forms. It’s the only layer that Reacts components communicate with.
  • Data storage and the Use Cases level work only with Entities and their business logic; they know nothing about React and ViewModels.

Conclusions

In summary, adopting the MVVM pattern within a React application aligns with the principles of Clean Architecture, offering a structured approach to managing business logic, user interfaces, and data layers. We can achieve a more modular, maintainable, and adaptable codebase by adhering to these architectural principles. Embracing this approach enhances code readability and ensures that front-end applications remain resilient to future technology shifts. The MVVM pattern and clean architecture principles provide a solid foundation for building robust and scalable applications as we navigate the dynamic front-end development landscape.

Anton Yatseniuk
Anton Yatseniuk
Frontend Competence Manager

Let's engineer your breakthrough

Share your idea
arrow
09:45
AM
Lviv / HQ Office

79015, Smal-Stotskoho St. 1 Ukraine
09:45
AM
Wroclaw / Development Office

50-062, Plac Solny 15 Poland
STAND WITH
Ukraine