场景 1:我们有一个两层关联,在 ActiveRecord 里面这样写:
orders = Order.where(id: [1,2]).includes(order_items: :product)
在 Lotus 里应该怎么写呢?按照官方的教学,查询应该直接写成一个方法:
order_repo = OrderRepository.new
orders = order_repo.find_with_order_items_with_product_by_id([1,2]) # => Array
但是问题来了,order_items 和 product 的关联是写在 OrderItemRepository 里的,OrderRepository 怎么知道存在这个关联呢?
为了保持 OrderRepository 的纯洁,我们只能把脏活交给 Controller 干:
order_repo = OrderRepository.new
orders = order_repo.find_by_id([1,2]) # => Array
order_items = OrderItemRepository.new.find_with_product_by_orders(orders) # => Array
order_repo.bind_order_items(orders, order_items)
数据总算是取出来了,可是 order_items 和 order 的关联弄丢了,没关系,我们再用一个方法补上。
取数据的部分完成了,看上去干了不少(?),但是总觉得怪怪的,Repository 的读逻辑往外层移动了,而且很难界定各部分逻辑应该由哪个 Repository 完成。
=======
场景 2:我们要创建一个聊天,并加上一条回复,其中涉及到三个表的关联,在 Rails 里这样写:
conversation = Conversation.new
reply = conversation.replies.new(message: 'xxx')
params[:attachments].each{|x| reply.attachments.new(file: x)}
conversation.save
在 Lotus 里,必须写成:
reply = Reply.new(message: 'xxx')
attachments = params[:attachments].map{|x| Attachment.new(file: x)}
ConversationRepository.new.create_with_reply_with_attachments(conversation, reply, attachments)
class ConversationRepository < Hanami::Repository
def create_with_reply_with_attachments(conversation, reply, attachments)
conversation = create
reply.conversation_id = conversation.id
saved_reply = ReplyRepository.new.save(reply)
attachments.each do |att|
att.reply_id = saved_reply.id
AttachmentRepository.new.save(att)
end
end
end
#其实真正lotus的repository是没有save的,我这里假装可以save entity,避免了传hash参数,如果是hash参数的话repository会承担更多的逻辑
为什么要把 reply 和 attachment 的数据都传到 create_with_reply_with_attachments 里呢?因为在 create reply 之前,attachment 不知道 reply 的 id,是不能保存的。所以你必须自己在 create_with_reply_with_attachments 里按顺序保存数据。
但是其实我们的 ConversationRepository 并不应该有 Attachment 甚至 Reply 的知识。这样的后果是,要么 Repository 变得越来越肥,要么是你还得像 Rails 一样,把 create 的逻辑写到 controller 里,这样 Repository 又再一次被架空了。
=======
以上两种场景都会有些逻辑的异位,不知道有什么好方法避免?
按我的理解,Repository 的正确用法应该是:
data = repository.query # complex sql
service.process(data)
repository.commit(data) # complex sql
其中 process 不操作数据库,只在内存里处理 entities,但是 query 和 commit 的 sql 其实都得自己写,而且这堆 sql 不知道如何 SRP。大概最后也会变成薄 service,厚 repository。
一种想法是把 data 做成一个数据表的 diff patch,在 commit 的时候自动 apply,但是这其实就是 AR 的做法。。只需要在内存里构建好数据和关联,然后 save 就够了。