[G1-Sync] Manual knowledge update
This commit is contained in:
@@ -0,0 +1,244 @@
|
||||
---
|
||||
id: arch-module-boundaries
|
||||
title: Module Boundaries — Public API / 의존 관리
|
||||
category: Coding
|
||||
status: draft
|
||||
source_trust_level: B
|
||||
verification_status: conceptual
|
||||
created_at: 2026-05-09
|
||||
updated_at: 2026-05-09
|
||||
tags: [architecture, module, package, vibe-coding]
|
||||
tech_stack: { language: "TS", applicable_to: ["Backend", "Frontend"] }
|
||||
applied_in: []
|
||||
aliases: [module boundary, public API, package, internal, dependency cruiser, layered]
|
||||
---
|
||||
|
||||
# Module Boundaries
|
||||
|
||||
> Folder ≠ module. **Public API (index.ts) 만 export, 내부 implementation 가림**. ESLint / dependency-cruiser / project references 로 강제. Modular monolith → 미래 microservice 분리 쉬움.
|
||||
|
||||
## 📖 핵심 개념
|
||||
- Public API: index.ts / barrel — 외부 사용 허용.
|
||||
- Internal: 외부 import 금지.
|
||||
- Acyclic: 순환 의존 X.
|
||||
- Layered: domain → app → infra (한 방향).
|
||||
|
||||
## 💻 코드 패턴
|
||||
|
||||
### 폴더 구조
|
||||
```
|
||||
src/
|
||||
modules/
|
||||
ordering/
|
||||
index.ts # public API
|
||||
domain/ # internal
|
||||
application/ # internal
|
||||
infrastructure/ # internal
|
||||
catalog/
|
||||
index.ts
|
||||
...
|
||||
```
|
||||
|
||||
```ts
|
||||
// modules/ordering/index.ts
|
||||
export { CreateOrderUseCase } from './application/createOrder';
|
||||
export { OrderRepository } from './ports/orderRepository';
|
||||
export type { Order, OrderId } from './domain/order';
|
||||
// internal 은 export X
|
||||
```
|
||||
|
||||
```ts
|
||||
// modules/billing/...
|
||||
import { CreateOrderUseCase, type Order } from '../ordering';
|
||||
// ✅ public API 만
|
||||
|
||||
import { OrderEntity } from '../ordering/domain/order';
|
||||
// ❌ internal — 차단
|
||||
```
|
||||
|
||||
### ESLint — no-restricted-imports
|
||||
```js
|
||||
// .eslintrc
|
||||
{
|
||||
"rules": {
|
||||
"no-restricted-imports": ["error", {
|
||||
"patterns": [
|
||||
"*/modules/*/domain/*",
|
||||
"*/modules/*/application/*",
|
||||
"*/modules/*/infrastructure/*",
|
||||
]
|
||||
}]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
→ 내부 import 시도 = lint 에러.
|
||||
|
||||
### dependency-cruiser
|
||||
```js
|
||||
// .dependency-cruiser.cjs
|
||||
module.exports = {
|
||||
forbidden: [
|
||||
{
|
||||
name: 'no-circular',
|
||||
severity: 'error',
|
||||
from: {},
|
||||
to: { circular: true },
|
||||
},
|
||||
{
|
||||
name: 'no-cross-module-internals',
|
||||
severity: 'error',
|
||||
from: { path: '^src/modules/([^/]+)' },
|
||||
to: { path: '^src/modules/(?!\\1)([^/]+)/(domain|application|infrastructure)' },
|
||||
},
|
||||
{
|
||||
name: 'domain-no-deps',
|
||||
severity: 'error',
|
||||
from: { path: '^src/modules/[^/]+/domain' },
|
||||
to: { path: '^src/modules/[^/]+/(application|infrastructure)' },
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
```bash
|
||||
depcruise src --output-type err
|
||||
```
|
||||
|
||||
### TS project references
|
||||
```jsonc
|
||||
// modules/ordering/tsconfig.json
|
||||
{
|
||||
"compilerOptions": { "composite": true, "rootDir": "src", "outDir": "dist" }
|
||||
}
|
||||
```
|
||||
|
||||
```jsonc
|
||||
// modules/billing/tsconfig.json
|
||||
{
|
||||
"references": [{ "path": "../ordering" }]
|
||||
}
|
||||
```
|
||||
|
||||
→ ordering 의 public API 만 type 노출.
|
||||
|
||||
### pnpm workspace (작은 monorepo)
|
||||
```yaml
|
||||
# pnpm-workspace.yaml
|
||||
packages:
|
||||
- 'modules/*'
|
||||
```
|
||||
|
||||
```jsonc
|
||||
// modules/ordering/package.json
|
||||
{
|
||||
"name": "@app/ordering",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
// 다른 module
|
||||
import { CreateOrderUseCase } from '@app/ordering';
|
||||
```
|
||||
|
||||
→ Module = npm package. 강제 boundary.
|
||||
|
||||
### Internal package (같은 monorepo, 외부 publish X)
|
||||
```jsonc
|
||||
{
|
||||
"name": "@app/ordering",
|
||||
"private": true,
|
||||
"version": "0.0.0"
|
||||
}
|
||||
```
|
||||
|
||||
### Cross-module 통신 = event / interface
|
||||
```ts
|
||||
// ordering 이 billing 직접 호출 금지
|
||||
// 대신:
|
||||
|
||||
// 1. Event
|
||||
// modules/ordering/domain/events.ts
|
||||
export class OrderPlaced { ... }
|
||||
|
||||
// modules/billing/handlers.ts
|
||||
import { OrderPlaced } from '@app/ordering';
|
||||
on(OrderPlaced, async (ev) => createInvoice(ev));
|
||||
|
||||
// 2. Or: ordering 이 BillingPort interface 정의 → composition root 가 wire
|
||||
export interface BillingPort {
|
||||
createInvoice(orderId: OrderId): Promise<Invoice>;
|
||||
}
|
||||
```
|
||||
|
||||
### Public API 변경 = breaking
|
||||
```ts
|
||||
// 명시적 versioning 또는 careful change
|
||||
// Library처럼 semver
|
||||
```
|
||||
|
||||
### Visibility 강제 안 되는 언어 (TS)
|
||||
TS 는 internal 키워드 X. eslint / depcruise 가 대안.
|
||||
Java/Rust 는 `package` / `crate` 가 native.
|
||||
|
||||
### Modular monolith → microservice 추출 쉬움
|
||||
```
|
||||
1. Module 이 명확 boundary
|
||||
2. 통신 = interface / event
|
||||
3. Module 의 DB 분리
|
||||
4. Module 을 별 service 로 분리
|
||||
```
|
||||
|
||||
→ 시작은 monolith, 필요 시 분리.
|
||||
|
||||
### 단계
|
||||
```
|
||||
Phase 1: 한 폴더 (chaos)
|
||||
Phase 2: layer 분리 (web, services, repositories)
|
||||
Phase 3: bounded context 분리 (modules/)
|
||||
Phase 4: package 별 (pnpm workspace)
|
||||
Phase 5: 별 service (microservice)
|
||||
```
|
||||
|
||||
→ 대부분 phase 3 으로 충분.
|
||||
|
||||
### Anemic boundary 함정
|
||||
```ts
|
||||
// boundary 약함 — 모두가 모두를 import
|
||||
import { db } from '../../db';
|
||||
import { logger } from '../../logger';
|
||||
import { config } from '../../config';
|
||||
```
|
||||
|
||||
→ Shared kernel 작게 + 명시.
|
||||
|
||||
## 🤔 의사결정 기준
|
||||
| 규모 | 추천 |
|
||||
|---|---|
|
||||
| <5 파일 | 한 폴더 OK |
|
||||
| <20 파일 | layer 분리 |
|
||||
| 큰 도메인 / 팀 | bounded context modules/ |
|
||||
| 큰 monorepo | package per module |
|
||||
| 분리 service 후보 | package + interface |
|
||||
| Internal lib | semver + private package |
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Barrel index.ts 가 거의 모두 export**: 내부 노출 = boundary 의미 없음.
|
||||
- **순환 의존**: A → B → A. depcruise 로 차단.
|
||||
- **Cross-module deep import**: 내부 변경 시 깨짐.
|
||||
- **Shared 폴더 거대**: 모두가 의존 — module 분리 의미 없음.
|
||||
- **공유 DB schema**: data coupling. module 별 schema 또는 view.
|
||||
- **Strong type sharing**: type 도 boundary. shared types 작게.
|
||||
|
||||
## 🤖 LLM 활용 힌트
|
||||
- index.ts barrel + 내부 차단 (eslint / depcruise).
|
||||
- 순환 의존 없음 항상.
|
||||
- Module = future service 후보.
|
||||
|
||||
## 🔗 관련 문서
|
||||
- [[Arch_Hexagonal_Clean]]
|
||||
- [[Arch_DDD_Bounded_Context]]
|
||||
- [[TS_Monorepo_Patterns]]
|
||||
Reference in New Issue
Block a user