Dry-Transaction을 활용한 Ruby 애플리케이션의 명령 패턴 및 오류 처리 개선

RubyConf 2021 - Service Objects With Dry.rb: Monads and Transactions by Paul Sadauskas

작성자
jeff
발행일
2022년 08월 10일

핵심 요약

  • 1 Dry-Transaction은 명령 패턴을 구현하고 비즈니스 로직을 명확한 단계로 분리하여 코드 가독성과 유지보수성을 향상시킵니다.
  • 2 Dry-Monads의 Maybe 및 Result 모나드는 nil 및 오류 처리를 선언적이고 안전한 방식으로 수행하여 런타임 예외를 줄입니다.
  • 3 Dry-Transaction은 강력한 오류 매칭 DSL과 스텝 오버라이딩 기능을 통해 예측 가능한 오류 처리와 쉬운 테스트를 가능하게 합니다.

도입

본 발표는 초기 Rails 애플리케이션에서 비즈니스 로직을 어디에 두어야 할지에 대한 고민에서 시작하여, 서비스 객체와 디자인 패턴의 중요성을 강조합니다. 특히 GoF(Gang of Four)나 Martin Fowler의 'Patterns of Enterprise Application Architecture'와 같은 고전적인 패턴들이 Ruby 언어의 유연성에 맞춰 어떻게 적용될 수 있는지 설명합니다. 이 강연은 Dry-Transaction이라는 강력한 Ruby 젬을 소개하며, 이를 통해 명령 패턴을 구현하고 코드의 구조와 오류 처리 방식을 혁신하는 방법을 제시합니다. Refactoring.Guru와 같은 유용한 자료를 언급하며, 개발자들이 더 나은 코드 디자인을 탐색하도록 독려합니다.

초기 Rails 개발에서 비즈니스 로직은 주로 Fat Model이나 Fat Controller에 집중되었으나, 이는 유지보수하기 어려운 코드를 초래했습니다. Dry-Transaction은 이러한 문제를 해결하기 위해 명령 패턴(Command Pattern)의 효과적인 구현을 제공합니다. 이는 단일 책임 원칙에 따라 비즈니스 로직을 명확하고 개별적인 단계(step)로 분리합니다. 각 단계는 순차적으로 실행되며, 이전 단계의 출력이 다음 단계의 입력으로 전달됩니다. 특히, 특정 단계에서 오류가 발생하면 즉시 트랜잭션이 중단되고 해당 오류가 반환되어, 복잡한 if-else 조건문이나 예외 처리(rescue) 없이 선형적인 코드 흐름을 유지할 수 있습니다. Dry-Transaction은 Dry-RB 생태계의 일부로, Dry-Monads, ROM, Hanami와 같은 다른 젬들과 연동하여 더욱 강력한 기능을 제공합니다. 이 젬들은 유연하고 동적인 Ruby 언어의 특성을 활용하여 C++나 Java와 같은 정적 언어에서 유래한 패턴들을 Ruby에 맞게 재해석합니다. Dry-Transaction의 핵심 개념은 ‘비즈니스 트랜잭션’으로, 데이터베이스 트랜잭션과는 달리 여러 객체에 걸쳐 비즈니스 로직을 캡슐화하고 오류 처리를 최우선 과제로 다룹니다. 이는 명확한 DSL(Domain Specific Language)을 통해 단계를 선언하고, 상태를 가지지 않으며, 동일한 입력에 대해 항상 동일한 출력을 보장합니다. ### 모나드(Monads)와 Dry-Monads Dry-Transaction의 기반에는 함수형 프로그래밍에서 차용한 모나드 개념이 있습니다. 특히 Maybe 모나드와 Result 모나드가 중요하게 사용됩니다. * Maybe 모나드: nil 값을 안전하게 처리하기 위해 Some (값이 있을 때) 또는 None (값이 없을 때)으로 감싸는 역할을 합니다. 이는 undefined method for nil과 같은 런타임 오류를 방지하며, bind, fmap, value_or와 같은 메서드를 통해 안전하게 값을 조작할 수 있도록 합니다. Ruby 2.7의 패턴 매칭을 지원하여 코드의 가독성을 높입니다. * Result 모나드: Maybe 모나드와 유사하지만, Success (성공 값) 또는 Failure (실패 값 및 오류 상세 정보)를 반환하여 오류 발생 시 더 구체적인 정보를 제공합니다. 이를 통해 오류의 원인에 따라 다른 방식으로 처리할 수 있습니다. ### 트랜잭션 스텝 및 어댑터 Dry-Transaction은 step DSL을 통해 각 단계를 정의하며, 각 단계는 Result 모나드를 반환해야 합니다. 이를 편리하게 사용하기 위해 다양한 스텝 어댑터를 제공합니다. * try: 특정 예외를 포착하여 Failure를 반환하고, 다른 예외는 전파합니다. * map: 메서드의 반환 값을 Success로 감쌉니다. * check: 메서드의 반환 값이 truthy인지 falsey인지 확인하여 Success 또는 Failure를 반환합니다. * t: 부수 효과(side effect)를 위한 단계로, 메서드의 반환 값을 무시하고 원래 입력을 Success로 반환합니다. 이러한 어댑터들을 통해 개발자는 if 조건문, rescue, 그리고 이른 반환(early return) 없이도 선형적이고 기능적인 코드 스타일을 유지할 수 있습니다. 각 단계는 독립적으로 작동하며, 고수준에서는 단계 이름만으로 트랜잭션의 전체 흐름을 파악할 수 있습니다. ### 오류 처리 Dry-Transaction의 가장 큰 장점 중 하나는 강력한 오류 처리 메커니즘입니다. call 메서드는 선택적으로 블록을 받아 Resultsuccess 또는 failure를 패턴 매칭할 수 있습니다. failure의 경우, 실패한 단계의 이름에 따라 다른 로직을 실행할 수 있어 특정 오류 상황에 대한 맞춤형 응답이 가능합니다. 예를 들어, 토큰 디코딩 실패와 사용자 미발견 실패를 다르게 처리할 수 있습니다. 또한, Dry-Schema 및 Dry-Validation 젬을 활용하여 트랜잭션 입력 값에 대한 세밀한 유효성 검사를 수행할 수 있습니다. Dry-Transaction의 매처(matcher) DSL은 엄격하여 모든 가능한 Result 값을 처리하지 않으면 NonExhaustiveMatchError를 발생시켜, 프로덕션 환경으로 오류가 유출되는 것을 방지합니다. ### 테스트 용이성 Dry-Transaction은 테스트를 매우 용이하게 만듭니다. 각 단계가 클래스 수준에서 정의되므로, 런타임에 특정 단계의 구현을 오버라이딩할 수 있습니다. 이는 특히 외부 서비스와의 연동을 테스트할 때 유용하며, Mock이나 VCR 없이도 타임아웃이나 데이터베이스 오류와 같은 특정 시나리오를 쉽게 시뮬레이션할 수 있습니다. 개발자는 특정 단계에 람다 함수를 주입하여 SuccessFailure를 강제로 반환하게 함으로써 트랜잭션의 동작을 직접 검증할 수 있습니다. ### 커스텀 스텝 어댑터 TextUs와 같은 실제 애플리케이션에서는 merge, validate, use, maybe, async와 같은 커스텀 스텝 어댑터를 개발하여 Dry-Transaction의 기능을 확장했습니다. merge 스텝은 키워드 인수를 병합하고, validate 스텝은 Dry-Validation DSL을 인라인으로 정의하며, use 스텝은 다른 트랜잭션을 호출하고, maybe 스텝은 유효성 검사 실패 시 트랜잭션 실행을 건너뛰며, async 스텝은 Active Job을 사용하여 트랜잭션을 백그라운드 작업으로 비동기 실행합니다. 이러한 커스텀 어댑터들은 반복적인 코드를 줄이고 특정 비즈니스 요구사항에 맞게 트랜잭션 동작을 최적화합니다.

결론

Dry-Transaction은 Ruby 애플리케이션에서 비즈니스 로직을 구조화하고 오류를 처리하는 강력하고 우아한 방법을 제공합니다. 명령 패턴, 모나드, 그리고 유연한 스텝 어댑터를 통해 개발자들은 `if` 조건문, `rescue`, 그리고 이른 반환과 같은 절차적 코드의 복잡성에서 벗어나 선형적이고 예측 가능한 코드 흐름을 구현할 수 있습니다. 이는 코드의 가독성을 높이고, 유지보수를 용이하게 하며, 특히 외부 서비스 연동과 같은 복잡한 시나리오에서 테스트를 단순화하는 데 크게 기여합니다. Dry-Transaction은 함수형 프로그래밍의 이점을 Ruby에 도입하여, 견고하고 확장 가능한 애플리케이션을 구축하는 데 필수적인 도구임을 입증합니다. 본 강연은 Dry-Transaction의 핵심 개념과 실제 적용 사례를 통해 개발자들이 함수형 프로그래밍과 모나드에 대한 더 깊은 관심을 갖도록 독려합니다.

댓글 0

로그인이 필요합니다

댓글을 작성하거나 대화에 참여하려면 로그인이 필요합니다.

로그인 하러 가기

아직 댓글이 없습니다

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