📦
Dataクラスを使ってDTOのサンプルを作ってみる
Dataクラスとは
Ruby3.2で導入されたクラス。
Struct
とあまり使用感が変わらないものの、できることを減らしている。
中でもimmutable(不変)であるところが大きい。
MyStruct = Struct.new(:a, :b)
MyData = Data.define(:a, :b)
my_struct = MyStruct.new(a: 1, b: 2)
my_data = MyData.new(a: 1, b: 2)
my_struct.a = 99
my_data.a = 99 # undefined method error
DTO(Data Transfer Object)とは
デザインパターンの一つで、データの受け渡しに使う入れ物。
使わない例
def show_profile(user_data)
puts "name: #{user_data[:name]}"
puts "age: #{user_data[:age]}"
end
show_profile({ name: "John Doe", age: 30 })
「RubyはすべてObjectだからhashもDTOだ!」と言えなくもない(?)が、型があまりになさすぎるので、保守性に欠ける。
使う例
class UserDto
attr_reader :name, :age
def initialize(name:, age:)
@name = name
@age = age
end
end
def display_user_info(user_dto)
puts "User Name: #{user_dto.name}"
puts "User Age: #{user_dto.age}"
end
user_dto = UserDto.new(name: "John Doe", age: 30)
display_user_info(user_dto)
どの値をやりとりしているかが分かりやすくなったし、ゲッタで取れるのでコード自体も書きやすくなった
他の書き方
Structを使う例
Struct(構造体)を使うことでもう少し簡素に書ける
UserDto = Struct.new(:name, :age)
def display_user_info(user_dto)
puts "User Name: #{user_dto.name}"
puts "User Age: #{user_dto.age}"
end
user_dto = UserDto.new(name: "John Doe", age: 30)
display_user_info(user_dto)
しかしStructはゲッタの他にセッタも作るため、immutableには作れない
user_dto = UserDto.new(name: "John Doe", age: 30)
user_dto.age = 100
display_user_info(user_dto)
=> # User Name: John Doe
# User Age: 100
DTOの概念としてはimmutableじゃないといけないということも無さそうだった、ただ自分の用途としては初期化以降は手を入れないものとして扱いたかった。
Dataクラスを使う例(本題)
ここでimmutableに作れるというDataクラスがうまくハマるのではと思ったのがこちら
UserDto = Data.define(:name, :age)
def display_user_info(user_dto)
puts "User Name: #{user_dto.name}"
puts "User Age: #{user_dto.age}"
end
user_dto = UserDto.new(name: "John Doe", age: 30)
display_user_info(user_dto)
こちらはしっかりエラーを出してくれる
user_dto = UserDto.new(name: "John Doe", age: 30)
user_dto.age = 100 # undefined method error
xxxServiceっぽいものを作ってみる
Railsで「Userテーブルを元にした集計データを返すサービス」という例で書いてみる。
class SummaryUsersQueryService
Dto = Data.define(:count, :max_age)
def execute
Dto.new(count: User.count, max_age: User.maximum(:age))
end
end
class ExampleController
def index
@summary = SummaryUsersQueryService.new.execute
@summary.max_age # => 30
end
end
immutableでしっかりしたDTOが割と簡素に書けるようになったと思う。
Discussion