Как шаблон использования обработчиков команд для работы с постоянством вписывается в чисто функциональный язык, где мы хотим сделать код, связанный с IO, как можно более тонким?
При реализации доменно-управляемого проектирования на объектно-ориентированном языке обычно используется шаблон Command / Handler для выполнения изменений состояния. В этом дизайне обработчики команд располагаются поверх ваших доменных объектов и отвечают за скучную логику, связанную с постоянством, такую как использование репозиториев и публикация событий домена. Обработчики являются публичным лицом вашей доменной модели; Код приложения, такой как пользовательский интерфейс, вызывает обработчики, когда ему нужно изменить состояние объектов домена.
Эскиз в 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 может (и происходит) где угодно. Хороший дизайн требует, чтобы различные типы ввода-вывода оставались раздельными, но компилятор не применяет их.
Итак: как мы можем согласовать разделение модели / представления с желанием перенести код ввода-вывода на самый край программы, когда модель должна сохраняться? Как сохранить два разных типа ввода-вывода отдельно , но все же от всего чистого кода?
Обновление : срок действия награды истекает менее чем за 24 часа. Я не чувствую, что ни один из текущих ответов вообще ответил на мой вопрос. Комментарий Flame от @ Ptharien's acid-state
кажется многообещающим, но он не является ответом и ему не хватает деталей. Я бы не хотел, чтобы эти пункты пропали даром!
acid-state
выглядит довольно здорово, спасибо за эту ссылку. С точки зрения дизайна API это все еще кажется связанным IO
; мой вопрос о том, как постоянная структура вписывается в большую архитектуру. Знаете ли вы о каких-либо приложениях acid-state
с открытым исходным кодом, которые используют наряду с уровнем представления, и преуспели в сохранении двух отдельных?
Query
и Update
монады довольно далеки от IO
этого. Я постараюсь привести простой пример в ответ.
acid-state
похоже, что это близко к тому, что вы описываете .