레일웨이 지향 프로그래밍: 코드를 제자리로 돌려놓는 방법

Railway Oriented Programming, Getting Us Back On Track - Ryan Bigg

작성자
Ruby Australia
발행일
2025년 09월 12일

핵심 요약

  • 1 레일웨이 지향 프로그래밍(ROP)은 코드 실행 경로를 성공(happy path)과 실패(sad path)로 명확히 분리하여 복잡한 비즈니스 로직의 가독성과 유지보수성을 극대화합니다.
  • 2 `dry-monads` 및 `dry-operation` Gem을 활용하여 컨트롤러와 모델의 역할을 재정의하고, 비즈니스 로직을 `Operation` 클래스에 집중시켜 코드의 응집도를 높이고 테스트 용이성을 향상시킵니다.
  • 3 기존 Rails 애플리케이션의 복잡하고 얽힌 코드를 단계별 `Operation`으로 분리하여 점진적으로 리팩토링함으로써 대규모 시스템에서도 구조적 명확성과 개발자 경험을 개선할 수 있습니다.

도입

본 발표는 레일웨이 지향 프로그래밍(Railway Oriented Programming, ROP)이 어떻게 코드를 '제자리로 돌려놓는' 데 도움을 주는지 탐구합니다. 발표자는 ROP의 개념을 소개하기에 앞서, 강연 준비 과정에서 겪었던 일련의 실패 경험들을 메타 스토리텔링 방식으로 공유합니다. Mermaid 다이어그램 생성, 투명 배경 처리, CLI 도구 설치 및 버전 문제 등 다양한 기술적 난관을 극복하는 과정은 본질적으로 코드가 '실패 경로(sad path)'를 만났을 때의 상황을 비유적으로 보여주며, ROP의 필요성을 간접적으로 시사합니다.

레일웨이 지향 프로그래밍(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-monadsdo 표기법과 dry-operation Gem

dry-monadsdo 표기법은 yield를 사용하여 각 단계를 순차적으로 실행하고, yieldFailure를 만나면 즉시 전체 메서드를 종료시키는 방식으로 코드를 간결하게 만듭니다. 여기서 더 나아가 dry-operation Gem은 dry-monads를 기반으로 Operation 클래스 구현을 더욱 단순화합니다. DryOperation을 상속받고 step 메서드를 사용하여 각 단계를 정의하면, yield 대신 step을 통해 성공/실패를 처리하며 조기 종료 기능을 활용할 수 있습니다. 또한 dry-operation은 Active Record 트랜잭션 래핑 및 실패 이벤트 구독 기능을 제공하여 데이터베이스 롤백과 오류 로깅을 용이하게 합니다.

질의응답 요약

  • dry 외 다른 구현 방식: dry-monads 외에도 직접 구현하거나 다른 접근 방식을 사용할 수 있습니다.
  • 오류 메시지 처리: Operation에서 반환된 오류(문자열 또는 심볼)는 컨트롤러 계층에서 UI에 표시될 메시지로 변환됩니다.
  • Sorbet 호환성: yield 구문과 Sorbet의 타입 체크 간에 충돌이 발생할 수 있으나, dry-operationstep 방식은 이 문제를 해결하는 데 도움이 될 수 있습니다.
  • 모든 호출의 성공/실패 반환: step으로 정의된 모든 메서드는 Success 또는 Failure를 반환해야 합니다.
  • 오류 디버깅: dry-operation의 실패 이벤트 구독 기능을 통해 실패 발생 시 로깅 및 스택 트레이스를 확보하여 디버깅에 활용할 수 있습니다.
  • 예외 처리와의 공존: 예상되는 예외는 Failure로 처리하고, 예상치 못한 예외는 최상단으로 버블링하여 로깅 시스템에서 처리하는 것이 좋습니다.

결론

레일웨이 지향 프로그래밍과 `dry-operation` Gem은 복잡한 애플리케이션 코드를 단순화하고, 책임 영역을 명확히 구분하며, 테스트 용이성을 극대화하는 강력한 방법론입니다. 이는 Rails와 같은 프레임워크가 담당하는 부분과 애플리케이션의 핵심 비즈니스 로직이 수행하는 역할을 명확하게 분리하는 데 기여합니다. 발표자는 대규모 리팩토링보다는 작은 `Operation`부터 점진적으로 구축하고 결합해나가는 방식을 권장하며, 이를 통해 코드의 품질과 개발자 경험을 크게 개선할 수 있음을 강조합니다. `dry-operation`의 GitHub 저장소와 문서를 참고하여 이 패턴을 실제 프로젝트에 적용해볼 것을 독려합니다.

댓글 0

댓글 작성

0/1000
정중하고 건설적인 댓글을 작성해 주세요.

아직 댓글이 없습니다

첫 번째 댓글을 작성해보세요!