docs(10_Wiki): 위키 전체 재구성 — Topic_* 폴더를 4개 카테고리로 통합 + 대규모 중복 제거

Topic_Agent/Topic_Blog/Topics/Topics_Biz/Topics_Meeting/Topics_Rag의 마크다운 지식 문서를
Topic_General/Topic_Programming/Topic_Graphic/Topic_Business 4개 카테고리로 재분류.

- 중복 제거: frontmatter의 status:duplicate/merged + duplicate_of/redirect_to 필드로
  자기 자신을 중복으로 선언한 리다이렉트 stub 1032개 제거, 완전 동일 내용 파일 472개 제거,
  동일 파일명·다른 내용 충돌 시 더 큰(완전한) 버전만 유지(162개 제거) — 총 1639개 중복 제거.
- 분류: 폴더 단위로 명확한 항목(AI_and_ML/Coding/Architecture 등 → Programming,
  Comfyui/Visual_Effects → Graphic, Topics_Biz/Topics_Meeting/사업 등 → Business,
  Poetic_Blog_Writing/창의성/Game_Design 등 → General)은 폴더 우선순위로,
  나머지 혼재 폴더(Topic_Agent/Topic_Blog/Topics 루트/Thinking & Reasoning/Other/UI_UX_Assets)는
  title/tags 키워드 스코어링으로 파일 단위 분류(불명확한 경우 General로 폴백).
  원본 폴더명은 "From_*" 서브폴더로 보존해 추적 가능성 유지.
- 최종 배치: Programming 2784 / General 1608 / Graphic 285 / Business 249 = 4926개 문서.
- 에이전트 운영 상태(.astra/.agent/.obsidian/sessions/memory/_company/docs/lessons/_shared/src)는
  지식 콘텐츠가 아니므로 재분류 대상에서 제외하고 원위치 유지.
- Topics/Topic_email(상위 보호 폴더 Topic_email과 파일명 100% 중복) 삭제 — 보호 폴더 자체는 미변경.
- 완전히 비게 된 Topic_Agent/Topic_Blog/Topics_Biz/Topics_Rag 폴더 제거.
This commit is contained in:
Antigravity Agent
2026-07-05 00:33:48 +09:00
parent 1cfd3bbb56
commit 9148c358d0
6455 changed files with 1 additions and 86875 deletions
@@ -0,0 +1,166 @@
---
id: backend-graphql-server-patterns
title: GraphQL Server — Schema / Resolver / DataLoader
category: Coding
status: draft
source_trust_level: B
verification_status: conceptual
created_at: 2026-05-09
updated_at: 2026-05-09
tags: [backend, graphql, dataloader, n+1, vibe-coding]
tech_stack: { language: "TS / Apollo / Pothos / Yoga", applicable_to: ["Backend"] }
applied_in: []
aliases: [Apollo, Yoga, Pothos, schema-first, code-first, N+1]
---
# GraphQL Server
> 클라이언트가 필요한 필드만 요청. **N+1 문제 = DataLoader** 가 답. Schema-first(SDL) vs Code-first(Pothos). REST 와 공존 — 단순 CRUD 는 REST 가 종종 낫다.
## 📖 핵심 개념
- Resolver: field 마다 lazy 로 호출.
- N+1: list resolver → 각 item resolver → DB 100번 hit.
- DataLoader: 같은 tick 안 batch + cache.
- Persisted query: 클라가 hash 만 보내 — 페이로드 줄임 + allowlist 보안.
## 💻 코드 패턴
### Pothos (code-first, type-safe)
```ts
import SchemaBuilder from '@pothos/core';
const builder = new SchemaBuilder<{
Context: { db: Db; loaders: Loaders };
}>({});
builder.objectType('User', {
fields: t => ({
id: t.exposeID('id'),
email: t.exposeString('email'),
posts: t.field({
type: ['Post'],
resolve: (user, _, ctx) => ctx.loaders.postsByUser.load(user.id),
}),
}),
});
builder.queryType({
fields: t => ({
me: t.field({
type: 'User',
resolve: (_, __, ctx) => ctx.db.getUser(ctx.userId),
}),
}),
});
export const schema = builder.toSchema();
```
### DataLoader — N+1 해결
```ts
import DataLoader from 'dataloader';
function makeLoaders(db: Db) {
return {
postsByUser: new DataLoader<string, Post[]>(async (userIds) => {
const posts = await db.posts.where('userId', 'in', userIds);
const byUser = new Map<string, Post[]>();
for (const p of posts) {
const arr = byUser.get(p.userId) ?? [];
arr.push(p);
byUser.set(p.userId, arr);
}
return userIds.map(id => byUser.get(id) ?? []);
}),
};
}
// 매 request 마다 새로 — cache 가 cross-request 누수 안 되게
app.use((req, res, next) => { req.loaders = makeLoaders(db); next(); });
```
### Mutations
```ts
builder.mutationType({
fields: t => ({
createPost: t.field({
type: 'Post',
args: {
input: t.arg({ type: CreatePostInput, required: true }),
},
resolve: async (_, { input }, ctx) => {
if (!ctx.userId) throw new Error('UNAUTHORIZED');
return ctx.db.posts.insert({ ...input, userId: ctx.userId });
},
}),
}),
});
```
### Subscription (real-time)
```ts
builder.subscriptionType({
fields: t => ({
postCreated: t.field({
type: 'Post',
subscribe: (_, __, ctx) => ctx.pubsub.subscribe('POST_CREATED'),
resolve: (payload) => payload,
}),
}),
});
```
### Server (Yoga)
```ts
import { createYoga } from 'graphql-yoga';
import { useResponseCache } from '@graphql-yoga/plugin-response-cache';
const yoga = createYoga({
schema,
context: ({ request }) => ({
db, userId: getUserId(request), loaders: makeLoaders(db),
}),
plugins: [
useResponseCache({ ttl: 1_000 }),
],
});
```
### Persisted query (보안 + 성능)
```ts
// 클라이언트 빌드 시 query → hash 매핑 .json 생성 → 서버에 동기화
// 서버는 hash 만 받고 등록된 query 만 실행
```
### Error handling
```ts
import { GraphQLError } from 'graphql';
throw new GraphQLError('Not found', { extensions: { code: 'NOT_FOUND' } });
```
## 🤔 의사결정 기준
| 상황 | 추천 |
|---|---|
| 다양한 클라 (web/mobile/admin) | GraphQL |
| 단순 CRUD 5개 endpoint | REST 충분 |
| 실시간 | Subscription (WS) 또는 SSE |
| 파일 업로드 | REST (multipart) — GraphQL 어색 |
| 마이크로서비스 통합 | Federation (Apollo / Mesh) |
| 강력 type safety | Pothos / GraphQL-Codegen |
## ❌ 안티패턴
- **DataLoader 안 씀**: N+1 으로 100ms → 5s.
- **DataLoader cross-request 공유**: 권한/cache leak.
- **Resolver 깊이 무제한**: query depth 제한 (e.g. 10) + cost analysis.
- **Internal error 그대로 노출**: stack trace 노출.
- **Auth resolver 안에서 검사**: 쉽게 까먹음. Auth directive 또는 plugin.
- **Mutation 이 read 도**: side-effect 명시 분리.
- **Schema 자동 노출 prod**: introspection off + persisted query.
## 🤖 LLM 활용 힌트
- Pothos + Yoga + DataLoader 디폴트.
- 매 요청 loaders 새로.
- Persisted query 권장.
## 🔗 관련 문서