1. geneva_drive의 핵심 철학: 제네바 드라이브 메커니즘
‘geneva_drive’라는 명칭은 연속적인 회전 운동을 간헐적인 회전 운동으로 변환하여 영화 영사기에서 필름을 한 프레임씩 정확하게 전진시키는 기계 장치에서 유래했습니다. 소프트웨어 워크플로우 엔진으로서 geneva_drive의 목표 역시 이와 동일합니다. 끊임없이 흐르는 시간과 복잡한 비즈니스 로직을 통제 가능하고 이산적인 단계(Discrete Steps)로 변환하여, 시스템이 각 단계를 명확하게 인식하고 실행할 수 있도록 돕는 것입니다.
2. “Hero” 패턴을 통한 도메인 모델과의 긴밀한 결합
대부분의 워크플로우 엔진은 애플리케이션의 데이터 영역과 분리된 외부 시스템으로 작동하지만, geneva_drive는 ‘Hero’ 패턴을 통해 Rails의 도메인 모델과 직접 연결됩니다.
- 주인공(Hero) 지정: 모든 워크플로우는 특정 ActiveRecord 모델(예: Payment, User, Order)을 주인공으로 삼아 다형성 관계(Polymorphic Association)를 맺습니다.
- 안정적 식별과 제약: 데이터베이스 수준에서 {hero_type, hero_id, workflow_type} 조합을 통해 동일한 대상에 대해 중복된 워크플로우가 실행되는 것을 방지합니다.
- 상태 엔티티화: 단순한 작업 티켓인 ActiveJob과 달리, 워크플로우 자체가 상태를 가진 영속적인 엔티티로 존재하여 비즈니스 로직의 흐름을 명확히 추적할 수 있습니다.
3. Ruby 객체 모델을 활용한 선언적 DAG와 명령적 실행의 분리
저자는 Ruby의 싱글톤 클래스(Eigenclass)와 인스턴스 메서드의 차이를 활용하여 워크플로우 설계의 고질적인 문제인 ‘구조 정의와 실행 코드의 혼재’를 해결했습니다.
- 선언적 DAG (Class Body): 클래스 정의 시점에 실행되는 step 메서드 호출은 워크플로우의 전체 구조(DAG)를 정의합니다. 이는 로드 타임에 한 번 평가되며 선언적인 성격을 띱니다.
- 명령적 실행 (Instance Blocks): step에 전달된 블록은 실제 실행 시점에 워크플로우 인스턴스 문맥에서 수행됩니다. 이를 통해 각 단계는 hero에 접근하거나 pause!와 같은 흐름 제어 메서드를 자유롭게 사용할 수 있습니다.
4. ActiveRecord 기반 아키텍처의 실질적 이점
geneva_drive의 가장 큰 특징은 워크플로우 자체가 ActiveRecord 모델이라는 점입니다. 이는 다음과 같은 강력한 이점을 제공합니다. - 강력한 쿼리 가능성: 표준 SQL을 사용하여 특정 단계에서 지연된 워크플로우를 찾거나, 특정 사용자의 온보딩 완료 여부를 조회하는 등의 작업이 매우 간편합니다. - 트랜잭션 일관성: 비즈니스 로직과 워크플로우의 상태 업데이트를 동일한 데이터베이스 트랜잭션 내에서 처리할 수 있어, 분산 시스템에서 발생하는 최종 일관성(Eventual Consistency) 문제를 최소화합니다. - 인프라 단순화: Redis, Kafka, gRPC 등 추가적인 외부 의존성 없이 기존 Rails 스택만으로 작동하므로 운영 및 유지보수 비용이 획기적으로 절감됩니다.
5. 정교한 흐름 제어와 내구성 있는 스케줄링
워크플로우 내부에서는 reattempt!, pause!, skip!, cancel!, finish! 등의 메서드를 통해 실행 흐름을 제어합니다.
- Ruby throw 활용: 이러한 제어 메서드들은 내부적으로 Ruby의 throw 메커니즘을 사용하여 구현되었으며, 실행 엔진이 이를 캐치하여 적절한 상태 변경 및 재스케줄링을 수행합니다.
- 안전한 재시도: ActiveJob의 wait_until을 활용하되, 모든 단계 실행 전에 StepExecution 레코드를 DB에 먼저 기록합니다. 이는 작업 큐 저장소가 유실되는 최악의 상황에서도 DB 레코드를 통해 전체 워크플로우를 복구할 수 있게 합니다.
6. 효율적인 테스트와 시간 여행 기능
수일에 걸쳐 실행되는 장기 워크플로우를 검증하기 위해 speedrun_workflow 테스트 헬퍼를 제공합니다. 이는 ActiveSupport::TestCase의 시간 여행(Time Travel) 기능을 활용하여, 실제 대기 시간 없이도 워크플로우의 모든 단계와 지연 로직을 밀리초 단위로 신속하게 테스트할 수 있도록 지원합니다.