장기 작업을 위한 이벤트 기반 워크플로 vs 요청-응답 API
승인, 타이머, 재시도, 감사 기록을 중심으로 장기 실행 프로세스에서 이벤트 기반 워크플로와 요청-응답 API를 비교합니다.

장기 프로세스가 비즈니스 앱에서 왜 까다로운가
“장기 실행” 프로세스는 한 번의 빠른 단계로 끝나지 않을 때를 말합니다. 사람, 시간, 외부 시스템에 의존해 몇 분, 몇 시간, 또는 며칠이 걸릴 수 있습니다. 승인, 인수인계, 대기 같은 모든 것이 여기에 해당합니다.
이럴 때 단순한 요청-응답 API 사고방식은 흔들리기 시작합니다. API 호출은 짧은 교환을 위해 설계되어 있습니다: 요청을 보내고 답을 받고 다음으로 갑니다. 긴 작업은 장(chapter)이 있는 이야기처럼 더 복잡합니다. 일시 중지하고, 정확히 어디까지 진행됐는지 기억하고, 나중에 추측 없이 계속해야 합니다.
구매 승인에서 매니저와 재무의 승인, 직원 온보딩의 문서 확인 대기, 결제 공급자에 의존하는 환불, 검토 후 적용해야 하는 접근 권한 요청 같은 일상적인 비즈니스 앱에서 이런 상황을 자주 봅니다.
팀이 긴 프로세스를 단일 API 호출처럼 취급하면 예측 가능한 문제들이 나타납니다:
- 재시작이나 배포 후 앱이 상태를 잃어 신뢰성 있게 재개하지 못한다.
- 재시도로 인해 중복이 생긴다: 두 번째 결제, 두 번째 이메일, 중복 승인.
- 소유권이 흐려진다: 다음에 누가 행동해야 할지(요청자, 매니저, 시스템 작업) 불분명해진다.
- 지원팀이 어디에 막혔는지 로그를 샅샅이 뒤져야만 알아낼 수 있다.
- 타이머, 알림, 마감 같은 대기 로직이 취약한 백그라운드 스크립트로 흩어진다.
구체적 예: 직원이 소프트웨어 접근 권한을 요청합니다. 매니저는 빠르게 승인했지만 IT는 프로비저닝에 이틀이 필요합니다. 앱이 프로세스 상태를 유지하고 리마인더를 보내고 안전하게 재개할 수 없다면 수작업 추적, 혼란스러운 사용자 경험, 추가 작업이 발생합니다.
그래서 장기 비즈니스 프로세스에서는 이벤트 기반 워크플로와 요청-응답 API의 선택이 매우 중요합니다.
두 가지 사고 모델: 동기 호출 vs 시간에 걸친 이벤트
가장 단순한 비교는 한 질문으로 좁힐 수 있습니다: 작업이 사용자가 기다리는 동안 끝나는가, 아니면 사용자가 떠난 뒤에도 계속되는가?
요청-응답 API는 단일 교환입니다: 하나의 호출이 들어오고 하나의 응답이 나갑니다. 레코드 생성, 견적 계산, 재고 확인처럼 빠르고 예측 가능한 작업에 적합합니다. 서버가 작업을 하고 성공 또는 실패를 반환하면 상호작용은 끝납니다.
이벤트 기반 워크플로는 시간에 걸친 일련의 반응입니다. 어떤 일이 발생하면(주문 생성, 매니저 승인, 타이머 만료 등) 워크플로가 다음 단계로 이동합니다. 이 모델은 인수인계, 대기, 재시도, 알림이 포함된 작업에 적합합니다.
실무적 차이는 상태(state)에 있습니다.
요청-응답에서는 상태가 현재 요청과 응답이 전송될 때까지의 서버 메모리에 머무르는 경우가 많습니다. 이벤트 기반 워크플로에서는 프로세스가 나중에 재개될 수 있도록 상태를 저장(예: PostgreSQL)해야 합니다.
장애 처리 방식도 달라집니다. 요청-응답은 보통 오류를 반환하고 클라이언트에 재시도를 요청합니다. 워크플로는 실패를 기록하고 조건이 좋아지면 안전하게 재시도할 수 있습니다. 또한 각 단계를 이벤트로 기록해 이력을 재구성하기 쉬워집니다.
단순한 예: “지출 보고서 제출”은 동기 처리될 수 있습니다. “승인 받기, 3일 대기, 매니저에게 리마인드, 그다음 결제”는 동기 처리에 적합하지 않습니다.
승인: 사람의 결정 처리 방식 비교
승인은 장기 작업이 현실이 되는 지점입니다. 시스템 단계는 밀리초 안에 끝나지만 사람은 2분이나 2일 뒤에 응답할 수 있습니다. 핵심 설계 선택은 그 대기를 일시 중지된 프로세스로 모델링할지, 아니면 나중에 도착하는 새 메시지로 볼지입니다.
요청-응답 API에서는 승인이 종종 어색하게 처리됩니다:
- 차단(실용적이지 않음)
- 폴링(클라이언트가 "승인됐나?"를 반복해서 묻기)
- 콜백/웹훅(서버가 나중에 호출)
이 방법들은 작동할 수 있지만 사람의 시간과 API 시간을 잇기 위해 배관 작업이 추가됩니다.
이벤트 모델에서는 승인이 이야기를 읽는 것처럼 자연스럽습니다. 앱은 “ExpenseSubmitted(지출 제출됨)” 같은 것을 기록하고, 나중에 “ExpenseApproved(지출 승인됨)”나 “ExpenseRejected(지출 거부됨)”를 받습니다. 워크플로 엔진(또는 자체 상태 기계)은 다음 이벤트가 도착했을 때만 레코드를 앞으로 이동시킵니다. 이는 대부분의 사람들이 이미 비즈니스 단계를 이해하는 방식과 맞습니다: 제출, 검토, 결정.
여러 승인자와 에스컬레이션 규칙이 있을 때 복잡성은 빠르게 증가합니다. 매니저와 재무 둘 다 승인이 필요하지만 시니어 매니저가 이를 무시할 수 있게 하려면 규칙을 명확히 모델링해야 합니다. 그렇지 않으면 프로세스는 추론하기 어렵고 감사가 더 힘들어집니다.
확장 가능한 단순 승인 모델
실용적인 패턴은 하나의 “요청” 레코드를 유지하고 결정을 별도로 저장하는 것입니다. 이렇게 하면 핵심 로직을 다시 쓰지 않고도 많은 승인자를 지원할 수 있습니다.
몇 가지 핵심 데이터를 일급 레코드로 캡처하세요:
- 승인 요청 자체: 승인 대상과 현재 상태
- 개별 결정: 누가 결정했는지, 승인/거부, 타임스탬프, 이유
- 요구되는 승인자: 역할 또는 개인, 순서 규칙
- 결과 규칙: “누구나 하나면 됨”, “과반수”, “모두 필요”, “오버라이드 허용” 등
어떤 구현을 선택하든 누가 언제 왜 무엇을 승인했는지를 로그 한 줄이 아니라 데이터로 항상 저장하세요.
타이머와 대기: 리마인더, 마감, 에스컬레이션
대기는 장기 작업을 복잡하게 만드는 지점입니다. 사람들은 점심을 가고 캘린더는 빽빽해지며 “곧 연락드리겠습니다”가 “지금 누가 이걸 담당하죠?”로 변합니다. 이것이 이벤트 기반 워크플로와 요청-응답 API의 가장 분명한 차이점 중 하나입니다.
요청-응답 API에서는 시간이 어색합니다. HTTP 호출은 타임아웃이 있어 이틀 동안 요청을 열어둘 수 없습니다. 팀들은 보통 폴링, 데이터베이스를 스캔하는 별도의 스케줄 작업, 또는 기한이 지난 항목을 처리하는 수동 스크립트 같은 패턴을 사용합니다. 이 방식들은 작동할 수 있지만 대기 로직이 프로세스 밖에 존재합니다. 잡이 두 번 실행되었을 때나 리마인더 직전에 레코드가 변경된 경우 같은 코너 케이스는 쉽게 놓칩니다.
워크플로는 시간을 정상적인 단계로 취급합니다. 예를 들어: 24시간 기다리고 리마인더를 보내고, 총 48시간이 되면 다른 승인자에게 에스컬레이션하라고 말할 수 있습니다. 시스템이 상태를 유지하므로 마감 규칙이 별도의 cron + 쿼리 프로젝트에 숨지 않습니다.
간단한 승인 규칙 예시는 다음과 같습니다:
지출 보고서 제출 후 1일 기다립니다. 상태가 여전히 "Pending(대기)"이면 매니저에게 알림을 보냅니다. 2일 후에도 여전히 대기 상태라면 매니저의 리더에게 재할당하고 에스컬레이션을 기록합니다.
타이머가 발동했을 때 상황이 바뀌었을 때 어떻게 행동할지는 중요한 디테일입니다. 좋은 워크플로는 항상 행동하기 전에 현재 상태를 다시 확인합니다:
- 최신 상태를 로드한다
- 여전히 대기인지 확인한다
- 담당자가 여전히 유효한지 확인한다(팀 변경은 발생한다)
- 결정과 이유를 기록한다
중복 없이 재시도와 실패 복구
재시도는 제어할 수 없는 이유로 무언가 실패했을 때 하는 것입니다: 결제 게이트웨이 타임아웃, 이메일 제공자의 일시적 오류, 또는 앱이 단계 A를 저장한 뒤 단계 B 전에 크래시되는 경우 등. 위험은 단순합니다: 다시 시도했다가 행동을 두 번 실행할 수 있습니다.
요청-응답에서는 보통 클라이언트가 엔드포인트를 호출하고 기다리다가 명확한 성공을 받지 못하면 다시 시도합니다. 이를 안전하게 만들려면 서버가 반복 호출을 동일한 의도(intent)로 처리해야 합니다.
실용적 해결책은 멱등성 키입니다: 클라이언트가 pay:invoice-583:attempt-1 같은 고유 토큰을 보냅니다. 서버는 그 키의 결과를 저장하고 반복 요청에 대해 동일한 결과를 반환합니다. 이렇게 하면 이중 청구, 중복 티켓, 중복 승인 등을 방지할 수 있습니다.
이벤트 기반 워크플로는 다른 유형의 중복 위험이 있습니다. 이벤트는 종종 적어도 한 번(at-least-once) 배달되므로 중복이 나타날 수 있습니다. 소비자는 중복 제거가 필요합니다: 이벤트 ID(또는 invoice_id + step 같은 비즈니스 키)를 기록하고 반복을 무시하세요. 이것은 워크플로 오케스트레이션 패턴의 핵심 차이점입니다: 요청-응답은 호출의 안전한 재실행을 다루고, 이벤트는 메시지의 안전한 재실행을 다룹니다.
어떤 모델에서든 잘 작동하는 재시도 규칙 몇 가지:
- 백오프 사용(예: 10초, 30초, 2분)
- 최대 시도 횟수 설정
- 일시적 오류(재시도)와 영구적 오류(즉시 실패)를 구분
- 반복 실패는 “검토 필요” 상태로 라우팅
- 모든 시도를 로그에 남겨 나중에 설명할 수 있게 함
재시도는 숨겨진 동작이 아니라 프로세스에 명시적으로 포함되어야 합니다. 그래야 실패를 가시화하고 고칠 수 있습니다.
감사 추적: 프로세스를 설명 가능하게 만들기
감사 추적(audit trail)은 “왜”에 대한 파일입니다. 누군가가 “왜 이 지출이 거부되었나요?”라고 묻는다면 몇 달이 지나도 추측하지 않고 답할 수 있어야 합니다. 이벤트 기반 워크플로와 요청-응답 API 모두에서 중요하지만 작업 방식은 다릅니다.
장기 실행 프로세스에서는 다음과 같은 사실을 기록해 이야기를 재생할 수 있어야 합니다:
- 행위자: 누가 했는가(사용자, 서비스, 시스템 타이머)
- 시간: 언제 일어났는가(시간대 포함)
- 입력: 그때 알려진 것들(금액, 공급사, 정책 임계값, 승인 내역)
- 출력: 어떤 결정이나 동작이 일어났는가(승인, 거부, 지급, 재시도)
- 규칙 버전: 어떤 정책/로직 버전이 사용되었는가
이벤트 기반 워크플로는 각 단계가 자연스럽게 “ManagerApproved”나 “PaymentFailed” 같은 이벤트를 생성하므로 감사가 더 쉬울 수 있습니다. 이벤트를 페이로드와 행위자와 함께 저장하면 깔끔한 타임라인이 생깁니다. 핵심은 이벤트를 설명적으로 유지하고 케이스별로 조회할 수 있게 저장하는 것입니다.
요청-응답 API도 감사 가능하지만 기록이 여러 서비스에 흩어져 있어서 이야기를 재구성하기 어렵습니다. 하나의 엔드포인트는 "approved"를 기록하고, 다른 하나는 "payment requested"를, 또 다른 하나는 "retry succeeded"를 기록하면 서로 다른 포맷 때문에 감사는 탐정 작업이 됩니다.
간단한 해결책은 공유된 “케이스 ID”(또는 correlation ID)를 사용하는 것입니다. 프로세스 인스턴스에 붙이는 하나의 식별자(예: "EXP-2026-00173")를 모든 요청, 이벤트, DB 레코드에 부착하면 전체 여정을 추적할 수 있습니다.
올바른 접근 방식 선택하기: 장점과 트레이드오프
정답은 즉시 답이 필요한가, 아니면 프로세스가 몇 시간 또는 며칠 동안 계속 움직여야 하는가에 달려 있습니다.
요청-응답은 작업이 짧고 규칙이 단순할 때 잘 맞습니다. 사용자가 폼을 제출하면 서버가 검증하고 데이터를 저장한 뒤 성공 또는 오류를 반환하는 경우입니다. 생성, 업데이트, 권한 확인 같은 단일 단계 작업에 적합합니다.
하지만 "단일 요청"이 승인 대기, 여러 외부 시스템 호출, 타임아웃 처리, 이후 분기 같은 여러 단계로 조용히 변할 때 문제가 생깁니다. 연결을 열어두거나(취약) 대기와 재시도를 백그라운드 작업으로 밀어넣으면 이해하기 어려운 구조가 됩니다.
이벤트 기반 워크플로는 프로세스가 시간에 걸친 이야기일 때 빛을 발합니다. 각 단계는 새로운 이벤트(승인됨, 거부됨, 타이머 발동, 결제 실패)에 반응하고 다음을 결정합니다. 이렇게 하면 일시 중지, 재개, 재시도, 그리고 결정 이유의 명확한 추적이 쉬워집니다.
실제 트레이드오프:
- 단순성 vs 내구성: 요청-응답은 시작이 쉽고, 이벤트 기반은 지연이 길 때 안전하다.
- 디버깅 스타일: 요청-응답은 직선적이며, 워크플로는 여러 단계에 걸쳐 추적해야 한다.
- 도구와 습관: 이벤트는 좋은 로깅, 상관 ID, 명확한 상태 모델이 필요하다.
- 변경 관리: 워크플로는 진화하고 분기하는 경향이 있으며, 잘 모델링하면 이벤트 기반 설계가 새로운 경로를 더 잘 처리한다.
실용적 예: 관리자 승인 → 재무 검토 → 지급이 필요한 지출 보고서가 있고, 지급 실패 시 중복 지급 없이 재시도하고 싶다면 이벤트 기반이 자연스럽습니다. 반면 단순한 제출과 빠른 검사라면 요청-응답으로 충분한 경우가 많습니다.
단계별: 지연을 견디는 장기 프로세스 설계
장기 비즈니스 프로세스는 지루한 이유로 실패합니다: 브라우저 탭을 닫음, 서버 재시작, 승인이 3일간 방치, 결제 공급자 타임아웃 등. 어떤 모델을 선택하든 처음부터 그런 지연을 염두에 두고 설계하세요.
먼저 저장하고 재개할 수 있는 소수의 상태를 정의하세요. 데이터베이스에서 현재 상태를 가리킬 수 없다면 재개 가능한 워크플로를 갖고 있지 않은 것입니다.
간단한 설계 순서
- 경계 설정: 시작 트리거, 종료 조건, 몇 가지 핵심 상태 정의(대기 중인 승인, 승인됨, 거부됨, 만료됨, 완료됨).
- 이벤트와 결정에 이름 붙이기: 시간이 지나면서 무슨 일이 일어날 수 있는지 적으세요(Submitted, Approved, Rejected, TimerFired, RetryScheduled). 이벤트 이름은 과거형으로 유지하세요.
- 대기 지점 선택: 프로세스가 사람, 외부 시스템, 또는 마감일을 위해 일시 중지되는 지점을 식별하세요.
- 단계별 타이머와 재시도 규칙 추가: 시간이 지났거나 호출이 실패했을 때 무슨 일이 일어날지 결정하세요(백오프, 최대 시도, 에스컬레이션, 포기).
- 프로세스 재개 방식 정의: 각 이벤트나 콜백에서 저장된 상태를 로드하고 유효성을 확인한 뒤 다음 상태로 이동하세요.
재시작을 견디려면 안전하게 계속하는 데 필요한 최소 데이터를 영속화하세요. 추측 없이 다시 실행할 수 있을 만큼 저장합니다:
- 프로세스 인스턴스 ID와 현재 상태
- 누가 다음에 행동할 수 있는지(담당자/역할)와 그들이 내린 결정
- 마감일(due_at), 리마인더(remind_at)와 에스컬레이션 레벨
- 재시도 메타데이터(시도 횟수, 마지막 오류, next_retry_at)
- 부작용에 대한 멱등성 키 또는 “이미 완료됨” 플래그(메시지 전송, 카드 청구 등)
저장된 데이터로 “우리가 어디에 있는가”와 “다음에 무엇을 할 수 있는가”를 재구성할 수 있으면 지연은 더 이상 두렵지 않습니다.
흔한 실수와 회피 방법
장기 프로세스는 실제 사용자가 등장한 뒤에야 고장나는 경우가 많습니다. 승인이 이틀 걸리고 재시도가 잘못된 시간에 발동하며 이로 인해 이중 결제나 누락된 감사 기록이 발생합니다.
흔한 실수들:
- 사람 승인을 기다리며 HTTP 요청을 열어둠. 타임아웃이 나고 서버 자원이 묶이며 사용자는 "무언가 진행 중"이라는 잘못된 인상을 받음.
- 멱등성 없이 호출을 재시도함. 네트워크 문제로 중복 청구나 중복 이메일, 반복된 "승인" 전환이 발생함.
- 프로세스 상태를 저장하지 않음. 상태가 메모리에 있으면 재시작 시 사라지고, 로그에만 있으면 신뢰할 수 있는 재개가 불가능함.
- 흐릿한 감사 추적. 이벤트들이 서로 다른 시계와 포맷으로 기록되면 사고나 규정 준수 리뷰 시 타임라인을 신뢰할 수 없음.
- 비동기와 동기를 혼합하면서 단일 진실 소스가 없음. 한 시스템은 "Paid"라 하고 다른 시스템은 "Pending"이라면 어느 쪽이 맞는지 알 수 없음.
단순한 예: 채팅에서 지출 보고서가 승인되고 웹훅이 늦게 도착해 결제 API가 재시도됩니다. 상태를 저장하고 멱등성을 적용하지 않으면 재시도는 두 번 결제하는 결과를 초래하고 기록은 왜 그런 일이 벌어졌는지 명확히 설명하지 못합니다.
대부분의 해결책은 명확히 하는 것입니다:
- 상태 전환(Requested, Approved, Rejected, Paid)을 데이터베이스에 누가/무엇이 변경했는지와 함께 영속화하세요.
- 결제, 이메일, 티켓 생성 같은 외부 부작용에는 멱등성 키를 사용하고 결과를 저장하세요.
- "요청을 수락"하는 것과 "작업을 완료"하는 것을 분리하세요: 빠르게 응답을 반환하고 워크플로를 백그라운드에서 완료하세요.
- 타임스탬프는 UTC로 표준화하고, 상관 ID를 추가하며 요청과 결과 둘 다 기록하세요.
빌드 전에 확인할 체크리스트
장기 작업은 완벽한 한 번의 호출이 아니라 지연, 사람, 실패 후에도 정합성을 유지하는 것이 중요합니다.
프로세스가 재시작 후 어떻게 재개되는지 명확히 정의하세요. 어떤 상태가 저장되고 다음에 무엇이 실행되는가?
실용적 체크리스트:
- 충돌이나 배포 후 프로세스가 어떻게 재개되는지 정의하세요. 어떤 상태가 저장되고 다음에 무엇이 실행될지?
- 각 인스턴스에 고유한 프로세스 키(예: ExpenseRequest-10482)와 명확한 상태 모델(Submitted, Waiting for Manager, Approved, Paid, Failed)을 부여하세요.
- 승인은 단순 결과가 아니라 레코드로 취급하세요: 누가 승인/거부했는지, 언제 했는지, 이유 또는 코멘트를 저장하세요.
- 리마인더, 마감, 에스컬레이션, 만료 같은 대기 규칙을 맵핑하고 각 타이머의 소유자(매니저, 재무, 시스템)를 지정하세요.
- 실패 처리 계획: 재시도는 제한적이고 안전해야 하며 사람이 데이터를 수정하거나 재시도를 승인할 수 있는 "검토 필요" 정지가 있어야 합니다.
건전성 테스트: 이미 카드가 청구된 뒤 결제 공급자가 타임아웃을 반환하는 상황을 상상하세요. 설계는 중복 청구를 방지하면서도 프로세스를 완료할 수 있어야 합니다.
예시: 마감과 지급 재시도가 있는 지출 승인
시나리오: 직원이 택시 영수증 $120을 환급을 위해 제출합니다. 48시간 내에 매니저 승인이 필요합니다. 승인되면 시스템이 직원에게 지급합니다. 지급이 실패하면 안전하게 재시도하고 명확한 기록을 남깁니다.
요청-응답 시나리오
요청-응답 API에서는 앱이 종종 계속 상태를 확인해야 하는 대화처럼 동작합니다.
직원이 제출 버튼을 누르면 서버는 상태가 "Pending approval"인 환급 레코드를 생성하고 ID를 반환합니다. 매니저는 알림을 받고 직원 앱은 보통 변경 사항을 확인하기 위해 폴링합니다(예: "GET reimbursement status by ID").
48시간 마감을 강제하려면 지연된 요청을 검사하는 스케줄 작업을 돌리거나 마감 타임스탬프를 저장하고 폴링할 때 확인해야 합니다. 작업이 지연되면 사용자는 오래된 상태를 볼 수 있습니다.
매니저가 승인하면 서버는 상태를 "Approved"로 바꾸고 결제 공급자에게 호출합니다. Stripe가 일시적 오류를 반환하면 서버는 지금 재시도할지, 나중에 재시도할지, 실패로 처리할지 결정해야 합니다. 멱등성 키가 없으면 재시도로 이중 지급이 일어날 수 있습니다.
이벤트 기반 시나리오
이벤트 기반 모델에서는 각 변경이 사실로 기록됩니다.
직원이 제출하면 "ExpenseSubmitted" 이벤트가 생성됩니다. 워크플로가 시작되어 "ManagerApproved" 이벤트나 48시간의 "DeadlineReached" 타이머 이벤트를 기다립니다. 타이머가 먼저 발동하면 워크플로는 "AutoRejected" 결과와 이유를 기록합니다.
승인되면 워크플로는 "PayoutRequested"를 기록하고 지급을 시도합니다. Stripe가 타임아웃을 내면 "PayoutFailed"와 오류 코드를 기록하고 재시도(예: 15분 후)를 예약하며, 멱등성 키를 사용해 한 번만 "PayoutSucceeded"를 기록합니다.
사용자가 보는 상태는 단순합니다:
- 승인 대기 중(48시간 남음)
- 승인됨, 지급 진행 중
- 지급 재시도 예약됨
- 지급 완료
감사 추적은 타임라인처럼 읽힙니다: 제출됨, 승인됨, 마감 확인됨, 지급 시도됨, 실패, 재시도됨, 지급 완료.
다음 단계: 모델을 작동하는 앱으로 바꾸기
하나의 실제 프로세스를 골라 끝까지 빌드한 뒤 일반화하세요. 지출 승인, 온보딩, 환불 처리는 사람 단계, 대기, 실패 경로를 포함하므로 시작하기 좋습니다. 목표는 작게 유지하세요: 하나의 정상 경로와 가장 흔한 두 가지 예외를 구현하세요.
프로세스를 화면이 아니라 상태와 이벤트로 작성하세요. 예: "Submitted" -> "ManagerApproved" -> "PaymentRequested" -> "Paid", 분기점은 "ApprovalRejected"나 "PaymentFailed" 같은 것들입니다. 대기 지점과 부작용이 명확해지면 이벤트 기반 워크플로와 요청-응답 API 중 어느 쪽이 실용적인지 결정하기 쉬워집니다.
프로세스 상태가 어디에 저장될지 결정하세요. 흐름이 단순하고 한 곳에서 업데이트를 강제할 수 있다면 DB로 충분할 수 있습니다. 타이머, 재시도, 분기가 필요하면 워크플로 엔진이 다음에 무엇이 일어날지 추적해 주어 더 편합니다.
처음부터 감사 필드를 추가하세요. 누가 무엇을 언제 왜(코멘트나 이유 코드) 했는지 저장하세요. 누군가가 "왜 이 결제가 재시도되었나?"라고 물었을 때 로그를 뒤질 필요 없이 명확한 답을 줄 수 있어야 합니다.
노코드 플랫폼에서 이런 워크플로를 구현한다면 AppMaster (appmaster.io)는 PostgreSQL에 데이터를 모델링하고 시각적으로 프로세스 로직을 구성할 수 있는 옵션 중 하나입니다. 이렇게 하면 웹과 모바일 앱 전반에서 승인과 감사 추적을 일관되게 유지하기 쉽습니다.
자주 묻는 질문
작업이 사용자가 기다리는 동안 빠르고 예측 가능하게 끝난다면 request-response를 사용하세요. 예를 들어 레코드 생성이나 폼 검증처럼 즉시 답을 줄 수 있는 경우입니다. 반면 프로세스가 몇 분에서 며칠에 걸치고 사람 승인, 타이머, 재시도, 재시작 이후 안전한 재개가 필요하면 이벤트 기반 워크플로를 사용하세요.
장기 작업은 한 번의 HTTP 요청에 맞지 않습니다. 연결이 타임아웃되고 서버가 재시작되며 작업은 종종 사람이나 외부 시스템에 의존합니다. 이를 하나의 호출로 처리하면 상태를 잃고, 재시도 시 중복이 발생하며, 대기 처리를 위한 별도 백그라운드 스크립트가 여기저기 흩어지게 됩니다.
기본값으로는 프로세스 상태를 데이터베이스에 명확히 저장하고 오직 명시적 전환을 통해만 진행하도록 하세요. 프로세스 인스턴스 ID, 현재 상태, 누가 다음에 행동할 수 있는지, 주요 타임스탬프를 저장하면 배포나 크래시 후에도 안전하게 재개할 수 있습니다.
승인은 차단하거나 반복 폴링하는 대신 결정이 도착하면 재개되는 일시 정지 단계로 모델링하세요. 누가 언제 승인/거부했는지(이유 포함)를 데이터로 기록하면 워크플로가 예측 가능하게 진행되고 이후 감사도 쉽습니다.
폴링은 단순한 경우엔 작동하지만 클라이언트가 계속 “끝났나?”를 물어보게 하여 소음과 지연이 생깁니다. 기본적으로 상태 변경 시 푸시 알림을 보내고 클라이언트는 필요할 때 갱신하도록 하는 편이 낫습니다. 서버가 상태의 단일 출처가 되어야 합니다.
타임을 프로세스의 일부로 취급하세요. 마감일과 리마인더 시간을 저장하고 타이머가 발동할 때 현재 상태를 다시 확인한 뒤 행동합니다. 이렇게 하면 이미 승인이 된 뒤에 리마인더가 가는 일을 막고, 잡이 늦게 또는 두 번 실행되더라도 일관성을 유지합니다.
결제나 이메일 같은 외부 영향(side effect)에 대해서는 반드시 멱등성 키(idempotency key)를 사용하고 그 결과를 저장하세요. 그러면 재시도해도 같은 의도가 반복되는 것이지 동작이 중복 실행되지는 않습니다.
메시지는 한 번 이상 전달될 수 있다고 가정하고 소비자가 중복을 제거하도록 설계하세요. 이벤트 ID(또는 단계별 비즈니스 키)를 저장해 반복 수신을 무시하면 재재생(replay)이 같은 행동을 두 번 트리거하지 않습니다.
타임라인 상의 사실들을 기록하세요: 행위자(actor), 타임스탬프, 당시 입력값, 결과, 사용된 정책/규칙 버전. 또한 프로세스 전체에 하나의 케이스 ID(상관 ID)를 붙여 지원팀이 관련 로그를 쉽게 추적할 수 있게 하세요.
하나의 요청 기록을 ‘케이스’로 두고 결정들은 별도로 저장하세요. 상태 변경은 재생 가능한(persisted) 전환을 통해 이루어지게 하며, 필요하면 no-code 도구인 AppMaster에서 PostgreSQL로 데이터를 모델링하고 시각적으로 단계 로직을 구현하면 승인, 재시도, 감사 필드를 앱 전반에 걸쳐 일관되게 유지하기 쉽습니다.


