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.


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
- UI Layer calls business logic hooks or functions.
- Business Logic Layer uses API services or state management to fetch/process data.
- Data/API Layer communicates with backend (.NET/Supabase).
- 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
- Martin Fowler: Layered Architecture
-
Martin Fowler: Modularizing React Applications with Established UI Patterns
- Supabase Docs
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 bindingsuseForexFormLogic.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: