레일웨이 지향 프로그래밍(ROP)의 핵심
레일웨이 지향 프로그래밍은 코드 실행 경로를 성공(happy path)과 실패(sad path)의 두 가지 트랙으로 명확하게 분리하는 방법론입니다. 이는 마치 기차가 성공 트랙을 달리다가 문제가 발생하면 즉시 실패 트랙으로 전환하여 더 이상 불필요한 단계를 수행하지 않는 것과 유사합니다. 발표자는 상인 인증(merchant authentication) 예시를 통해, 상인 존재 여부, 활성화 상태, 비밀번호 일치 여부 등 여러 검증 단계 중 어느 하나라도 실패하면 즉시 실패 경로로 이동하는 과정을 설명합니다.
Rails 개발 방식의 진화와 문제점
과거 ‘Rails Way’는 ‘Skinny Models, Fat Controllers’를 강조했으나, 이는 컨트롤러에 과도한 비즈니스 로직을 집중시키고, 결국 모델이 비대해지는 결과를 초래했습니다. 이로 인해 코드의 가독성과 유지보수성이 저하되고, 테스트가 어려워지는 문제가 발생했습니다. unless 구문을 활용한 조기 반환(early return) 방식은 가독성을 일부 개선했지만, 여전히 비즈니스 로직이 컨트롤러나 모델에 분산되는 경향이 있었습니다.
Operation 클래스를 통한 책임 분리
발표자는 비즈니스 로직을 app/operations 디렉토리 내의 Operation 클래스로 분리하는 것을 제안합니다. 이는 ‘트랜잭션’이나 ‘서비스’와 같은 다른 명칭보다 ‘오퍼레이션’이 더 직관적이며, Ruby 언어의 맥락에 더 잘 맞는다고 설명합니다. Operation 클래스는 dry-monads Gem의 Result 기능을 활용하여 성공(Success) 또는 실패(Failure) 객체를 반환함으로써 코드의 결과를 명확하게 나타내고, 컨트롤러는 Ruby 3.0의 case-within 패턴 매칭 기능을 사용하여 이 결과를 깔끔하게 처리할 수 있습니다. 이 방식은 컨트롤러를 단순화하고, 비즈니스 로직을 중앙 집중화하여 테스트 용이성을 크게 향상시킵니다.
대규모 애플리케이션에서의 ROP 적용
발표자는 FatZebra의 결제 처리 시스템과 같은 대규모 코드베이스(수백~수천 라인)에서도 ROP가 효과적임을 강조합니다. 복잡한 결제 로직을 ‘입력 유효성 검사’, ‘카드 조회’, ‘사기 방지 검사’, ‘은행 전송’ 등 6가지 주요 단계로 나누고, 각 단계를 작은 Operation으로 구현하면, 어느 단계에서든 실패 시 즉시 처리 과정을 중단하여 불필요한 리소스 소모를 막을 수 있습니다.
dry-monads의 do 표기법과 dry-operation Gem
dry-monads의 do 표기법은 yield를 사용하여 각 단계를 순차적으로 실행하고, yield가 Failure를 만나면 즉시 전체 메서드를 종료시키는 방식으로 코드를 간결하게 만듭니다. 여기서 더 나아가 dry-operation Gem은 dry-monads를 기반으로 Operation 클래스 구현을 더욱 단순화합니다. DryOperation을 상속받고 step 메서드를 사용하여 각 단계를 정의하면, yield 대신 step을 통해 성공/실패를 처리하며 조기 종료 기능을 활용할 수 있습니다. 또한 dry-operation은 Active Record 트랜잭션 래핑 및 실패 이벤트 구독 기능을 제공하여 데이터베이스 롤백과 오류 로깅을 용이하게 합니다.
질의응답 요약
dry외 다른 구현 방식:dry-monads외에도 직접 구현하거나 다른 접근 방식을 사용할 수 있습니다.- 오류 메시지 처리:
Operation에서 반환된 오류(문자열 또는 심볼)는 컨트롤러 계층에서 UI에 표시될 메시지로 변환됩니다. - Sorbet 호환성:
yield구문과 Sorbet의 타입 체크 간에 충돌이 발생할 수 있으나,dry-operation의step방식은 이 문제를 해결하는 데 도움이 될 수 있습니다. - 모든 호출의 성공/실패 반환:
step으로 정의된 모든 메서드는Success또는Failure를 반환해야 합니다. - 오류 디버깅:
dry-operation의 실패 이벤트 구독 기능을 통해 실패 발생 시 로깅 및 스택 트레이스를 확보하여 디버깅에 활용할 수 있습니다. - 예외 처리와의 공존: 예상되는 예외는
Failure로 처리하고, 예상치 못한 예외는 최상단으로 버블링하여 로깅 시스템에서 처리하는 것이 좋습니다.