---
id: wiki-2026-0508-안전한-typescript-데이터-모델링-및-설정-관리-구
title: 안전한 TypeScript 데이터 모델링 및 설정 관리 구축
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [TS Safe Data Modeling, TS Config Management, Parse Don't Validate TS]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [typescript, data-modeling, configuration, validation, zod, effect]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
  language: TypeScript
  framework: TypeScript 5.7+/Zod/Effect
---

# 안전한 TypeScript 데이터 모델링 및 설정 관리 구축

## 매 한 줄
> **"매 boundary 에서 parse, 매 internal 에서 trust"**. unknown data (HTTP body, env var, DB row) 가 매 application 에 들어오는 매 모든 boundary 에서 매 Zod/Effect 로 parse 하여 매 typed domain object 로 변환, 매 internal 에서는 매 branded + readonly + discriminated union 으로 매 illegal state unrepresentable. 2026 년 production TS app 의 표준.

## 매 핵심

### 매 5 원칙
1. **Parse, don't validate** (Wlaschin) — 매 boundary 에서 매 Result<T, E>.
2. **Make illegal states unrepresentable** — 매 union 으로 분기.
3. **Brand domain primitives** — 매 UserId vs OrderId.
4. **Readonly by default** — 매 mutation 매 명시적.
5. **Total functions** — 매 partial 함수 매 X.

### 매 boundary
- HTTP request body / query / params.
- Environment variables.
- DB rows (ORM 결과 매 untrusted 처리).
- File / external API.
- WebSocket / queue message.

### 매 응용
1. Env config (process.env → typed config).
2. API request validation.
3. DB row → domain entity.
4. CLI argument parsing.

## 💻 패턴

### Env config (Zod)
```ts
import { z } from "zod";

const EnvSchema = z.object({
  NODE_ENV: z.enum(["development", "production", "test"]),
  PORT: z.coerce.number().int().min(1).max(65535).default(3000),
  DATABASE_URL: z.string().url(),
  STRIPE_KEY: z.string().startsWith("sk_"),
  LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"),
  REDIS_URL: z.string().url().optional(),
});

export type Env = z.infer<typeof EnvSchema>;

export const env: Env = EnvSchema.parse(process.env);
```

### HTTP request (zod + hono)
```ts
import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator";
import { z } from "zod";

const CreateOrderBody = z.object({
  userId: z.string().uuid().brand<"UserId">(),
  items: z.array(z.object({
    sku: z.string().regex(/^[A-Z]{3}-\d{4}$/),
    qty: z.number().int().positive(),
  })).min(1).max(100),
  couponCode: z.string().optional(),
});

const app = new Hono();
app.post("/orders", zValidator("json", CreateOrderBody), async (c) => {
  const body = c.req.valid("json");
  const order = await placeOrder(body);
  return c.json({ id: order.id });
});
```

### Illegal state unrepresentable
```ts
// 매 X bad — 매 invalid 조합 가능
interface BadRequest {
  status: "pending" | "approved" | "rejected";
  approvedBy?: string;
  rejectedReason?: string;
  approvedAt?: Date;
}

// 매 O good — 매 discriminated union
type Request =
  | { kind: "pending"; createdAt: Date }
  | { kind: "approved"; createdAt: Date; approvedBy: UserId; approvedAt: Date }
  | { kind: "rejected"; createdAt: Date; rejectedReason: string; rejectedAt: Date };

function notify(r: Request) {
  switch (r.kind) {
    case "approved": return sendApprovalMail(r.approvedBy, r.approvedAt);
    case "rejected": return sendRejectionMail(r.rejectedReason);
    case "pending": return;
  }
}
```

### DB row → domain entity (parse layer)
```ts
import { z } from "zod";

const DbOrderRow = z.object({
  id: z.string().uuid().brand<"OrderId">(),
  user_id: z.string().uuid().brand<"UserId">(),
  total_cents: z.number().int().nonnegative(),
  currency: z.enum(["USD", "EUR", "KRW"]),
  status: z.enum(["pending", "paid", "shipped", "cancelled"]),
  created_at: z.coerce.date(),
});

export class OrderEntity {
  private constructor(private readonly props: z.infer<typeof DbOrderRow>) {}

  static fromRow(row: unknown): OrderEntity {
    return new OrderEntity(DbOrderRow.parse(row));
  }

  get id() { return this.props.id; }
  get total(): Money { return Money.of(this.props.total_cents, this.props.currency); }
  isPaid(): boolean { return this.props.status === "paid"; }
}
```

### Effect schema + decode
```ts
import { Schema, Effect } from "effect";

const User = Schema.Struct({
  id: Schema.String.pipe(Schema.brand("UserId")),
  email: Schema.String.pipe(Schema.pattern(/^[^@]+@[^@]+$/), Schema.brand("Email")),
  age: Schema.Number.pipe(Schema.int(), Schema.greaterThanOrEqualTo(0)),
});

const decodeUser = (input: unknown) =>
  Schema.decodeUnknown(User)(input);

const program = Effect.gen(function* () {
  const user = yield* decodeUser(rawJson);
  console.log(user.email);
});
```

### Readonly + immutable update
```ts
type CartItem = Readonly<{
  sku: SKU;
  qty: number;
  priceCents: number;
}>;

type Cart = Readonly<{
  userId: UserId;
  items: ReadonlyArray<CartItem>;
}>;

function addItem(cart: Cart, item: CartItem): Cart {
  return { ...cart, items: [...cart.items, item] };
}

function setQty(cart: Cart, sku: SKU, qty: number): Cart {
  return {
    ...cart,
    items: cart.items.map(i => i.sku === sku ? { ...i, qty } : i),
  };
}
```

### Smart constructor + validated factory
```ts
type Email = string & Brand.Brand<"Email">;
type NonEmpty = string & Brand.Brand<"NonEmpty">;

const Email = (raw: string): Email => {
  if (!/^[^\s@]+@[^\s@]+$/.test(raw))
    throw new TypeError(`bad email: ${raw}`);
  return raw as Email;
};

const NonEmpty = (raw: string): NonEmpty => {
  const t = raw.trim();
  if (t.length === 0) throw new TypeError("empty string");
  return t as NonEmpty;
};

interface User {
  email: Email;
  displayName: NonEmpty;
}

const u: User = {
  email: Email("a@b.com"),
  displayName: NonEmpty("Alice"),
};
```

## 매 결정 기준
| Layer | Tool |
|---|---|
| Env / config | Zod schema (startup parse) |
| HTTP boundary | Zod + framework adapter |
| DB row | Zod / Effect Schema |
| Internal domain | Branded + discriminated union |
| Complex validation pipeline | Effect Schema |
| Single project simplicity | Zod 만 |

**기본값**: Zod (boundary) + Effect Brand (internal) + readonly everywhere.

## 🔗 Graph
- 부모: [[TypeScript Type System]] · [[Domain-Driven Design]]
- 변형: [[Parse Don't Validate]] · [[Smart Constructor]] · [[Type-Driven Development]]
- 응용: [[Zod]] · [[Effect Schema]] · [[Valibot]] · [[ArkType]]
- Adjacent: [[브랜디드 타입 (Branded Types)]] · [[약한 타입 탐지 (Weak Type Detection)]] · [[대규모 TypeScript 애플리케이션 아키텍처 설계]]

## 🤖 LLM 활용
**언제**: 매 production app, 매 external boundary, 매 multi-team, 매 strong audit/compliance.
**언제 X**: 매 throwaway script, 매 internal-only tool, 매 validation overhead > value.

## ❌ 안티패턴
- **`any` cast at boundary**: 매 type system bypass — 매 production runtime crash.
- **Validation only (no parse)**: 매 `validate(x)` 후 매 `x` 가 매 still untyped.
- **Mutating domain object**: 매 readonly 무시 — 매 race condition.
- **Schema drift**: 매 API spec 과 매 zod schema 매 manual sync — 매 codegen 사용.
- **Boundary 매 optional check**: 매 일부 endpoint 만 검증 — 매 전부 검증.

## 🧪 검증 / 중복
- Verified (Scott Wlaschin "Domain Modeling Made Functional", Zod docs, Effect Schema docs, TypeScript best practices 2026).
- 신뢰도 A.

## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — boundary parsing + 7 patterns |
