새롭고 흥미로운 루비 젬(gem)을 찾고 계신가요?
그렇다면 dry-rb를 한번 살펴보세요. dry-rb는 개발에서 자주 마주치는 문제들에 대한 해결책을 제공하는 젬 모음입니다. 18개 이상의 루비 젬으로 구성되어 있으며, 각 젬은 서로 함께 사용할 수도 있고 독립적으로 사용할 수도 있습니다.
대표적인 젬은 다음과 같습니다:
- dry-initializer
- dry-struct
- dry-validations
- dry-events
- dry-transaction
이 글에서는 그중 3가지 젬을 소개해 드리겠습니다. dry-rb가 어떤 가능성을 제공하는지 맛볼 수 있을 것입니다!
dry-struct로 더 견고한 Struct 만들기
루비의 Struct는 Struct 클래스로부터 생성할 수 있는 일종의 객체인데, 몇 가지 한계점이 있습니다.
예를 들어, 인자 일부가 빠진 채로 struct를 생성해도 루비는 아무런 불만 없이 그냥 넘어갑니다.
dry-struct 젬을 사용하면 훨씬 엄격한 구조체를 만들 수 있습니다.
사용 방법은 다음과 같습니다:
require 'dry-struct' module Types include Dry::Types.module end class Video < Dry::Struct attribute :title, Types::String attribute :views, Types::Views attribute :comments, Types::Array end Video.new(title: "abc", views: 10, comments: [])
이제 속성 중 하나라도 누락하면 다음과 같은 에러가 발생합니다:
[Video.new] :comments is missing in Hash input
에러 메시지 자체는 친절하지 않지만, 원하는 동작은 확실히 해줍니다.
나만의 타입을 만들어 제약 조건을 추가할 수도 있습니다:
module Types include Dry::Types.module Age = Integer.constrained(gt: 0) end
여기서 "gt"는 "greater than(초과)"을 의미합니다.
즉, Age는 반드시 0보다 큰 정수여야 합니다.
기본값(default)도 설정할 수 있을까요?
네, 가능합니다. 방법은 다음과 같습니다:
module Types
include Dry::Types.module
Name = String.default('')
end
참고로 일반 루비 Struct와 비슷한 형태로 dry 구조체를 더 짧게 생성하는 방법도 있습니다.
예시:
Book = Dry::Struct(title: Dry::Types["string"]) Book.new(title: "Computer Science 101")
dry-events로 옵저버 패턴 구현하기
옵저버(Observer) 디자인 패턴은 하나의 소스가 여러 리스너에게 업데이트를 전달하는 패턴입니다. 뉴스 알림, 주식 경보, 그룹 채팅의 새 메시지 등이 대표적인 예입니다.
매우 흔하게 사용되는 패턴이며, 루비에는 이를 구현할 수 있도록 도와주는 observable 모듈이 기본 포함되어 있습니다.
하지만 dry-events도 좋은 선택지가 될 수 있습니다.
퍼블리셔(publisher)는 다음과 같이 만들 수 있습니다:
require 'dry/events/publisher'
class Blog
include Dry::Events::Publisher[:blog]
register_event('article.published')
register_event('article.upated')
end
이제 어떤 클래스든 이벤트가 발생했을 때 이를 받도록 스스로를 등록할 수 있습니다.
방법은 다음과 같습니다:
class Reader
def on_article_published(event)
puts "New article published: #{event[:title]}"
end
def on_article_updated(event)
puts "Article has been updated: #{event[:title]}"
end
end
blog = Blog.new
reader = Reader.new
blog.subscribe(reader)
publish 메서드를 사용하면 이벤트를 브로드캐스트할 수 있습니다:
blog.publish('article.published', title: 'How to Use The dry-events Gem')
한 가지 주의할 점은, 존재하지 않는 이벤트를 구독하거나 발행하더라도 아무런 오류 없이 조용히 실패한다는 것입니다.
dry-auto_inject로 의존성 주입하기
의존성(dependency)이란 무엇일까요?
의존성은 클래스가 동작하기 위해 필요한 요소, 즉 다른 객체나 클래스 등을 말합니다.
이런 객체들을 클래스 내부에서 직접 생성할 수도 있지만...
의존성 주입(dependency injection)을 사용할 수도 있습니다.
의존성 주입은 필요한 객체들을 클래스에 "주입"하는 방식입니다. 다시 말해, 파라미터로 전달받는 것입니다.
예시:
require 'faraday'
class GitHubAPI
def initialize(http_client = Faraday.new)
@http_client = http_client
end
end
이렇게 하면 의존성을 직접 제어할 수 있어 테스트가 훨씬 쉬워집니다.
이제 dry-auto_inject를 사용하면 이런 객체들을 자동으로 생성해 줍니다. 필요한 의존성만 선언하면 됩니다.
다음 예제에서 Fruit는 Store에 의존합니다:
require 'dry/auto_inject'
class Store
def bananas
puts 'Have some organic bananas!'
end
end
Import = Dry::AutoInject({ 'store' => Store.new })
class Fruit
include Import['store']
def eat
store.bananas
end
end
fruit = Fruit.new
fruit.eat
먼저 객체가 어떻게 생성되는지 지정합니다:
Import = Dry::AutoInject({ 'store' => Store.new })
그리고 해당 클래스에서 사용할 수 있도록 선언합니다:
include Import['store']
이후 다음과 같이 다른 Store를 사용하는 Fruit 객체를 손쉽게 만들 수 있습니다:
Fruit.new(store: MyStore.new)
물론 의존성 주입에 꼭 젬이 필요한 것은 아니지만, 또 다른 접근 방식을 살펴볼 수 있다는 점에서 흥미롭습니다.
마무리
이번 글에서는 코드의 특정 설계 문제를 해결하는 데 활용할 수 있는 흥미로운 루비 젬 모음인 dry-rb에 대해 알아보았습니다.
이제 여러분 차례입니다. 직접 실습하면서 활용해 보세요!
읽어주셔서 감사합니다.