docs(architecture): rewrite dependency-flow for vertical features + boundary rules
This commit is contained in:
@@ -1,50 +1,70 @@
|
||||
# Dependency Flow
|
||||
|
||||
## Package Dependencies (one direction only)
|
||||
|
||||
```
|
||||
apps/web-next → @repo/api-client, @repo/ui
|
||||
Startup: @repo/cms-core (config) + @repo/cms-client (init local)
|
||||
apps/web-tanstack → @repo/api-client, @repo/ui
|
||||
Startup: @repo/cms-core (config) + @repo/cms-client (init local)
|
||||
apps/cms → @repo/cms-core, payload, next
|
||||
apps/storybook → @repo/ui
|
||||
+-------------+ +-----------------+ +-----------+
|
||||
| apps/web- | | apps/web- | | apps/cms |
|
||||
| next | | tanstack | | |
|
||||
+------+------+ +--------+--------+ +-----+-----+
|
||||
| | |
|
||||
+------------------+--------------+ | |
|
||||
| | | | |
|
||||
+----v-----+ +-----v------+ +-----v----v---+ +-------v------+
|
||||
| core-api | | core-trpc | | feature | | core-cms |
|
||||
| | | | | packages | | |
|
||||
+-----+----+ +-----+------+ +------+-------+ +-------+------+
|
||||
| | | |
|
||||
| | | |
|
||||
+--+-------+------+---------------+----+ +-------------+
|
||||
| | | |
|
||||
+----v---+ +-v---------+ +-------v---v---+
|
||||
| core- | | core-ui | | core-shared |
|
||||
| shared | | | | |
|
||||
+--------+ +-----------+ +----------------+
|
||||
|
||||
@repo/api-client → @repo/api (router types only)
|
||||
@repo/api → @repo/core/interface-adapters (controllers)
|
||||
@repo/cms-core → @repo/core/application (use cases for hooks), payload (types)
|
||||
@repo/cms-client → (standalone — receives Payload instance, doesn't import it)
|
||||
@repo/ui → (standalone — tailwind, shadcn)
|
||||
Boundary rules (enforced by eslint-plugin-boundaries):
|
||||
app → app, core, feature, core-composition (any)
|
||||
feature → core (any), but NOT other features, NOT app
|
||||
core → core, but NOT feature, NOT app
|
||||
core-composition → core, feature subpath exports only (`/cms`, `/api`)
|
||||
core-api → @repo/<feature>/api
|
||||
core-cms → @repo/<feature>/cms
|
||||
```
|
||||
|
||||
## Core Internal Dependencies
|
||||
## Concrete examples
|
||||
|
||||
```
|
||||
core/entities → (standalone — zero deps)
|
||||
core/application → core/entities only
|
||||
core/interface-adapters → core/application, core/entities
|
||||
core/infrastructure → core/application, core/entities, @repo/cms-client, external libs
|
||||
core/di → all internal layers
|
||||
Allowed:
|
||||
```ts
|
||||
// in apps/web-next
|
||||
import { appRouter } from "@repo/core-api";
|
||||
import { NextTrpcProvider } from "@repo/core-trpc/next";
|
||||
import { bindProductionBlog } from "@repo/blog/di/bind-production";
|
||||
|
||||
// in packages/blog
|
||||
import { slugifyIfMissing } from "@repo/core-shared/payload";
|
||||
|
||||
// in packages/core-api
|
||||
import { blogRouter } from "@repo/blog/api"; // composition exception
|
||||
import { router } from "@repo/core-shared/trpc/init"; // core → core fine
|
||||
|
||||
// in packages/core-cms
|
||||
import { articles } from "@repo/blog/cms"; // composition exception
|
||||
```
|
||||
|
||||
## Circular Dependency Prevention — HARD RULES
|
||||
Disallowed:
|
||||
```ts
|
||||
// in packages/blog (cross-feature)
|
||||
import { Article } from "@repo/marketing-pages"; // ❌ feature → feature
|
||||
|
||||
- **NEVER:** packages/core → apps/*
|
||||
- **NEVER:** apps/cms → packages/core/infrastructure
|
||||
- **NEVER:** packages/cms-client → apps/cms or packages/core or packages/cms-core
|
||||
- **NEVER:** packages/cms-core → packages/cms-client
|
||||
- **NEVER:** core/entities → anything
|
||||
- **NEVER:** core/application → core/infrastructure
|
||||
// in packages/blog (deep import past public exports)
|
||||
import { articles } from "@repo/blog/src/integrations/cms/collections/articles"; // ❌ no-private
|
||||
|
||||
## Why These Rules Exist
|
||||
// in packages/core-shared
|
||||
import { blogRouter } from "@repo/blog/api"; // ❌ core → feature
|
||||
|
||||
The Payload CMS integration creates a potential circular dependency:
|
||||
```
|
||||
apps/cms hooks → @repo/core/application (use cases)
|
||||
@repo/core/infrastructure → @repo/cms-client → Payload API
|
||||
// in packages/core-trpc
|
||||
import { someBlogThing } from "@repo/blog"; // ❌ core → feature (only core-api/core-cms have exception)
|
||||
```
|
||||
|
||||
This is resolved by:
|
||||
1. `cms-client` is standalone — receives Payload instance via injection, never imports it
|
||||
2. `cms-core` hooks only import from `core/application`, never `core/infrastructure`
|
||||
3. App startup code (not a shared package) wires Payload instance into cms-client
|
||||
## Three-layer enforcement
|
||||
|
||||
ESLint catches accidental cross-package imports at lint time. The `package.json` `exports` map blocks deep imports at module-resolution time. Workspace `dependencies` declarations make the package graph itself the source of truth — if you didn't declare it, you can't import it.
|
||||
|
||||
Reference in New Issue
Block a user