summaryrefslogtreecommitdiff
path: root/lib/approval/README_CACHE.md
blob: b7bee00f553195e2f2ac567abb4c400e0aa34e5d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# 결재 시스템 캐시 무효화 가이드

## 문제 상황

Next.js는 서버 컴포넌트의 데이터 fetch 결과를 자동으로 캐시합니다. 결재 시스템에서 `withApproval()`로 결재를 상신하거나 폴링 서비스가 결재를 승인/반려 처리해도 캐시가 무효화되지 않아 페이지에 최신 데이터가 표시되지 않는 문제가 있었습니다.

특히 **백그라운드 프로세스** (폴링 서비스)에서는 Next.js의 `revalidateTag`를 직접 사용할 수 없습니다. `revalidateTag`는 request 컨텍스트가 필요한데, 백그라운드에서는 이 컨텍스트가 존재하지 않기 때문입니다.

## 해결 방법

API 라우트를 통한 캐시 무효화 시스템을 구축했습니다.

### 1. 캐시 태그 시스템

결재 관련 데이터에 캐시 태그를 추가:

```typescript
// lib/approval-log/service.ts
export async function getApprovalLogList(input: ListInput) {
  return unstable_cache(
    async () => {
      // ... 데이터 조회 로직
    },
    [cacheKey],
    {
      tags: ['approval-logs'], // 🏷️ 캐시 태그
      revalidate: 60, // 60초마다 자동 재검증 (폴백)
    }
  )();
}
```

### 2. 캐시 무효화 API

백그라운드에서도 사용 가능한 API 라우트:

```typescript
// app/api/revalidate/approval/route.ts
export async function POST(request: NextRequest) {
  const { tags } = await request.json();
  
  // 캐시 태그 무효화
  for (const tag of tags) {
    revalidateTag(tag);
  }
  
  return NextResponse.json({ success: true });
}
```

### 3. 캐시 무효화 헬퍼 함수

편리하게 사용할 수 있는 유틸리티:

```typescript
// lib/approval/cache-utils.ts

// 결재 로그 캐시 무효화
export async function revalidateApprovalLogs() {
  await fetch('/api/revalidate/approval', {
    method: 'POST',
    body: JSON.stringify({ tags: ['approval-logs'] })
  });
}

// 모든 결재 관련 캐시 무효화
export async function revalidateAllApprovalCaches() {
  await fetch('/api/revalidate/approval', {
    method: 'POST',
    body: JSON.stringify({
      tags: ['approval-logs', 'pending-actions', 'approval-templates']
    })
  });
}
```

### 4. 워크플로우에 통합

결재 상신/승인/반려 시 자동으로 캐시 무효화:

```typescript
// lib/approval/approval-workflow.ts

export async function withApproval(...) {
  // ... 결재 상신 로직
  
  // 캐시 무효화
  await revalidateApprovalLogs();
  
  return result;
}

export async function executeApprovedAction(apInfId: string) {
  // ... 액션 실행 로직
  
  // 캐시 무효화 (백그라운드에서도 동작! ✨)
  await revalidateApprovalLogs();
  
  return result;
}

export async function handleRejectedAction(apInfId: string, reason?: string) {
  // ... 반려 처리 로직
  
  // 캐시 무효화
  await revalidateApprovalLogs();
}
```

## 동작 원리

```mermaid
sequenceDiagram
    participant BG as 백그라운드 프로세스<br/>(폴링 서비스)
    participant API as API 라우트<br/>/api/revalidate/approval
    participant Cache as Next.js 캐시
    participant Page as 결재 로그 페이지

    BG->>BG: 결재 승인 처리
    BG->>API: POST /api/revalidate/approval<br/>{ tags: ['approval-logs'] }
    API->>Cache: revalidateTag('approval-logs')
    Cache-->>Cache: 캐시 무효화 ✅
    
    Note over Page: 사용자가 페이지 접속
    Page->>Cache: 데이터 요청
    Cache->>Page: 최신 데이터 반환 🎉
```

## 사용 예시

### 예시 1: 새로운 결재 액션에 캐시 무효화 추가

```typescript
// lib/my-feature/approval-actions.ts
'use server';

import { withApproval } from '@/lib/approval';

export async function myActionWithApproval(data: MyData) {
  // withApproval이 자동으로 캐시 무효화를 처리합니다
  return await withApproval('my_action', data, {
    templateName: '나의 액션 결재',
    variables: { ... },
    currentUser: { ... }
  });
}
```

### 예시 2: 수동으로 캐시 무효화

```typescript
import { revalidateApprovalLogs } from '@/lib/approval';

// 필요한 시점에 수동으로 호출
await revalidateApprovalLogs();
```

### 예시 3: 여러 캐시 동시 무효화

```typescript
import { revalidateApprovalCache } from '@/lib/approval';

await revalidateApprovalCache([
  'approval-logs',
  'pending-actions',
  'my-custom-cache'
]);
```

## 캐시 태그 규칙

| 태그 이름 | 적용 대상 | 무효화 시점 |
|----------|---------|-----------|
| `approval-logs` | 결재 로그 목록 | 결재 상신/승인/반려 시 |
| `pending-actions` | 대기 중인 액션 목록 | 액션 실행/반려 시 |
| `approval-log-${apInfId}` | 특정 결재 상세 | 해당 결재 상태 변경 시 |
| `approval-templates` | 결재 템플릿 목록 | 템플릿 생성/수정/삭제 시 |

## 보안 (선택사항)

환경 변수로 시크릿 키를 설정하여 무단 접근 방지:

```env
# .env.local
REVALIDATION_SECRET=your-secret-key-here
```

```typescript
// lib/approval/cache-utils.ts
await fetch('/api/revalidate/approval', {
  method: 'POST',
  body: JSON.stringify({
    tags: ['approval-logs'],
    secret: process.env.REVALIDATION_SECRET
  })
});
```

## 장점

✅ **백그라운드 프로세스 지원**: 폴링 서비스에서도 캐시 무효화 가능  
✅ **공통 솔루션**: 모든 결재 관련 페이지에 자동 적용  
✅ **유연성**: 필요한 캐시만 선택적으로 무효화  
✅ **신뢰성**: API 호출 실패해도 60초 후 자동 재검증 (폴백)  
✅ **확장성**: 새로운 캐시 태그 추가 용이  

## 주의사항

⚠️ **과도한 무효화 방지**: 너무 자주 캐시를 무효화하면 성능 저하 발생  
⚠️ **에러 처리**: 캐시 무효화 실패는 치명적이지 않으므로 로그만 남기고 진행  
⚠️ **개발 환경**: 개발 환경에서는 캐싱이 비활성화될 수 있음  

## 트러블슈팅

### 문제: 캐시가 무효화되지 않음

1. API 라우트가 올바르게 호출되는지 확인:
   ```bash
   # 로그 확인
   [Approval Workflow] Revalidating cache after approval submission
   [Cache Revalidation] Tag revalidated: approval-logs
   ```

2. 캐시 태그가 올바르게 설정되었는지 확인:
   ```typescript
   // getApprovalLogList에 tags: ['approval-logs'] 있는지 확인
   ```

3. 환경 변수 확인:
   ```bash
   # NEXT_PUBLIC_BASE_URL이 올바르게 설정되어 있는지
   ```

### 문제: API 호출이 실패함

```typescript
// cache-utils.ts에서 에러 로그 확인
[Approval Cache] Failed to revalidate cache: Error: ...
```

원인:
- 네트워크 이슈
- 잘못된 BASE_URL
- 시크릿 키 불일치

해결: 로그를 확인하고 환경 변수를 점검하세요.

## 참고 자료

- [Next.js Caching Documentation](https://nextjs.org/docs/app/building-your-application/caching)
- [revalidateTag API Reference](https://nextjs.org/docs/app/api-reference/functions/revalidateTag)
- [unstable_cache API Reference](https://nextjs.org/docs/app/api-reference/functions/unstable_cache)