Skip to content

React Layered Architecture (Martin Fowler Inspired)

This document describes the layered architecture for our React application following Martin Fowler’s principles. The architecture separates UI, Business Logic, and Data/API layers for better scalability and maintainability.

The evolution of a React application 1: https://martinfowler.com/articles/modularizing-react-apps.html

The evolution of a React application 5: https://martinfowler.com/articles/modularizing-react-apps.html


1. Architecture Overview

UI Layer (React Components / Pages)
β”‚
β–Ό
Business Logic Layer (Domain Logic / State / Hooks)
β”‚
β–Ό
Data/API Layer (API Requests / Database Access / Supabase)

Key Points:

  • UI Layer: Responsible for rendering components and user interactions.
  • Business Logic Layer: Handles domain logic, state management, and pure functions.
  • Data/API Layer: Handles all communication with backend APIs or databases. Backend routes are the source of truth.

2. Folder Structure

src/
β”œβ”€β”€ ui/ # UI Layer
β”‚ β”œβ”€β”€ pages/
β”‚ β”‚ β”œβ”€β”€ Dashboard.jsx
β”‚ β”‚ β”œβ”€β”€ Transactions.jsx
β”‚ β”‚ └── TransactionDetail.jsx
β”‚ β”œβ”€β”€ components/
β”‚ β”‚ β”œβ”€β”€ TransactionCard.jsx
β”‚ β”‚ └── CategorySelect.jsx
β”‚ └── hooks/
β”‚ └── useFormValidation.js
β”‚
β”œβ”€β”€ business-logic/ # Business Logic Layer
β”‚ β”œβ”€β”€ transactions/
β”‚ β”‚ β”œβ”€β”€ useTransactions.js
β”‚ β”‚ β”œβ”€β”€ transactionHelpers.js
β”‚ β”‚ └── transactionSelectors.js
β”‚ β”œβ”€β”€ user/
β”‚ β”‚ └── useUser.js
β”‚ └── store/
β”‚ └── index.js
β”‚
β”œβ”€β”€ data/ # Data/API Layer
β”‚ β”œβ”€β”€ api/
β”‚ β”‚ β”œβ”€β”€ apiRoutes.js
β”‚ β”‚ β”œβ”€β”€ transactionService.js
β”‚ β”‚ └── userService.js
β”‚ β”œβ”€β”€ supabase/
β”‚ β”‚ └── client.js
β”‚ └── models/
β”‚ └── transactionModel.js
β”‚
└── App.jsx # React Router / Root Component

3. Layer Details and File Naming Conventions

Layer File / Folder Example Purpose
UI Layer pages/Transactions.jsx
components/TransactionCard.jsx
Renders pages and reusable UI components
Business Logic Layer business-logic/transactions/useTransactions.js
transactionHelpers.js
Domain logic, state hooks, pure functions
Data/API Layer data/api/transactionService.js
data/api/apiRoutes.js
Backend API communication, Supabase client

File Naming Guidelines:

  • Hooks: use[Feature].js β†’ useTransactions.js
  • Helpers / Pure functions: [feature]Helpers.js β†’ transactionHelpers.js
  • API Services: [feature]Service.js β†’ transactionService.js
  • Constants / API routes: apiRoutes.js

4. Data Flow

  1. UI Layer calls business logic hooks or functions.
  2. Business Logic Layer uses API services or state management to fetch/process data.
  3. Data/API Layer communicates with backend (.NET/Supabase).
  4. Data flows back through the business logic to UI for rendering.

5. Best Practices

  • Keep UI components stateless whenever possible.
  • Keep business logic separate from UI.
  • Use constants for backend API routes to prevent hardcoding.
  • Consider using OpenAPI / Swagger codegen to auto-generate API clients.
  • Follow Martin Fowler principles: layers, separation of concerns, single source of truth for data.

6. References


Additional

🧩 Guiding Principle

  • βœ… UI components should be as dumb (stateless) as possible.
  • 🧠 Logic hooks handle state and side effects.

That means:

  • ForexForm.tsx β†’ mainly layout + input bindings
  • useForexFormLogic.js β†’ owns all the form state, including conditional logic
  • Sub-logic hooks (like useForexAmountLogic) can hold their own mini state only if they are isolated and reusable

πŸ”‘ Rule of Thumb

Layer Can have useState? Purpose
UI (ForexForm.tsx) ⚠️ Rarely Only for temporary UI states (like modal open/close)
Orchestrator (useForexFormLogic) βœ… Yes Central place for form + conditional logic
Sub-logic hooks βœ… Optional If logic is isolated and reusable (e.g., amount, account)
Service / API ❌ No Pure functions, no React state

Micro-Orchestrator architecture

Now we’re moving into Micro-Orchestrator architecture β€” the next step after modularizing by layers.

This pattern lets you scale feature logic without spaghetti useEffects in one massive file, while keeping clean data flow between logic hooks.

Let’s go deep step-by-step πŸ‘‡

src/features/forex/
β”‚
β”œβ”€β”€ components/
β”‚   β”œβ”€β”€ ForexForm.tsx
β”‚   β”œβ”€β”€ AmountRateFields.tsx
β”‚   β”œβ”€β”€ AccountFields.tsx
β”‚   β”œβ”€β”€ ApprovalFields.tsx
β”‚   └── ...
β”‚
β”œβ”€β”€ hooks/
β”‚   β”œβ”€β”€ useForexFormLogic.ts          ← main orchestrator
β”‚   β”œβ”€β”€ useForexAmountLogic.ts        ← micro orchestrator #1
β”‚   β”œβ”€β”€ useForexAccountLogic.ts       ← micro orchestrator #2
β”‚   β”œβ”€β”€ useForexApprovalLogic.ts      ← micro orchestrator #3
β”‚   β”œβ”€β”€ useForexCounterpartyLogic.ts  ← micro orchestrator #4
β”‚   └── ...
β”‚
└── services/
    β”œβ”€β”€ forexApi.ts
    └── forexValidation.ts

Now we’ll structure them so each hook is isolated, but still coordinated by one master logic file: useForexFormLogic.js.


🧠 Core Idea

We’ll use a hub-and-spoke model:

  • Each β€œspoke” (useForexAmountLogic, useForexAccountLogic, etc.) handles one concern.

  • The β€œhub” (useForexFormLogic) connects them together and shares shared state (e.g. form instance, values, RTK queries).

This avoids cross-hook dependencies β€” no hook should import another hook.

Instead, all communication happens through the orchestrator.


Layer Contains Example Files useEffect?
UI Layer JSX + Form rendering + layout ForexForm.tsx, AmountRateFields.tsx ❌ None (only controlled rendering)
Logic Layer useEffect, watchers, orchestration, field syncing useForexFormLogic.ts, useForexAmountLogic.ts βœ… Yes
Data Layer RTK Query, API, validation forexApi.ts, forexValidation.ts ❌ None

🧭 Data Flow Visualization

 ForexForm (UI)
      ↓
 useForexFormLogic (main orchestrator)
      ↓
 β”œβ”€β”€ useForexAmountLogic      β†’ calculates totals
 β”œβ”€β”€ useForexAccountLogic     β†’ fetches accounts
 └── useForexApprovalLogic    β†’ toggles approval fields
      ↓
 forexApi (RTK Query)

🧭 What Is an β€œOrchestrator”

Think of an orchestrator as a central controller that coordinates different feature modules (API calls, UI updates, routing, and side effects).

It doesn’t render UI directly, but it decides what happens and when.

You can imagine your app layers like this:

UI (React Components)
⬆
Orchestrator (Application Logic / Coordinator)
⬆
Services (API / Supabase / Data Access)
⬆
Domain Models (Entities / Business Rules)