MCPcopy Index your code
hub / github.com/davidroberto/react-redux-clean-architecture-tdd-eda-vertical-slices

github.com/davidroberto/react-redux-clean-architecture-tdd-eda-vertical-slices @main

Chat with this repo
repository ↗ · DeepWiki ↗ · + Follow
101 symbols 299 edges 73 files 0 documented · 0%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

The purpose of this repository is to build a front end application with react, using the Clean Architecture, Event Driven Architecture, TDD and vertical slices principles.

This architecture have been showcased in the "CTO InShape" podcast during a pair programming session with Mathieu Kahlaoui! You can watch the podcast here: CTO In Shape - Clean Architecture with Redux.

Table of Contents

  1. Introduction
  2. Issues with React and "classic" state management
  3. Stack
  4. Installation
  5. Clean architecture in front-end
  6. Redux
  7. Dev methodology with TDD
  8. Execution flow
  9. DDD ?
  10. Vertical slices
  11. Useful Ressources

1. Introduction

This project is exploratory and aims to experiment with different ways to structure a front end application and use of TDD. It's my way of doing it and it can surely be better, so feel free to contribute, provide feedback or start PR if you have any ideas, suggestions, or believe something can be improved!

The React components and React Native screens are not the main focus of this project and have been kept simple. So you may still encounter some TODO items or TypeScript warnings / errors (even some "any", oops) in the UI layer. Also there is not test for react component.

My issues with react without clean architecture and with "classic" react state management:

  • Feeling like I'm hacking things together to manage my components' state
  • Struggling to decouple enough from the UI
  • Creating/moving hooks and "services" here and there
  • Difficulty to reason about the state and the transitions between states and get the big picture
  • Overuse of hooks
  • Having part of the business logic in the UI layer
  • Failing to do TDD or test properly (too much coupling, too fragile, little logic to test...) and struggling to give value to those tests
  • Switching from one state management library to another
  • Trying to implement principles that don't fit well with React’s flow, ending up with overengineered solutions
  • Having to reload the page to see state changes and replay scenarios
  • Needing a backend to test scenarios and polluting the database with every manual test

Stack

JS TS React Redux React Native Jest
Logo JS Logo JS Logo JS Logo JS Logo JS Logo JS

Installation

  1. Fork this repository
  2. Clone your forked repository
  3. Install dependencies
  npm install
  1. Run the app with expo
  npm run start
  1. Run the unit tests
  npm run test

Clean architecture in frontend:

Clean architecture is often associated with back-end development. Using it on the front end is sometimes seen as overkill, because for a lot of people the frontend is easy. Maybe it was when SSR was the norm. But in a lot of projects using a separate frontend Single Page App (react, View, Angular etc), it's not the case anymore.

And implementing Clean Architecture is not necessary complex, and it can be very, very useful, event in frontend apps.

The main idea here is to separate the React component from the business logic and the data access. This way, we can develop and test the business logic without needing to open the browser or having to wait for a backend.

Imagine this user story: “As a user, I want to create an exercise.”

The business logic is to create an exercise. The data access involves sending a fetch request to create an exercises. The React component is responsible for displaying an form and allowing the user to create a new exercise.

So we can already separate the code into three parts:

  • The React component (UI)
  • The business logic (use case)
  • The data access (repository)

We can create a class or function called CreateExerciseUseCase that validates the data sent by the user and creates the exercise by calling the repository. The repository contains the fetch function to actually create the exercise in the backend.

In practice, the UI calls the use case, and the use case calls the repository. Because the use case is coupled to the repository, it cannot be easily tested without sending a request to the backend.

To solve this, we can apply the Dependency Inversion Principle (DIP) and inject the repository into the use case via an interface (here). This way, we can mock the repository: either for developping the frontend without having a backend yet with a in-memory implementation (useful for rapid Proof of Value), or in tests with fakes, allowing us to test the use case without actually making real requests.

Annnnd that’s it. We have a Clean Architecture approach in the frontend as the dependencies go inward.

clean-archi.png (image from Milan Jovanović blog)

Please note that the layers here are not defined with traditional folders like application, domain, infrastructure, etc., but Clean Architecture principles are still respected. A vertical slices approach is used instead to gather nearly all the code related to a feature in one place (here) and it's not contradictory to the Clean Architecture. As Robert C. Martin says: “The architecture should scream the use cases of the application.” (Robert C. Martin).

But as the state was managed by React, it's still difficult to test the state changes and the transitions between states. That's when Redux comes in.

What about Redux?

To store data in our application (e.g., the logged-in user, a list of exercises, etc.), we need state. To share this state between components, we can use the Context API or a state management library such as Zustand, Recoil, or Redux. Redux has a reputation for having a lot of boilerplate and is sometimes considered overkill.

So why choose Redux? Because Redux is not just a state manager. In this app, we use Redux and RTK for four main purposes:

1) State Management: to store the global application state. 2) (Kind of) Pub/Sub System / Synchronous event bus: In Redux we dispatch actions. This actions are "listened" by reducers in order to update the state accordingly. This part of Redux can be seen as a synchronous event bus. So in this repository, the actions dispatched by the use case are named "event" (here), because they feel more like it (in a event driven architecture style).

redux-message-bus.png image from Yazan Alaboudi Redux talk

3) Dependency Injection: By using the extraArgument option in the Redux store creation (using RTK configure store, here), we can inject the repository (for data fetching, etc.) or any other infrastucture dependency into the use case (here). 4) Middleware for Side Effects: Redux Thunk handles side effects such as API calls. Therefore, our use cases are implemented as Redux thunks, providing more granular control over how Redux actions (events) are dispatched related to the side effects (here).

react-redux-clean-archi.png

It's important to notice that Redux lives in our domain / application layers. It's not considered as an infrastructure tool, but as a core part of our application. It's a pragmatic choice for reducing the complexity.

I feel like Redux perfectly fills the missing holes with Clean Architecture with React. The event driven architecture which is enabled by Redux allows us to manage the state in a predictable way. And to think about state and transition without React in mind. That way, we can focus on the business logic and the state transitions, and test them without needing to open the browser. We can also modelize the state transitions with a state machine diagram, which is a great way to visualize the application flow, and using TDD to develop the use cases and state changes. When done, we just need to plug in the React component.

So React is used only for what it was designed for: the UI.

Please note that: - If other events related to another feature need to be dispatched after an event (e.g., creating an exercise triggers a new fetch of exercises), they are dispatched within the same use case. Because a use case does not calls another use case. And React component should not manage the application flow: it's not his responsibility call a use case after one is done. - The state in this project is not normalized (using Normalizer for exemple) and the ui state is not separated from the "entity" state in the store (but it can be if the relational / nested data become is too complex) - The selectors in this project are not created using createSelector (Reselect) because the data retrieved from the store is not derived or transformed

My Dev methodology using TDD:

  1. Definition of the user story and scenarios for the feature. Example:
  2. As a user, I want to create an exercise
  3. Given no exercise is already created
  4. When the exercise creation starts
  5. Then the loading should be true
  6. Creation of a state machine diagram to visualize state transitions (directly in webstorm using Mermaid):
---
title: Create Exercice State
---

flowchart TD
  A[
        Idle

Status: idle
Error: null

Notifications: n

List Exercices Data: n
]

B[
Loading

Status: loading
Error: null

Notifications: n

List Exercices Data: n
]

C[
Error

Status: error
Error: error message

Notification: n + 1 error

List Exercices Data: n
]

D[
Success

Status: success
Error: null

Notification: n + 1 success
]


E[
List exercices Success

...
Data: n + created exercice
]

subgraph Create Exercice
A -->|Exercice creation Started|B
B -->|Exercice creation failed|C
B -->|Exercice Created|D
end

subgraph List exercices
D -->|...|E
end

  1. Writing the first acceptance test based the scenario. (here): red
  2. Implementation of the use case using baby steps (green)
  3. Refactoring the code (refactor)
  4. Writing a second acceptance test

The unit tests here are usually socials (and can serve as specifications) in order to avoid fragile tests because of "behavior sensitivity" : test should change if the scenario change, not if the inner code change (check the awesome Ian Cooper's "TDD Revisited" talk).

In this repo, the use case is the starting point and it asserts against the current state with selectors.

Execution flow:

```mermaid

title: clean archi + redux flow

sequenceDiagram autonumber

box rgb(211,69,92) UI
    participant REACT COMPONENT
end

box rgb(32,120,103) APPLICATION
    participant USE CASE (THUNK)
    participant EVENT
    p

Extension points exported contracts — how you extend this code

ExerciceRepositoryInterface (Interface)
(no doc) [8 implementers]
src/exercice/features/shared/exercice.repository.interface.ts
MuscleRepositoryInterface (Interface)
(no doc) [6 implementers]
src/muscle/features/shared/muscle.repository.interface.ts

Core symbols most depended-on inside this repo

getExercicesListData
called by 28
src/exercice/features/list-exercices/list-exercices.state.model.ts
createTestStoreWithExercices
called by 11
src/exercice/features/shared/test/utils/create-test-store-with-exercices.ts
createTestStore
called by 10
src/shared/application/test/test.store.ts
create
called by 9
src/exercice/features/shared/exercice.repository.interface.ts
getNotificationsList
called by 9
src/notification/features/shared/notification.state.model.ts
generateNotification
called by 9
src/notification/features/add-notification/notification-generator.service.ts
findAll
called by 8
src/exercice/features/shared/exercice.repository.interface.ts
getExerciceCreateStatus
called by 5
src/exercice/features/create-exercice/create-exercice.state.model.ts

Shape

Function 54
Method 28
Class 10
Enum 7
Interface 2

Languages

TypeScript100%

Modules by API surface

src/exercice/features/shared/infrastructure/exercice.repository.in-memory.ts8 symbols
src/exercice/features/shared/test/exercice-success.repository.fake.ts7 symbols
src/exercice/features/shared/test/exercice-loading.repository.fake.ts7 symbols
src/exercice/features/shared/test/exercice-error.repository.fake.ts7 symbols
src/exercice/features/shared/exercice.repository.interface.ts6 symbols
src/exercice/features/list-exercices/list-exercices.state.model.ts4 symbols
src/exercice/features/get-exercice-by-id/get-exercice-by-id.state.model.ts4 symbols
src/muscle/shared/infrastructure/muscle.repository.in-memory.ts3 symbols
src/exercice/ui/update-exercice/update-exercice.view-model.ts3 symbols
src/exercice/ui/list-all-exercices/display-exercice/exercice-actions/exercice-actions.view-model.ts3 symbols
src/exercice/ui/create-exercice/create-exercice.view-model.ts3 symbols
src/exercice/features/update-exercice/update-exercice.state.model.ts3 symbols

For agents

$ claude mcp add react-redux-clean-architecture-tdd-eda-vertical-slices \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page