Ruby Data 클래스: 값 객체 정의 및 Comparable 모듈 활용

Ruby `Data` Class – A Convenient Way to Create Value Objects

작성자
발행일
2025년 08월 14일

핵심 요약

  • 1 Ruby 3.2에 도입된 `Data` 클래스는 불변하며 값에 의한 동등성을 가지는 값 객체(Value Object)를 간편하게 정의할 수 있도록 지원합니다.
  • 2 값 객체는 도메인 모델에서 의미론적 명확성과 동작을 캡슐화하며, 원시 타입의 한계를 극복하고 코드의 가독성과 정확성을 높입니다.
  • 3 `Comparable` 모듈을 포함하고 `<=>` 연산자를 정의함으로써 `Data` 인스턴스 간의 비교 기능을 확장할 수 있으며, 필요에 따라 커스텀 유효성 검사 로직을 추가할 수 있습니다.

도입

Ruby 3.2에서 새롭게 선보인 `Data` 클래스는 마틴 파울러(Martin Fowler)와 에릭 에반스(Eric Evans)가 주창한 값 객체(Value Object) 개념을 루비 언어 차원에서 구현하기 위해 도입되었습니다. 값 객체는 좌표(x, y)나 자동차의 속도와 같이 여러 원시 값들의 조합으로 표현되는 속성들에 의미론적 명확성과 고유한 동작을 부여하여, 단순히 정수나 문자열 같은 원시 타입을 사용하는 것보다 더 풍부한 도메인 모델링을 가능하게 합니다. 기존의 `Struct` 클래스도 유사한 역할을 수행할 수 있지만, `Struct`가 가변(mutable) 객체인 반면 `Data` 클래스는 불변(immutable) 객체를 지향한다는 점에서 차이가 있습니다. 이러한 불변성은 값 객체의 핵심적인 특성 중 하나로, 예측 가능한 동작과 안정적인 코드를 작성하는 데 기여합니다. 본문에서는 Ruby의 `Data` 클래스가 값 객체의 주요 특성인 식별자 없음(No Identity), 불변성(Immutable), 값에 의한 동등성(Equality by Value), 작고 단순함(Small and Simple), 재사용성(Reusable)을 어떻게 지원하는지 심층적으로 탐구합니다.

Ruby의 Data 클래스는 별도의 외부 라이브러리 없이 핵심 Ruby 클래스로 제공됩니다. Data.define 메서드를 사용하여 간단하게 값 객체를 정의할 수 있으며, 정의된 객체는 new 또는 [] 메서드를 통해 인스턴스화할 수 있습니다. 예를 들어, MarsRover = Data.define(:name, :x, :y)와 같이 정의하면 MarsRover.new('Curiosity', 0, 0) 형태로 객체를 생성할 수 있습니다. Data 객체의 가장 중요한 특징은 불변성입니다. 한 번 생성된 Data 인스턴스의 속성은 변경할 수 없으며, 변경을 시도할 경우 NoMethodError가 발생합니다. 또한, Data 객체는 기본적으로 == 연산자를 통해 값에 의한 동등성 비교를 지원합니다. 즉, 서로 다른 인스턴스라도 내부 값이 동일하다면 true를 반환합니다. 이는 값 객체의 핵심 원칙 중 하나입니다. 더 실용적인 예시로, Location = Data.define(:x, :y)와 같이 위치를 나타내는 값 객체를 정의하고, Rover 클래스에서 Location 객체를 사용하여 로버의 위치를 관리할 수 있습니다. 로버의 위치를 업데이트할 때는 기존 Location 객체를 변경하는 것이 아니라 새로운 Location 객체를 생성하여 할당하는 방식으로 불변성을 유지합니다. Data 인스턴스는 기본적으로 ==eql?을 지원하지만, < , > , between?와 같은 비교 연산자는 기본적으로 제공하지 않습니다. 이러한 비교 기능을 활성화하려면 Comparable 모듈을 포함하고 <=> 연산자를 직접 정의해야 합니다. 예를 들어, Location 클래스에 include Comparable을 추가하고 def <=>(other); [x, y] <=> [other.x, other.y]; end와 같이 구현하면 두 Location 객체 간의 크기 비교가 가능해집니다. Data 클래스는 자체적으로 유효성 검사(validation) 기능을 내장하고 있지 않지만, initialize 메서드를 오버라이드하여 사용자 정의 유효성 검사 로직을 추가할 수 있습니다. 예를 들어, xy가 숫자인지 확인하는 로직을 initialize 내부에 구현하여 유효하지 않은 값에 대한 ArgumentError를 발생시킬 수 있습니다. 만약 더 정교한 타입 강제(type coercion) 및 유효성 검사 기능이 필요하다면, dry-struct와 같은 외부 라이브러리를 활용하는 것이 좋은 대안이 될 수 있습니다. dry-struct는 스키마 기반의 타입 정의와 강력한 유효성 검사 기능을 제공하여 복잡한 도메인 객체를 모델링하는 데 유용합니다.

결론

결론적으로, Ruby의 `Data` 클래스는 Ruby 3.2부터 제공되는 강력한 기능으로, 최소한의 코드로 불변하고 값에 의한 동등성을 가지는 값 객체를 정의할 수 있도록 돕습니다. 이는 도메인 모델의 의미론적 명확성을 높이고 코드의 안정성과 예측 가능성을 향상하는 데 크게 기여합니다. 비록 `Data` 클래스가 기본적으로 유효성 검사를 지원하지는 않지만, `initialize` 메서드 오버라이드 또는 `Comparable` 모듈 활용과 같은 확장 메커니즘을 통해 필요한 기능을 추가할 수 있습니다. 또한, `dry-struct`와 같은 전문적인 라이브러리는 더 엄격한 타입 시스템과 고급 유효성 검사가 요구되는 복잡한 시나리오에서 훌륭한 대안이 될 수 있습니다. 위치, 금액 또는 기타 복합 데이터를 모델링할 때 값 객체를 활용함으로써 코드는 더욱 명확해지고 정확성을 확보할 수 있습니다. `Data` 클래스는 Ruby 개발자가 더욱 견고하고 표현력 있는 애플리케이션을 구축하는 데 필수적인 도구입니다.

댓글 0

댓글 작성

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

아직 댓글이 없습니다

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