Academic Integrity: tutoring, explanations, and feedback — we don’t complete graded work or submit on a student’s behalf.

How does the pattern of using command handlers to deal with persistence fit into

ID: 644310 • Letter: H

Question

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?

Explanation / Answer

The general way to separate components in Haskell is through monad transformer stacks. I explain this in more detail below.

Imagine we're building a system that has several large-scale components:

a component that talks with the disk or database (submodel)
a component that does transformations on our domain (model)
a component that interacts with the user (view)
a component that describes the connection between view, model, and submodel (controller)
a component that kickstarts the whole system (driver)
We decide that we need to keep these components loosely coupled in order to maintain good code style.

Therefore we code each of our components polymorphically, using the various MTL classes to guide us:

every function in the submodel is of type MonadState DataState m => Foo -> Bar -> ... -> m Baz
DataState is a pure representation of a snapshot of the state of our database or storage
every function in the model is pure
every function in the view is of type MonadState UIState m => Foo -> Bar -> ... -> m Baz
UIState is a pure representation of a snapshot of the state of our user interface
every function in the controller is of type MonadState (DataState, UIState) m => Foo -> Bar -> ... -> m Baz
Notice that the controller has access to both the state of the view and the state of the submodel
the driver has only one definition, main :: IO (), which does the near-trivial work of combining the other components into one system
the view and submodel will need to be lifted into the same state type as the controller using zoom or a similar combinator
the model is pure, and so can be used without restriction
in the end, everything lives in (a type compatible with) StateT (DataState, UIState) IO, which is then run with the actual contents of the database or storage to produce IO.

Hire Me For All Your Tutoring Needs
Integrity-first tutoring: clear explanations, guidance, and feedback.
Drop an Email at
drjack9650@gmail.com
Chat Now And Get Quote