docs(adr): add ADRs 006-009 for vertical refactor
This commit is contained in:
27
docs/decisions/adr-006-vertical-feature-packages.md
Normal file
27
docs/decisions/adr-006-vertical-feature-packages.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# ADR-006: Vertical Feature Packages
|
||||
|
||||
**Status:** Accepted
|
||||
**Date:** 2026-05-04
|
||||
|
||||
## Context
|
||||
|
||||
The original template organized packages by architectural layer: `core` (all domains together), `api` (all routers), `ui` (all components). As features grow, shared code accumulates and cross-feature dependencies become implicit.
|
||||
|
||||
## Decision
|
||||
|
||||
Reorganize by business capability. Each feature owns a vertical slice from entities through UI. `core-*` packages host only non-business concerns (DI, shared types, UI primitives).
|
||||
|
||||
**Result:** 5 feature packages (`auth`, `blog`, `media`, `marketing-pages`, `navigation`) + 5 core packages (`core-shared`, `core-cms`, `core-api`, `core-trpc`, `core-ui`).
|
||||
|
||||
## Consequences
|
||||
|
||||
- Features evolve independently without coordinating with shared code
|
||||
- Cross-feature coupling is visible at the package-graph level (ESLint enforces it)
|
||||
- Per-feature DI containers eliminate symbol collisions
|
||||
- New team members can understand a feature completely by reading one package
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
- Horizontal layers (kept): Simpler initially, but scales to implicit hidden dependencies
|
||||
- Monolithic single package: No modularity, impossible to reason about at scale
|
||||
- Feature shells + shared core: Hybrid approach tried by many teams; creates "dump" in core that nobody owns
|
||||
36
docs/decisions/adr-007-drop-cms-client-wrapper.md
Normal file
36
docs/decisions/adr-007-drop-cms-client-wrapper.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# ADR-007: Drop the Dual-Mode CMS Client Wrapper
|
||||
|
||||
**Status:** Accepted
|
||||
**Date:** 2026-05-04
|
||||
|
||||
## Context
|
||||
|
||||
ADR-004 introduced `@repo/cms-client` as a standalone wrapper supporting both Local API and HTTP modes. It was never used in production after Payload 3.x solidified its Local API as the primary pattern.
|
||||
|
||||
## Decision
|
||||
|
||||
Delete the wrapper. Feature payload-backed repositories call `getPayload({ config })` directly. The `config` is injected via constructor, avoiding hard coupling to `@repo/cms-core` at import time.
|
||||
|
||||
**Example:**
|
||||
```typescript
|
||||
export class PayloadArticlesRepository implements IArticlesRepository {
|
||||
constructor(private config: Config) {}
|
||||
|
||||
async getById(id: string) {
|
||||
const payload = await getPayload({ config: this.config });
|
||||
return payload.findByID({ collection: "articles", id });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Consequences
|
||||
|
||||
- One fewer abstraction layer — repositories work directly with Payload's typed API
|
||||
- No "modes" — always use Local API from server code
|
||||
- Package graph stays acyclic: feature packages never import `@repo/cms-client`
|
||||
- `apps/cms` can directly boot Payload with the assembled config
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
- Keep the wrapper for "future-proofing": Payload 3.x is stable; wrapper was never activated in practice
|
||||
- Add HTTP mode later if needed: Simple to implement when actually required
|
||||
36
docs/decisions/adr-008-per-feature-di-containers.md
Normal file
36
docs/decisions/adr-008-per-feature-di-containers.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# ADR-008: Per-Feature InversifyJS Containers
|
||||
|
||||
**Status:** Accepted
|
||||
**Date:** 2026-05-04
|
||||
|
||||
## Context
|
||||
|
||||
The original template used a single shared InversifyJS Container in `packages/core/src/di/`. As the number of features grows, the container becomes a point of coordination: adding a symbol requires modifying shared code, and tests that mock one feature risk breaking others.
|
||||
|
||||
## Decision
|
||||
|
||||
Each feature owns its own `Container` and symbol table. No shared DI state. Tests rebind per feature in isolation. Apps boot by calling `bindProduction*()` for each feature independently.
|
||||
|
||||
**Example:**
|
||||
```typescript
|
||||
// packages/blog/src/di/container.ts
|
||||
export const container = createContainer();
|
||||
|
||||
// packages/blog/tests/feature.test.ts
|
||||
beforeEach(() => {
|
||||
rebindRepository(new TestRepository());
|
||||
// Only blog's container is affected
|
||||
});
|
||||
```
|
||||
|
||||
## Consequences
|
||||
|
||||
- Zero cross-feature DI coupling — each feature's test can mock its repos without coordination
|
||||
- Symbol collisions impossible — each feature has its own `ARTICLES_REPOSITORY` symbol
|
||||
- Shared services (if needed) are explicitly bound in each feature that uses them
|
||||
- Apps must boot each feature's container on startup
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
- Single shared container: Simpler upfront, becomes a bottleneck and test coordination point
|
||||
- Function injection (no DI): Avoids framework overhead, but loses the scaling benefits of DI
|
||||
34
docs/decisions/adr-009-integrations-folder-naming.md
Normal file
34
docs/decisions/adr-009-integrations-folder-naming.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# ADR-009: Rename `adapters/` to `integrations/`
|
||||
|
||||
**Status:** Accepted
|
||||
**Date:** 2026-05-04
|
||||
|
||||
## Context
|
||||
|
||||
The source spec (monorepo-architecture-spec-detailed-v5.md) uses `adapters/cms` and `adapters/api` for Payload and tRPC integration points. Clean Architecture also uses `adapters/` (or `interface-adapters/`) for transport-agnostic controller layer. The name collision is confusing.
|
||||
|
||||
## Decision
|
||||
|
||||
Use `integrations/` for role-based plug points (Payload and tRPC). Keep `interface-adapters/` for the Clean Architecture controller layer.
|
||||
|
||||
**Result:**
|
||||
```
|
||||
packages/<feature>/src/
|
||||
interface-adapters/controllers/ ← Clean Architecture layer (transport-agnostic)
|
||||
integrations/
|
||||
cms/ ← Payload collections/globals
|
||||
api/ ← tRPC routers
|
||||
```
|
||||
|
||||
## Consequences
|
||||
|
||||
- No naming collision — intent is unambiguous
|
||||
- `integrations/` semantically captures "external system integration" better than `adapters/`
|
||||
- Minor deviation from source spec, documented here as deliberate choice
|
||||
- All new features follow this naming consistently
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
- Keep `adapters/`: Collision with Clean Architecture terminology is confusing
|
||||
- Use `external/`: Less specific; doesn't convey "Payload and tRPC"
|
||||
- Rename Clean Architecture layer to `controllers/`: Could work; less standard
|
||||
Reference in New Issue
Block a user