Skip to content

Instantly share code, notes, and snippets.

@Kjaer
Last active April 9, 2023 00:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Kjaer/8d71bd38bab1ee3f89a01f84c284bca1 to your computer and use it in GitHub Desktop.
Save Kjaer/8d71bd38bab1ee3f89a01f84c284bca1 to your computer and use it in GitHub Desktop.
Clean + MVVM Architecture
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

#separationOfConcerns #singeResponsibility #scalability #compositionOverInheritance #looslyCoupled

An Event based Clean + MVVM Architecture for Plugin Apps

This document introduces the Clean + Model-View-View-Model (MVVM) front-end architecture will be using to develop Plugin Apps (micro-apps), in the Crate Solutions Single Page Web Application.

Introduction

Given future product plans, the Crate Analytics Platform (CAP) front-end architecture must be able to:

  • work for multiple clients
  • work for different business models
  • handle applications of a larger scope than the current client (Alpla)

Separating business related information in the application is our first action to take. For this, we're leveraging Clean Architecture[4] capabilities that describes us how to abstract away all the business data that becomes foundation of the application.

Building large-scale applications in JavaScript ecosystem has changed at least one fundamental way in the last few years. The one key concept that many developer adapted on is composition.(1) Functional paradigm is one of the agreement out of architecture proposal meetings and our architecture solution should favour composition over inheritance. Over the years we practiced with the component-based libraries like React, and frameworks like Vue and Angular. As a result we are familiar with modularising an application, composing components to build client-side JavaScript applications.

There is still a problem we need to compete. No matter how 3rd party component tools make easier the development, they don't impose any technique or approach how to organize your code base. They left it to whom use it. Model-View-ViewModel architecture is filling the gap for this requirement and tell us how to organize our application codebase at scale. MVVM is keeping Views free from business related information thanks to its ViewModels and those ViewModels are granular enough to contain particular informations. More general data will be dealt inside the Models at which state managers are fit in.

MVVM is enforcer of single responsibility. So that, it draws clear and self-evident boundaries around app components and models(2)

Last but not least, proposed architecture (Clean + MVVM Architecture) is a recipe how to glue business data with our component-based application and keeping them decoupled from each other.

Our components must be free from any direct business data manipulation. Decoupling business related information out of Application Logic will prevent scalability issues in future too.

Finally Events are channeling the information between tiers, without breaking decoupling nature of the architecture. At any given time, units (not the business related data, they're all about business concerns) emits and listens to events to carry information around.

Concepts Overview

Proposed architecture blends existing and proven techniques, and moves in the direction of a general method for building apps (2). It does not aim to impose new conventions or concepts, nor does it require us to introduce new roles/capabilities.

This is an event-based, 3-tier architecture. At the highest level, there are three topics

  • Domain,
  • Data,
  • and App for which guide application a structure. They are discussed in detail in the following section.

Domain Tier

Borrowed from Clean Architecture, this relates to building or forming business data.

Unlike Clean architecture constraints, the Domain layer is not the innermost nested layer, but the leftmost layer. It is independent from the development stack, and it must be written purely in the programming language.

The business related informations of the system, do not need to know what kind of application do they serve. Developers should focus on abstraction of business data while they working on the domain section.

Domain is the first place where separation of concern is coming in action.

Entity

Entities are meaningful pieces of information that map to the business you're dealing with. They're also known as business objects. An entity can be an object with methods, or it can be a set of data structures and functions(4). From that characteristics, we can say entities are used for encapsulating enterprise-wide, the most generic and high-level business rules.

Entities are the least likely to change when something external changes. For example, you would not expect these objects to be affected by a change to page navigation, or security. (4)

Use Cases

Use Cases are your business logic(1). To simply put, Business behaviours are defined as use cases and they generally used to complete a user's operation.

Use Cases coordinate the flow of data to and from the entity layer, and in the process achieve the goals of the use case by enforcing the entity's business rules. Changes to the use case (logic) do not affect the entity (rule). (7)

(Following sections are cited from this blogpost -- https://proandroiddev.com/why-you-need-use-cases-interactors-142e8a6fe576)

Useless Use Cases

Many times while writing use-cases, you end up use-cases are only wrapper for repositories. Even if they “do nothing” other than just calling a repository method, there are several reasons for still using them:

First: Consistency

Wouldn’t it be bad if some models would call use-cases while others would directly call repositories? Some less experienced developers or new joiners will have a hard time understanding what they are supposed to do while looking at the codebase.

Second: They protect the code from future changes

One of the purposes of Clean Architecture is to give you a codebase that can easily adapt when requirements change, which also means that the amount of code that needs to change should always be minimum.

If you skip creating use-cases, and using repositories directly in Models or worse in ViewModels, then when you require to update a repository for some reason, you're going to have to be make sure where this repository is used and make the changes accordingly.

Third, The “Screaming Architecture”

In most of the projects that you’ll see you won’t be able to understand what the app is about at first look. This might not bother you since maybe you started the project from scratch, but what about new joiners? How long will it take to them to understand what they have to do and how? Sake of predictability, and let your app screaming what it's doing, keep stick of the use-cases and repositories along with it.

A Use Case per Repository method?

This is a very common question and the answer is: most probably yes.

You don’t have a Use Case because you have a repository method, you have a repository method because you have a Use Case that needs it.

Clean Architecture is a Use Case driven architecture, hence each repository method exists only because it is supporting a Use Case.

It is also true that a Repository method should be used by one, Use Case so that if we get rid of a Use Case of the app then the persistence logic that is supporting it should disappear as well.

Now, I said you’ll “most probably” have a Use Case for each repository method, the reason why is not always true is that a Use Case can use more than one Repository and more than one method (as far as it is the only Use Case using them).

Data Tier

This is the aggregation tier where domain is used as inputs and prepared for the application state. It is also responsible of persisting/receiving data from outer sources, dealing data produced by application.

Data tier holds application-wide, purposeful, particular states along with requests the information from external sources like API.

Its purpose is deriving/containing a state and serving it throughout the application. Having said that, informations in the data tier must be general enough but contextual to serve all ends of application at any time. This constrain disallow you to put specific data in it. (This part will be explained in detail Model section)

Another function of data tier is encapsulating the persistent logic of your app. (This part will be explained in detail Repositories section)

Data tier can be dependant to our tooling and tech stack.

It is the location of which Single Responsibility feature

Data tier seeks Single Responsibility on its models.

Model

Model is the place where application states are created for a specific purpose. It uses domain entities and use-cases in order to create or derive the purposeful state for an application.

Being contextual is important for the Model. It should deal with all the aspects of a specific topic in the application(3). Mixture of different concerns is discouraged.

Models are units where we're committing Single Responsibility characteristic of the Architecture. So to say, if your Model is spanning responsibilities of more than one context than you are at risk that ending up fat, God Model that contain everything. It's clear violation of single responsibility, besides that makes getting harder to scale for future.

Best practice is keeping your concerns in a different models and allow models to negotiate each other. That communication can be happen via Events or you can leverage any tooling capability to achieve it.

A Model can only and should communicate another Models or ViewModels. Otherwise Model's consistency would compromised by unintended behaviour.

Global Model

Let's discuss the necessity of the global state for the application. In fact it's necessary most of the time for an Frontend Application. But the architecture we discuss has mechanism to eliminate that need:

  • Models can communicate each other via their Event Channel
  • Single Responsibility concern forces us create multiple models for different contexts.

Global Model is in this architecture is nothing more than "yet another" model. It holds its own concerns that cannot fit other Models and it can share its common data with other models. Given this example this Global model is not different from other models.

Possible replacement with State Managers

Suggested mechanism for Models is using event channels sending/receiving data from and to Models and ViewModels. Even though using events are convenient, one would like to use a state manager library and deals the creating and deriving state job by using it.

Using external state manager is still subject to fulfil architecture's expectations and follow its instructions for integrity. Having said that, State manager in the Model must not accessed by Views but ViewModels, State manager must configured to contain contextual and purposeful data, not be used as Global Object.

This is usually a cumbersome task. These state managers are designed to work directly inside the components. That's most likely cause collision. In this architecture, We're abstracted all the application data into ViewModels. That means you will end up using state manager only in the ViewModel but keeping away directly from View (component).

This abstraction may seem unnecessary effort at first sight, but in the long run, it will be a win. Consider yourself refactoring a class components to a function components in react.js. Imagine how hard it could be replacing entire content including component logic, state data and other intertwined parts of the components. If you followed the architecture rules and keeping every data related thing inside the ViewModel, you were only worried the Component itself and all others are already dealt inside the ViewModel.

Another challenging task would be using multiple stores for different contexts and communicate them each other. Architecture we discuss here suggests to using events for this need. When you go for a tech to replace models with 3rd party state manager, it will now up to you how to establish this communication mechanism between models. From then on architecture only enforces you to follow its rules.

Repository

Repositories are persistent logic containers. Since we're developing this architecture for Frontend Apps, persistence means API usage and Browser storage usage.

Repositories are used by Use Cases and they're instruments for the completing a use case duties.

Another important feature is they're injected in use cases in runtime. A use case will not have hard dependency a repository. Model is the place where you should inject the repository in a use case

App Tier

(to be written)

View Model

(to be written)

View

(to be written)

Structure

Clean + MVVM Architecture is not dictating Layered, Union architectures like Clean Architecture does. All tiers (Domain - Data - Application) are in same, horizontal level.

Domain holds and defines business rules and logics via entities and use-cases. Data tier consumes the Domain tier and creates application states for specific responsibilities (mind the plural states here. Architecture tries to avoid putting everything into a one, global state). Data tier is also encapsulates all the persistence logic with Repositories. App tier in which consists of ViewModels and Views interacts with Data tier and presents the application state to the end user. App tier is able to mutate the application state by user interactions.

Data flow

MVVM uses a half-duplex and sequential data flow, in the form of events which move between respective channels. The Data and App layers therefore need to be capable of sending events to one another. The following data flow constraints apply:

  • Views can only send or receive events from their ViewModels.
  • ViewModels can send or receive events from at least one or multiple Models.
  • ViewModels should not share data each other. Even though it's possible through events, it's an obvious violation of data flow. That causes traceability issues of information.
  • Models shares their data each other. They do that their respective event channels.

EventBus

EventBus is a general term that references the event channels between units. Views communicate their ViewModels via events, therefore they using EventBus. Same mechanism goes for ViewModel - Model communication as well as Model - Model communication. Please consult the PoC here (https://github.com/crate/crate-solutions-ui-mvvm-poc) to see how EventBus is working in action.

Pod

Finally, MVVM groups contextual elements together as pods. Pod elements are closely thematically related, and using them can make it easy to manage files and folder structure during development(2).

Generally pods are forming of multiple Views and ViewModels and they can be extended to including their own Model. It's very depend on the occasion and it should be beneficial if you group them together.


Resources

(1) https://medium.com/google-developers/javascript-application-architecture-on-the-road-to-2015-d8125811101b

(2) http://vieux.io/

(3) https://github.com/bailabs/react_js_clean_architecture

(4) https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html

(5) https://yuriktech.com/2019/06/11/Implementing-Clean-Architecture/

(6) https://proandroiddev.com/why-you-need-use-cases-interactors-142e8a6fe576

(7) https://phodal.github.io/clean-frontend/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment