How does the pattern of using command handlers to deal with persistence fit into a purely functional language, where we want to make IO-related code as thin as possible?
When implementing Domain-Driven Design in an object-oriented language, it's common to use the Command/Handler pattern to execute state changes. In this design, command handlers sit on top of your domain objects, and are responsible for the boring persistence-related logic like using repositories and publishing domain events. The handlers are the public face of your domain model; application code like the UI calls the handlers when it needs to change domain objects' state.
A sketch in 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);
}
}
The document
domain object is responsible for implementing the business rules (like "the user should have permission to discard the document" or "you can't discard a document that's already been discarded") and for generating the domain events we need to publish (document.NewEvents
would be an IEnumerable<Event>
and would probably contain a DocumentDiscarded
event).
This is a nice design - it's easy to extend (you can add new use cases without changing your domain model, by adding new command handlers) and is agnostic as to how objects are persisted (you can easily swap out an NHibernate repository for a Mongo repository, or swap a RabbitMQ publisher for an EventStore publisher) which makes it easy to test using fakes and mocks. It also obeys model/view separation - the command handler has no idea whether it's being used by a batch job, a GUI, or a REST API.
In a purely-functional language like Haskell, you might model the command handler roughly like this:
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"
Here's the part I'm struggling to understand. Typically, there'll be some sort of 'presentation' code which calls into the command handler, like a GUI or a REST API. So now we have two layers in our program which need to do IO - the command handler and the view - which is a big no-no in Haskell.
As far as I can make out, there are two opposing forces here: one is model/view separation and the other is the need to persist the model. There needs to be IO code to persist the model somewhere, but model/view separation says that we can't put it in the presentation layer with all the other IO code.
Of course, in a "normal" language, IO can (and does) happen anywhere. Good design dictates that the different types of IO be kept separate, but the compiler doesn't enforce it.
So: how do we reconcile model/view separation with the desire to push IO code to the very edge of the program, when the model needs to be persisted? How do we keep the two different types of IO separate, but still away from all the pure code?
Update: The bounty expires in less than 24 hours. I don't feel that either of the current answers has addressed my question at all. @Ptharien's Flame's comment about acid-state
seems promising, but it's not an answer and it's lacking in detail. I'd hate for these points to go to waste!
acid-state
seems to be close to what you are describing.acid-state
looks pretty great, thanks for that link. In terms of API design it still seems to be bound toIO
; my question is about how a persistence framework fits into a larger architecture. Do you know of any open-source applications that useacid-state
alongside a presentation layer, and succeed in keeping the two separate?Query
andUpdate
monads are pretty far removed fromIO
, actually. I'll try to give a simple example in an answer.