🚀
Query Serviceとは
Daily Blogging94日目
複数のリソースにまたがるデータの取得ロジックはどこに置けばいいんだろう
Query Service
DDDにおいて、特定のユースケースで使用する複数のリソースにまたがる複雑なデータの取得ロジックはQuery Serivceを利用するらしい。
CQRSのデータ参照部分を担う。
Repositoryを使用する場合、リソースごとのRepositoryを呼んでそのあとでデータを適宜加工する。みたいな処理が必要
これが面倒臭い
コードは複雑になり、別々に取得してくるのでクエリのパフォーマンスも悪くなる可能性がある。
Repositoryの場合
# User のリポジトリ
class UserRepository
def find_by_id(user_id)
User.find(user_id) # 単純な取得
end
end
# Order のリポジトリ
class OrderRepository
def find_by_user_id(user_id)
Order.where(user_id: user_id).order(created_at: :desc).to_a
end
end
これらをユースケース層で呼び出す。
class UserOrderApplicationService
def initialize(user_repository = UserRepository.new, order_repository = OrderRepository.new)
@user_repository = user_repository
@order_repository = order_repository
end
def fetch_user_orders(user_id)
user = @user_repository.find_by_id(user_id) # ユーザー情報取得
orders = @order_repository.find_by_user_id(user_id) # 注文一覧取得
{
user_id: user.id,
user_name: user.name,
orders: orders.map do |order|
{
order_id: order.id,
total_price: order.total_price,
order_date: order.created_at
}
end
}
end
end
Query Serviceを使うと
こんな感じ
class UserOrderQueryService
def initialize(db_connection = ActiveRecord::Base.connection)
@db_connection = db_connection
end
def fetch_user_orders(user_id)
sql = <<-SQL
SELECT users.id AS user_id, users.name AS user_name,
orders.id AS order_id, orders.total_price, orders.created_at AS order_date
FROM users
LEFT JOIN orders ON users.id = orders.user_id
WHERE users.id = ?
ORDER BY orders.created_at DESC
SQL
result = @db_connection.exec_query(sql, "SQL", [[nil, user_id]])
orders = result.map do |row|
OrderReadModel.new(row["order_id"], row["total_price"], row["order_date"])
end
first_row = result.first
UserOrderReadModel.new(first_row["user_id"], first_row["user_name"], orders)
end
end
Data Transfer Object
class UserOrderReadModel
attr_reader :user_id, :user_name, :orders
def initialize(user_id, user_name, orders)
@user_id = user_id
@user_name = user_name
@orders = orders
end
end
class OrderReadModel
attr_reader :order_id, :total_price, :order_date
def initialize(order_id, total_price, order_date)
@order_id = order_id
@total_price = total_price
@order_date = order_date
end
end
呼び出し元はこう
class UserOrderApplicationService
def initialize(user_order_query_service = UserOrderQueryService.new)
@user_order_query_service = user_order_query_service
end
def fetch_user_orders(user_id)
@user_order_query_service.fetch_user_orders(user_id)
end
end
シンプル!
Discussion