씬(Thin) 컨트롤러의 필요성 및 문제점
Rails 컨트롤러에 비즈니스 로직이 포함될 경우 다음과 같은 문제점이 발생합니다. * 테스트의 어려움: HTTP 요청/응답과 비즈니스 로직이 섞여 있어 단위 테스트가 복잡해집니다. * 이해하기 어려움: 코드의 응집도가 낮아지고, 특정 로직의 흐름을 파악하기 어렵습니다. * 변경의 어려움: 작은 변경이 예상치 못한 부작용을 일으킬 수 있으며, 로직 재사용이 어렵습니다.
컨트롤러는 오직 HTTP 요청을 받고 응답을 반환하는 역할에 집중하고, 모든 비즈니스 로직은 서비스 객체나 유스케이스(Use Case)와 같은 외부 계층에서 처리되어야 합니다.
dry-monads
활용
dry-monads
는 Success
와 Failure
와 같은 특별한 객체를 사용하여 오류를 명시적으로 처리하고 작업을 연결하는 깔끔한 방법을 제공합니다.
* if/else
문이나 nil
반환 대신 항상 의미 있는 결과를 반환하여 코드의 예측 가능성과 가독성을 높입니다.
* yield
키워드를 통해 각 단계의 결과를 전달하며, 중간에 Failure
가 발생하면 즉시 실행을 중단하고 Failure
를 반환합니다. 이를 통해 복잡한 오류 처리 로직 없이도 깔끔한 흐름 제어가 가능합니다.
```ruby class CreateUserService include Dry::Monads[:result, :do]
def call(params) user = yield validate(params) result = yield save_user(user) Success(result) end
private def validate(params) if params[:email].nil? Failure(“Email can’t be blank”) else Success(params[:email]) end end
def save_user(email) user = User.create(email: email) user.persisted? ? Success(user) : Failure(“User creation failed”) end end ```
dry-transaction
활용
dry-transaction
은 비즈니스 로직을 일련의 step
으로 구성하여 정의하는 간단한 방법을 제공합니다.
* 각 step
은 dry-monads
의 Result
객체(Success
또는 Failure
)를 반환합니다.
* 한 step
에서 Failure
가 발생하면 전체 트랜잭션의 실행이 자동으로 중단되고 해당 Failure
결과가 반환됩니다.
* 이를 통해 오류 처리 로직이 비즈니스 로직과 섞이지 않고 깔끔하게 분리됩니다.
```ruby class CreateUser include Dry::Transaction include Dry::Monads[:result]
step :validate step :persist
def validate(input) return Failure(:invalid) if input[:name].blank? Success(input) end
def persist(input) user = User.create(input) Success(user) end end ```
dry-matcher
를 통한 컨트롤러의 결과 처리
dry-matcher
를 사용하면 컨트롤러에서 서비스의 Success
또는 Failure
결과를 on.success
및 on.failure
블록을 통해 깔끔하게 패턴 매칭하여 처리할 수 있습니다.
ruby
MyService.call(params) do |on|
on.success do |presenter|
render json: presenter
end
on.failure do |result|
render json: result, status: 400
end
end
서비스 호출 및 공통 설정
- 서비스 객체는 일반적으로
MyService.new.call
형태로 호출됩니다.dry-transaction
을 사용하면.call
메소드가 자동으로 정의됩니다. include Dry::Transaction
및include Dry::Monads[:result]
와 같은 반복적인 코드를 피하기 위해, 이들을 포함하는 기본 서비스 클래스를 생성하고 모든 서비스가 이를 상속받도록 할 수 있습니다.dry-rails
젬은dry-monads
와dry-transaction
을 포함한 여러dry
관련 젬을 한 번에 설치하여dry
생태계 전체를 Rails 프로젝트에 통합할 수 있도록 돕습니다. 필요한 경우 개별 젬만 설치할 수도 있습니다.