diff --git a/docs/issue-42-[section]-scalable-application.md b/docs/issue-42-[section]-scalable-application.md new file mode 100644 index 00000000..8b47abbb --- /dev/null +++ b/docs/issue-42-[section]-scalable-application.md @@ -0,0 +1,199 @@ +# Scalable Application Structure + +This section outlines a strategy for organizing a React + Redux + TypeScript application to ensure long-term maintainability, scalability, and reusability. The primary goal is to move away from grouping files by type (e.g., all actions in one folder, all reducers in another) and instead group them by **feature**. + +## Core Goals + +1. **Feature-Based Grouping**: All logic related to a specific domain (e.g., `User`, `Product`, `Checkout`) lives together. +2. **Easy Addition/Removal**: Features can be added or removed by simply adding or deleting a folder, with minimal impact on the rest of the codebase. +3. **On-Demand Enable/Disable**: Features can be conditionally loaded or disabled via configuration without breaking the application. +4. **Reusability & Pluggability**: Features are self-contained modules that can be dropped into different Redux applications with minimal wiring. + +## Recommended Directory Structure + +The following structure demonstrates a scalable architecture where features are isolated. + +```text +src/ +├── app/ # App-level configuration, entry point, global styles +│ ├── index.tsx +│ ├── store.ts # Root store configuration +│ └── App.tsx +├── features/ # Feature modules (The core of scalability) +│ ├── auth/ # Feature: Authentication +│ │ ├── components/ # UI components specific to Auth +│ │ ├── hooks/ # Custom hooks for Auth logic +│ │ ├── slices/ # Redux Slice (Actions, Reducer, Selectors) +│ │ │ └── authSlice.ts +│ │ ├── services/ # API calls specific to Auth +│ │ └── index.ts # Public API (exports only what is needed) +│ ├── products/ # Feature: Product Management +│ │ ├── components/ +│ │ ├── hooks/ +│ │ ├── slices/ +│ │ │ └── productsSlice.ts +│ │ ├── services/ +│ │ └── index.ts +│ └── cart/ # Feature: Shopping Cart +│ ├── components/ +│ ├── hooks/ +│ ├── slices/ +│ │ └── cartSlice.ts +│ ├── services/ +│ └── index.ts +├── shared/ # Shared resources used across multiple features +│ ├── components/ # Generic UI components (Button, Modal, Input) +│ ├── hooks/ # Generic hooks (useDebounce, useLocalStorage) +│ ├── types/ # Shared TypeScript interfaces +│ └── utils/ # Utility functions +└── config/ # Environment variables and feature flags + ├── index.ts + └── featureFlags.ts +``` + +## Implementation Details + +### 1. Feature Isolation via Slices + +Each feature should define its own Redux Slice using `@reduxjs/toolkit`. This encapsulates state, actions, and reducers within the feature folder. + +**Example: `src/features/auth/slices/authSlice.ts`** + +```typescript +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + +interface AuthState { + user: { id: string; name: string } | null; + isAuthenticated: boolean; + isLoading: boolean; +} + +const initialState: AuthState = { + user: null, + isAuthenticated: false, + isLoading: false, +}; + +const authSlice = createSlice({ + name: 'auth', + initialState, + reducers: { + loginStart: (state) => { + state.isLoading = true; + }, + loginSuccess: (state, action: PayloadAction<{ id: string; name: string }>) => { + state.isLoading = false; + state.isAuthenticated = true; + state.user = action.payload; + }, + loginFailure: (state) => { + state.isLoading = false; + state.isAuthenticated = false; + }, + logout: (state) => { + state.user = null; + state.isAuthenticated = false; + }, + }, +}); + +export const { loginStart, loginSuccess, loginFailure, logout } = authSlice.actions; +export default authSlice.reducer; +``` + +### 2. The Public API Pattern (`index.ts`) + +To ensure features are pluggable and reusable, each feature folder should export a clean public API via its `index.ts` file. This prevents other parts of the app from importing internal implementation details (like specific component files or private utilities). + +**Example: `src/features/auth/index.ts`** + +```typescript +// Export the reducer for the store configuration +export { default as authReducer } from './slices/authSlice'; + +// Export actions +export { loginStart, loginSuccess, loginFailure, logout } from './slices/authSlice'; + +// Export selectors (if defined in the slice or a separate file) +export { selectUser, selectIsAuthenticated } from './slices/authSlice'; + +// Export components (only those meant for public use) +export { LoginForm } from './components/LoginForm'; +export { ProtectedRoute } from './components/ProtectedRoute'; + +// Export hooks +export { useAuth } from './hooks/useAuth'; +``` + +### 3. Dynamic Store Configuration + +To enable **on-demand** feature loading, the root store should be configured to accept a map of reducers. This allows you to conditionally include features based on environment variables or feature flags. + +**Example: `src/app/store.ts`** + +```typescript +import { configureStore } from '@reduxjs/toolkit'; +import { featureFlags } from '../config/featureFlags'; + +// Import reducers dynamically +import authReducer from '../features/auth'; +import productsReducer from '../features/products'; +import cartReducer from '../features/cart'; + +// Map of available reducers +const allReducers = { + auth: authReducer, + products: productsReducer, + cart: cartReducer, +}; + +// Filter reducers based on feature flags +const enabledReducers = Object.keys(allReducers).reduce((acc, key) => { + const featureKey = key as keyof typeof allReducers; + if (featureFlags[featureKey]) { + acc[featureKey] = allReducers[featureKey]; + } + return acc; +}, {} as Record); + +export const store = configureStore({ + reducer: enabledReducers, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware({ + serializableCheck: false, // Adjust as needed for your async logic + }), +}); + +export type RootState = ReturnType; +export type AppDispatch = typeof store.dispatch; +``` + +### 4. Feature Flags Configuration + +Centralize your feature toggles to easily enable or disable entire modules without code changes in the logic itself. + +**Example: `src/config/featureFlags.ts`** + +```typescript +// In a real app, these might come from an API or environment variables +export const featureFlags = { + auth: true, + products: true, + cart: process.env.REACT_APP_ENABLE_CART === 'true', // Example of env-based toggle +}; +``` + +## Benefits of This Structure + +* **Scalability**: As the application grows, you simply add new folders to `features/`. The existing code remains untouched. +* **Maintainability**: When a bug occurs in the "Cart" feature, you only need to look in `src/features/cart`. You don't need to search through a massive `actions/` or `reducers/` folder. +* **Reusability**: Because features export a clean API and encapsulate their dependencies, the `auth` feature can be copied into a different project and wired up with minimal effort. +* **Performance**: By conditionally loading reducers (and potentially code-splitting components), you can reduce the initial bundle size for users who don't need certain features. + +## Summary Checklist + +- [ ] Are all feature-specific files (components, hooks, slices) grouped in a single feature folder? +- [ ] Does each feature have an `index.ts` that acts as a public API? +- [ ] Are shared utilities and generic components kept in a separate `shared/` folder? +- [ ] Is the root store configured to dynamically include/exclude reducers? +- [ ] Can a feature be disabled via a configuration flag without breaking the app? \ No newline at end of file