# ADR-005: Feature Module Pattern

**Date**: 2025-01-15
**Status**: Accepted

## Context

The Portal (Next.js frontend) needs a scalable organization pattern. Common approaches:

- **File-type grouping**: All components in `/components`, all hooks in `/hooks`
- **Feature grouping**: All billing code in `/features/billing`

## Decision

Organize Portal code by **feature modules** with consistent internal structure:

```
apps/portal/src/features/billing/
├── api/              # Data fetching (billing.api.ts)
├── hooks/            # React Query hooks (useBilling.ts)
├── stores/           # Zustand state (if needed)
├── components/       # Feature UI (InvoiceList.tsx)
├── views/            # Page-level views (InvoicesList.tsx)
└── index.ts          # Barrel exports
```

Pages in `app/` are thin wrappers that import views from features.

## Rationale

### Why Feature Modules?

1. **Cohesion**: All billing-related code is in one place. Need to modify billing? Look in `features/billing/`.

2. **Scalability**: Adding a new feature = adding a new folder. No need to touch multiple scattered directories.

3. **Clear ownership**: Easy to understand what a feature encompasses.

4. **Encapsulation**: Features export a public API via `index.ts`. Internal implementation details are hidden.

### Why Thin Pages?

```typescript
// ✅ GOOD: Page is a thin wrapper
// app/account/billing/invoices/page.tsx
import { InvoicesListView } from "@/features/billing/views";

export default function InvoicesPage() {
  return <InvoicesListView />;
}

// ❌ BAD: Page contains logic
export default function InvoicesPage() {
  const invoices = useInvoices();  // Data fetching in page
  return <InvoiceTable data={invoices} />;  // Logic in page
}
```

Benefits of thin pages:

- Pages are declarative route definitions
- Business logic is testable in isolation
- Views can be reused across routes if needed

### Alternatives Considered

| Approach               | Pros                    | Cons                                     |
| ---------------------- | ----------------------- | ---------------------------------------- |
| **File-type grouping** | Familiar, simple        | Poor cohesion, hard to find related code |
| **Domain-driven**      | Clean boundaries        | Overkill for frontend                    |
| **Feature modules**    | High cohesion, scalable | Requires discipline                      |

## Consequences

### Positive

- High cohesion: all related code together
- Easy to add new features
- Clear public API per feature
- Pages remain declarative

### Negative

- Slightly more initial setup per feature
- Requires consistent discipline across team

## Implementation

### Feature Module Structure

```
features/[feature-name]/
├── api/                 # Data fetching layer
│   ├── [feature].api.ts # API service functions
│   └── index.ts         # Re-export
├── hooks/               # React Query hooks
│   ├── use[Feature].ts  # Primary hook
│   └── index.ts
├── stores/              # Zustand stores (if needed)
│   └── [feature].store.ts
├── components/          # Feature-specific UI
│   ├── [Component].tsx
│   └── index.ts
├── views/               # Page-level views
│   └── [Feature]View.tsx
├── utils/               # Feature utilities
└── index.ts             # Public API (barrel export)
```

### Example: Billing Feature

```typescript
// features/billing/api/billing.api.ts
export const billingService = {
  getInvoices: async (params) => apiClient.GET("/api/invoices", { params }),
  getInvoice: async (id) => apiClient.GET(`/api/invoices/${id}`),
};

// features/billing/hooks/useBilling.ts
export function useInvoices(params?: InvoiceQueryParams) {
  return useQuery({
    queryKey: queryKeys.billing.invoices(params),
    queryFn: () => billingService.getInvoices(params),
  });
}

// features/billing/views/InvoicesList.tsx
export function InvoicesListView() {
  const { data: invoices } = useInvoices();
  return <InvoiceTable invoices={invoices} />;
}

// features/billing/index.ts (public API)
export { billingService } from "./api";
export { useInvoices, useInvoice } from "./hooks";
export { InvoicesListView } from "./views";
```

### Page Usage

```typescript
// app/account/billing/invoices/page.tsx
import { InvoicesListView } from "@/features/billing";

export default function InvoicesPage() {
  return <InvoicesListView />;
}
```

## Related

- [Portal Architecture](../development/portal/architecture.md)
- [ADR-006: Thin Controllers](./006-thin-controllers.md)
