Struct는 물러나라: Ruby의 새로운 Data.define를 만나보세요

발행: (2026년 1월 15일 오전 05:50 GMT+9)
4 min read
원문: Dev.to

Source: Dev.to

Introduction

Ruby 3.2가 도입되면서 Data.define은 현대 Ruby 애플리케이션에서 DTO(데이터 전송 객체)를 다루는 선호되는 방법이 되었습니다. 오랫동안 Struct가 기본 선택이었지만, 순수 데이터 전송에 있어서는 몇 가지 특이점 때문에 이상적이지 않았습니다. Data값 객체 팩토리를 위해 특별히 설계되었습니다.

Data.define ?

Data는 ( Struct와 유사한) 클래스 팩토리로, 간단하고 불변 객체를 정의하는 데 사용됩니다.

# Define a Data class
Address = Data.define(:city, :zip)

# Initialize it
home = Address.new(city: "Vilnius", zip: "01100")

# Access data
home.city # => "Vilnius"

Immutable by Design

DataStruct의 가장 큰 차이점은 Data 객체가 불변이라는 점입니다—설정자 메서드가 없습니다.

Point = Data.define(:x, :y)
p1 = Point.new(x: 1, y: 2)

p1.x = 10
# NoMethodError (undefined method `x=' for #)

DTO 상황에서는 불변성이 큰 장점이 됩니다. 데이터가 애플리케이션의 여러 계층을 통과하면서 일관성을 유지하도록 보장해 줍니다.

Flexible initialization

Data는 기본적으로 위치 인자와 키워드 인자 모두를 받아들입니다.

Point = Data.define(:x, :y)

# Both work:
p1 = Point.new(1, 2)
p2 = Point.new(x: 1, y: 2)

Note: 키워드를 사용할 때는 검증이 이루어집니다. 오타(z: 3 등)를 전달하면 ArgumentError가 발생합니다.

Equality and pattern matching

Data 객체의 두 인스턴스는 값이 동일하면 같은 것으로 간주됩니다.

p1 = Point.new(1, 2)
p2 = Point.new(1, 2)

p1 == p2    # => true
p1.eql?(p2) # => true

Data 객체는 Ruby의 패턴 매칭(case/in)과도 완벽히 호환됩니다.

case p1
in Point(x, y) if x > 0
  puts "X is positive: #{x}"
end

Adding custom logic

Struct와 마찬가지로 Data.define에 블록을 전달해 메서드를 추가할 수 있습니다.

Money = Data.define(:amount, :currency) do
  def to_s
    "#{amount} #{currency}"
  end
end

puts Money.new(100, "USD").to_s # => "100 USD"

Data vs. Struct

FeatureData.define (Ruby 3.2+)Struct
MutabilityImmutable (read‑only)Mutable (read/write)
InitializationPositional or keywordsPositional (keywords must be enabled)
Enumerable methodsNoYes (.each, .map, etc.)
Intended useValue objects / DTOsLightweight “property” classes
Interface sizeMinimal (clean)Large (inherits many methods)

When to still use Struct

Data가 일반적으로 DTO에 더 좋지만, Struct가 여전히 유용한 상황도 있습니다:

  • Mutability needed – 객체가 자주 업데이트되는 임시 “스크래치패드” 역할을 할 때.
  • Enumerable needed – 객체의 속성에 대해 .each, .select 등을 직접 호출하고 싶을 때.
  • Legacy Ruby – Ruby 3.1 이하를 사용하고 있어 Data.define을 사용할 수 없을 때.

Data.define for DTOs

일반적인 Rails 또는 Dry‑Ruby 스택에서 DTO는 서비스 객체에서 뷰로, 혹은 API 응답에서 모델로 데이터를 전달합니다. Data.define을 사용하면 다음과 같은 장점이 있습니다:

  • 우발적인 부작용을 방지합니다(요청 중간에 값이 변경될 수 없습니다).
  • 코드가 더 “정직”해집니다—객체는 단순히 데이터일 뿐입니다.
  • 키워드 인자를 기본 지원하므로 위치 기반 Struct 인자보다 코드 가독성이 크게 향상됩니다.

Example of a modern DTO

UserDTO = Data.define(:id, :email, :role)

# In a controller or service
user_data = UserDTO.new(id: user.id, email: user.email, role: user.role)
Back to Blog

관련 글

더 보기 »

Go에서 우아한 도메인 주도 설계 객체

❓ Go에서 도메인 객체를 어떻게 정의하시나요? Go는 전형적인 객체‑지향 언어가 아닙니다. Domain‑Driven Design(DDD) 같은 개념을 구현하려고 할 때, 예를 들어 En…

Ruby로 Telegram 인용 봇 만들기

요구 사항 - IDE가 필요합니다 - 프로젝트 구조: project_folder/ ├── bot.rb ├── quote.rb ├── help.rb ├── start.rb └── Gemfile 참고: .env 파일은 커밋되지 않습니다.