|
1 | | ---- |
2 | | -name: adapter-develop |
3 | | -description: "Functorium 프레임워크 기반 어댑터 레이어(Repository, Query Adapter, Endpoint, DI 등록)를 구현합니다. 'Repository 구현', '어댑터 추가', '엔드포인트 만들어줘', 'DI 등록', 'EF Core 설정', 'Dapper 쿼리' 등의 요청에 반응합니다." |
4 | | ---- |
5 | | - |
6 | | -## 선행 조건 |
7 | | - |
8 | | -`application-develop` 스킬에서 생성한 `application/03-implementation-results.md`를 읽어 Port 목록(IRepository, IQueryPort, External Service)을 확인합니다. |
9 | | -`architecture-design` 스킬에서 생성한 `01-architecture-design.md`가 있으면 읽어 폴더 구조와 영속성 전략을 확인합니다. |
10 | | - |
11 | | -## 후속 스킬 |
12 | | - |
13 | | -``` |
14 | | -project-spec → architecture-design → domain-develop → application-develop → **adapter-develop** → observability-develop → test-develop |
15 | | -``` |
16 | | - |
17 | | -어댑터 구현 완료 후 `observability-develop` 스킬에서 관측성 전략(KPI→메트릭 매핑, 대시보드, 알림, ctx.* 전파)을 설계합니다. |
18 | | - |
19 | | -# Adapter Develop Skill |
20 | | - |
21 | | -Functorium 프레임워크 기반 어댑터 레이어를 구현하는 스킬입니다. |
22 | | - |
23 | | -## 워크플로우 |
24 | | - |
25 | | -### Phase 1: 포트 → 어댑터 매핑 |
26 | | - |
27 | | -사용자에게 질문: |
28 | | -- 어떤 포트를 구현할까요? (IRepository, IQueryPort, External API, Endpoint) |
29 | | -- 영속성 전략은? (EF Core, InMemory, Dapper) |
30 | | -- Soft Delete가 필요한가? |
31 | | - |
32 | | -### Phase 2: 어댑터 구현 |
33 | | - |
34 | | -**Repository (Write Side):** |
35 | | -- `[GenerateObservablePort]` 어트리뷰트 적용 |
36 | | -- `EfCoreRepositoryBase<TAgg, TId, TModel>` 상속 |
37 | | - - `ToDomain` / `ToModel` 매퍼 구현 |
38 | | - - `DbContext`, `DbSet` 오버라이드 |
39 | | - - `PropertyMap`으로 Specification 매핑 |
40 | | - - Include 선언 (N+1 방지) |
41 | | -- `InMemoryRepositoryBase<TAgg, TId>` 상속 (테스트용) |
42 | | -- 모든 메서드 `virtual` (Source Generator 파이프라인 필수) |
43 | | - |
44 | | -**Query Adapter (Read Side / CQRS):** |
45 | | -- `DapperQueryBase<TEntity, TDto>` 상속 |
46 | | - - `SelectSql`, `CountSql`, `DefaultOrderBy`, `AllowedSortColumns` 오버라이드 |
47 | | - - `DapperSpecTranslator` 구현 |
48 | | -- `InMemoryQueryBase<TEntity, TDto>` 상속 (테스트용) |
49 | | - |
50 | | -**Endpoint (Presentation):** |
51 | | -- FastEndpoints의 `Endpoint<TRequest, TResponse>` 상속 |
52 | | -- `IMediator.Send()` → `FinResponse` 매핑 |
53 | | -- `SendCreatedFinResponseAsync` / `SendFinResponseAsync` |
54 | | - |
55 | | -**External API:** |
56 | | -- `IObservablePort` 구현 |
57 | | -- `IO.liftAsync` + try/catch |
58 | | -- `AdapterError.For` / `AdapterError.FromException` |
59 | | - |
60 | | -상세 패턴은 references 파일을 읽으세요. |
61 | | - |
62 | | -### ObservableSignal — Adapter 내부 개발자 로깅 |
63 | | - |
64 | | -`ObservableSignal`은 Adapter 구현 코드 내부에서 개발자가 직접 운영 목적의 로그를 출력하는 정적 API입니다. |
65 | | -Observable 래퍼가 설정한 공통 컨텍스트(`request.layer`, `request.category.name`, `request.handler.name`, `request.handler.method`)를 자동으로 포함합니다. |
66 | | - |
67 | | -**Pillar 범위:** |
68 | | -- Debug → Logging만 (고빈도 → Tracing 노이즈 방지) |
69 | | -- Warning/Error → Logging + Activity Event |
70 | | - |
71 | | -**로그 수준 가이드:** |
72 | | -| Level | 용도 | 예시 | |
73 | | -|-------|------|------| |
74 | | -| Debug | 정상 흐름 상세 (프로덕션 비활성) | 캐시 미스, 쿼리 상세 | |
75 | | -| Warning | 자동 복구 가능한 열화 | Polly 재시도, rate limit, 느린 쿼리 | |
76 | | -| Error | 복구 불가 실패 | 재시도 소진, 서킷 오픈, DLQ 이동 | |
77 | | - |
78 | | -**사용 예시:** |
79 | | -```csharp |
80 | | -// Polly 재시도 시 |
81 | | -ObservableSignal.Warning("Retry attempt {Attempt}/{MaxRetry}", |
82 | | - ("adapter.retry.attempt", attempt), |
83 | | - ("adapter.retry.delay_ms", delay.TotalMilliseconds)); |
84 | | - |
85 | | -// 캐시 미스 시 |
86 | | -ObservableSignal.Debug("Cache miss", ("adapter.cache.key", cacheKey)); |
87 | | - |
88 | | -// 재시도 소진 시 |
89 | | -ObservableSignal.Error(ex, "Database operation failed after exhausting retries", |
90 | | - ("adapter.db.retry.attempt", maxRetries)); |
91 | | -``` |
92 | | - |
93 | | -**부가 필드 프리픽스**: `adapter.*` (adapter.retry.*, adapter.http.*, adapter.db.*, adapter.cache.*, adapter.message.*) |
94 | | - |
95 | | -### CtxEnricherPipeline |
96 | | -`CtxEnricherPipeline`은 파이프라인 최선두에서 `IUsecaseCtxEnricher`를 호출하여 |
97 | | -`ctx.*` 필드를 Logging/Tracing/MetricsTag에 동시 전파합니다. |
98 | | - |
99 | | -Adapter 초기화에서 `CtxEnricherContext.SetPushFactory`를 등록하여 |
100 | | -Serilog LogContext + Activity.SetTag + MetricsTagContext를 통합합니다. |
101 | | - |
102 | | -Observable Port의 `RequestCategory` 예시: |
103 | | -- Repository → `"repository"` |
104 | | -- QueryAdapter → `"query"` |
105 | | -- ExternalApi → `"external_api"` |
106 | | -- UnitOfWork → `"unit_of_work"` |
107 | | - |
108 | | -### Phase 3: DI 등록 |
109 | | - |
110 | | -- `RegisterScopedObservablePort<IPort, AdapterObservable>()` |
111 | | -- `RegisterConfigureOptions<TOptions, TValidator>()` |
112 | | -- Provider 분기 (InMemory vs Sqlite) |
113 | | - |
114 | | -### Phase 4: 구현 |
115 | | - |
116 | | -.cs 파일 생성 + EF Core Configuration |
117 | | - |
118 | | -**출력:** `{context}/adapter/` 폴더에 4단계 문서 + 소스 코드 |
119 | | - |
120 | | -## References |
121 | | - |
122 | | -- Repository 패턴: `references/repository-patterns.md` |
123 | | -- Endpoint 패턴: `references/endpoint-patterns.md` |
124 | | -- DI 등록 패턴: `references/di-registration.md` |
| 1 | +--- |
| 2 | +name: adapter-develop |
| 3 | +description: "Functorium 프레임워크 기반 어댑터 레이어(Repository, Query Adapter, Endpoint, DI 등록)를 구현합니다. 'Repository 구현', '어댑터 추가', '엔드포인트 만들어줘', 'DI 등록', 'EF Core 설정', 'Dapper 쿼리' 등의 요청에 반응합니다." |
| 4 | +--- |
| 5 | + |
| 6 | +## 선행 조건 |
| 7 | + |
| 8 | +`application-develop` 스킬에서 생성한 `application/03-implementation-results.md`를 읽어 Port 목록(IRepository, IQueryPort, External Service)을 확인합니다. |
| 9 | +`architecture-design` 스킬에서 생성한 `01-architecture-design.md`가 있으면 읽어 폴더 구조와 영속성 전략을 확인합니다. |
| 10 | + |
| 11 | +## 후속 스킬 |
| 12 | + |
| 13 | +``` |
| 14 | +project-spec → architecture-design → domain-develop → application-develop → **adapter-develop** → observability-develop → test-develop |
| 15 | +``` |
| 16 | + |
| 17 | +어댑터 구현 완료 후 `observability-develop` 스킬에서 관측성 전략(KPI→메트릭 매핑, 대시보드, 알림, ctx.* 전파)을 설계합니다. |
| 18 | + |
| 19 | +# Adapter Develop Skill |
| 20 | + |
| 21 | +Functorium 프레임워크 기반 어댑터 레이어를 구현하는 스킬입니다. |
| 22 | + |
| 23 | +## 워크플로우 |
| 24 | + |
| 25 | +### Phase 1: 포트 → 어댑터 매핑 |
| 26 | + |
| 27 | +사용자에게 질문: |
| 28 | +- 어떤 포트를 구현할까요? (IRepository, IQueryPort, External API, Endpoint) |
| 29 | +- 영속성 전략은? (EF Core, InMemory, Dapper) |
| 30 | +- Soft Delete가 필요한가? |
| 31 | + |
| 32 | +### Phase 2: 어댑터 구현 |
| 33 | + |
| 34 | +**Repository (Write Side):** |
| 35 | +- `[GenerateObservablePort]` 어트리뷰트 적용 |
| 36 | +- `EfCoreRepositoryBase<TAgg, TId, TModel>` 상속 |
| 37 | + - `ToDomain` / `ToModel` 매퍼 구현 |
| 38 | + - `DbContext`, `DbSet` 오버라이드 |
| 39 | + - `PropertyMap`으로 Specification 매핑 |
| 40 | + - Include 선언 (N+1 방지) |
| 41 | +- `InMemoryRepositoryBase<TAgg, TId>` 상속 (테스트용) |
| 42 | +- 모든 메서드 `virtual` (Source Generator 파이프라인 필수) |
| 43 | + |
| 44 | +**Query Adapter (Read Side / CQRS):** |
| 45 | +- `DapperQueryBase<TEntity, TDto>` 상속 |
| 46 | + - `SelectSql`, `CountSql`, `DefaultOrderBy`, `AllowedSortColumns` 오버라이드 |
| 47 | + - `DapperSpecTranslator` 구현 |
| 48 | +- `InMemoryQueryBase<TEntity, TDto>` 상속 (테스트용) |
| 49 | + |
| 50 | +**Endpoint (Presentation):** |
| 51 | +- FastEndpoints의 `Endpoint<TRequest, TResponse>` 상속 |
| 52 | +- `IMediator.Send()` → `FinResponse` 매핑 |
| 53 | +- `SendCreatedFinResponseAsync` / `SendFinResponseAsync` |
| 54 | + |
| 55 | +**External API:** |
| 56 | +- `IObservablePort` 구현 |
| 57 | +- `IO.liftAsync` + try/catch |
| 58 | +- `AdapterError.For` / `AdapterError.FromException` |
| 59 | + |
| 60 | +상세 패턴은 references 파일을 읽으세요. |
| 61 | + |
| 62 | +### ObservableSignal — Adapter 내부 개발자 로깅 |
| 63 | + |
| 64 | +`ObservableSignal`은 Adapter 구현 코드 내부에서 개발자가 직접 운영 목적의 로그를 출력하는 정적 API입니다. |
| 65 | +Observable 래퍼가 설정한 공통 컨텍스트(`request.layer`, `request.category.name`, `request.handler.name`, `request.handler.method`)를 자동으로 포함합니다. |
| 66 | + |
| 67 | +**Pillar 범위:** |
| 68 | +- Debug → Logging만 (고빈도 → Tracing 노이즈 방지) |
| 69 | +- Warning/Error → Logging + Activity Event |
| 70 | + |
| 71 | +**로그 수준 가이드:** |
| 72 | +| Level | 용도 | 예시 | |
| 73 | +|-------|------|------| |
| 74 | +| Debug | 정상 흐름 상세 (프로덕션 비활성) | 캐시 미스, 쿼리 상세 | |
| 75 | +| Warning | 자동 복구 가능한 열화 | Polly 재시도, rate limit, 느린 쿼리 | |
| 76 | +| Error | 복구 불가 실패 | 재시도 소진, 서킷 오픈, DLQ 이동 | |
| 77 | + |
| 78 | +**사용 예시:** |
| 79 | +```csharp |
| 80 | +// Polly 재시도 시 |
| 81 | +ObservableSignal.Warning("Retry attempt {Attempt}/{MaxRetry}", |
| 82 | + ("adapter.retry.attempt", attempt), |
| 83 | + ("adapter.retry.delay_ms", delay.TotalMilliseconds)); |
| 84 | + |
| 85 | +// 캐시 미스 시 |
| 86 | +ObservableSignal.Debug("Cache miss", ("adapter.cache.key", cacheKey)); |
| 87 | + |
| 88 | +// 재시도 소진 시 |
| 89 | +ObservableSignal.Error(ex, "Database operation failed after exhausting retries", |
| 90 | + ("adapter.db.retry.attempt", maxRetries)); |
| 91 | +``` |
| 92 | + |
| 93 | +**부가 필드 프리픽스**: `adapter.*` (adapter.retry.*, adapter.http.*, adapter.db.*, adapter.cache.*, adapter.message.*) |
| 94 | + |
| 95 | +### CtxEnricherPipeline |
| 96 | +`CtxEnricherPipeline`은 파이프라인 최선두에서 `IUsecaseCtxEnricher`를 호출하여 |
| 97 | +`ctx.*` 필드를 Logging/Tracing/MetricsTag에 동시 전파합니다. |
| 98 | + |
| 99 | +Adapter 초기화에서 `CtxEnricherContext.SetPushFactory`를 등록하여 |
| 100 | +Serilog LogContext + Activity.SetTag + MetricsTagContext를 통합합니다. |
| 101 | + |
| 102 | +Observable Port의 `RequestCategory` 예시: |
| 103 | +- Repository → `"repository"` |
| 104 | +- QueryAdapter → `"query"` |
| 105 | +- ExternalApi → `"external_api"` |
| 106 | +- UnitOfWork → `"unit_of_work"` |
| 107 | + |
| 108 | +### Phase 3: DI 등록 |
| 109 | + |
| 110 | +- `RegisterScopedObservablePort<IPort, AdapterObservable>()` |
| 111 | +- `RegisterConfigureOptions<TOptions, TValidator>()` |
| 112 | +- Provider 분기 (InMemory vs Sqlite) |
| 113 | + |
| 114 | +### Phase 4: 구현 |
| 115 | + |
| 116 | +.cs 파일 생성 + EF Core Configuration |
| 117 | + |
| 118 | +**출력:** `{context}/adapter/` 폴더에 4단계 문서 + 소스 코드 |
| 119 | + |
| 120 | +## References |
| 121 | + |
| 122 | +- Repository 패턴: `references/repository-patterns.md` |
| 123 | +- Endpoint 패턴: `references/endpoint-patterns.md` |
| 124 | +- DI 등록 패턴: `references/di-registration.md` |
0 commit comments