使用命令处理程序处理持久性的模式如何适合于一种纯函数式语言,在这种语言中,我们希望使与IO相关的代码尽可能的薄?
当以面向对象的语言实现域驱动设计时,通常使用命令/处理程序模式执行状态更改。在这种设计中,命令处理程序位于您的域对象之上,并负责无聊的与持久性相关的逻辑,例如使用存储库和发布域事件。处理程序是您域模型的公开面孔;诸如UI之类的应用程序代码在需要更改域对象的状态时会调用处理程序。
C#中的草图:
public class DiscardDraftDocumentCommandHandler : CommandHandler<DiscardDraftDocument>
{
IDraftDocumentRepository _repo;
IEventPublisher _publisher;
public DiscardDraftCommandHandler(IDraftDocumentRepository repo, IEventPublisher publisher)
{
_repo = repo;
_publisher = publisher;
}
public override void Handle(DiscardDraftDocument command)
{
var document = _repo.Get(command.DocumentId);
document.Discard(command.UserId);
_publisher.Publish(document.NewEvents);
}
}
该document
域对象是负责执行业务规则(如“用户应该有权丢弃文档”或“你不能放弃一个已经被丢弃的文件”),并产生我们需要发布域事件(document.NewEvents
会是一个IEnumerable<Event>
,并且可能包含一个DocumentDiscarded
事件)。
这是一个不错的设计-易于扩展(您可以通过添加新的命令处理程序来添加新用例,而无需更改域模型),并且不知道对象的持久化方式(可以轻松地将NHibernate存储库换成Mongo存储库,或将RabbitMQ发布者替换为EventStore发布者),这使得使用伪造品和模拟进行测试变得容易。它还遵循模型/视图分离-命令处理程序不知道批处理作业,GUI或REST API是否正在使用它。
在Haskell这样的纯功能语言中,您可以大致像这样对命令处理程序进行建模:
newtype CommandHandler = CommandHandler {handleCommand :: Command -> IO Result)
data Result a = Success a | Failure Reason
type Reason = String
discardDraftDocumentCommandHandler = CommandHandler handle
where handle (DiscardDraftDocument documentID userID) = do
document <- loadDocument documentID
let result = discard document userID :: Result [Event]
case result of
Success events -> publishEvents events >> return result
-- in an event-sourced model, there's no extra step to save the document
Failure _ -> return result
handle _ = return $ Failure "I expected a DiscardDraftDocument command"
这是我努力理解的部分。通常,将有某种“表示”代码调用到命令处理程序中,例如GUI或REST API。因此,现在我们的程序中有两层需要执行IO-命令处理程序和视图-这在Haskell中是一个很大的禁忌。
据我所知,这里有两个相反的作用力:一个是模型/视图分离,另一个是需要保持模型。需要将IO代码保存在某个地方的模型,但是模型/视图分离表示我们不能将其与所有其他IO代码一起放在表示层中。
当然,以“普通”语言,IO可以(并且确实)发生在任何地方。良好的设计要求将不同类型的IO保持分开,但编译器不会强制执行。
因此:当模型需要持久化时,我们如何将模型/视图的分离与将IO代码推送到程序的最边缘的愿望协调起来?我们如何使两种不同类型的IO分开,但仍远离所有纯代码?
更新:赏金会在不到24小时后过期。我觉得目前的答案都没有解决我的问题。@ Ptharien's Flame的评论acid-state
似乎很有希望,但这不是答案,而且缺乏详细信息。我讨厌浪费这些要点!
acid-state
看起来很棒,感谢您的链接。就API设计而言,它似乎仍然受到约束IO
; 我的问题是关于持久性框架如何适合更大的体系结构。您是否知道acid-state
与表示层一起使用的任何开源应用程序,并且成功地将两者分开?
Query
和和Update
monad相差甚远IO
。我将尝试在答案中给出一个简单的例子。
acid-state
似乎与您所描述的很接近。