Как шаблон использования обработчиков команд для работы с постоянством вписывается в чисто функциональный язык, где мы хотим сделать код, связанный с 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
похоже, что это близко к тому, что вы описываете .acid-state
выглядит довольно здорово, спасибо за эту ссылку. С точки зрения дизайна API это все еще кажется связаннымIO
; мой вопрос о том, как постоянная структура вписывается в большую архитектуру. Знаете ли вы о каких-либо приложенияхacid-state
с открытым исходным кодом, которые используют наряду с уровнем представления, и преуспели в сохранении двух отдельных?Query
иUpdate
монады довольно далеки отIO
этого. Я постараюсь привести простой пример в ответ.Ответы:
Общий способ разделения компонентов в Haskell - через монадные стеки трансформаторов. Я объясню это более подробно ниже.
Представьте, что мы создаем систему, которая имеет несколько крупных компонентов:
Мы решили, что нам нужно поддерживать слабую связь этих компонентов, чтобы поддерживать хороший стиль кода.
Поэтому мы кодируем каждый из наших компонентов полиморфно, используя для этого различные классы MTL:
MonadState DataState m => Foo -> Bar -> ... -> m Baz
DataState
это чистое представление снимка состояния нашей базы данных или хранилищаMonadState UIState m => Foo -> Bar -> ... -> m Baz
UIState
это чистое представление снимка состояния нашего пользовательского интерфейсаMonadState (DataState, UIState) m => Foo -> Bar -> ... -> m Baz
main :: IO ()
которое выполняет почти тривиальную работу по объединению других компонентов в одну системуzoom
или подобный комбинаторStateT (DataState, UIState) IO
, который затем запускается с фактическим содержимым базы данных или хранилища для созданияIO
.источник
DataState
чистым представлением снимка состояния нашей базы данных или хранилища». Предположительно, вы не хотите загружать всю базу данных в память!Нужно ли сохранять модель? Во многих программах сохранение модели необходимо, поскольку состояние непредсказуемо, любая операция может каким-либо образом изменить модель, поэтому единственный способ узнать состояние модели - получить к ней доступ напрямую.
Если в вашем сценарии последовательность событий (команды, которые были проверены и приняты) всегда могут генерировать состояние, то это события, которые должны сохраняться, а не обязательно состояние. Состояние всегда может быть сгенерировано путем воспроизведения событий.
Сказав это, часто состояние сохраняется, но просто как снимок / кэш, чтобы избежать воспроизведения команд, а не как важные данные программы.
После того, как команда принята, событие передается двум адресатам (хранилище событий и система отчетов), но на одном уровне программы.
См. Также
Источник событий
Eager Read Derivation
источник
Вы пытаетесь выделить место в интенсивном приложении ввода-вывода для всех операций, не связанных с вводом-выводом; К сожалению, типичные CRUD-приложения, о которых вы говорите, мало чем отличаются от IO.
Я думаю, что вы прекрасно понимаете соответствующее разделение, но если вы пытаетесь разместить постоянный код ввода-вывода на некотором количестве уровней по сравнению с кодом презентации, общий факт в том, что ваш контроллер находится там, где вы должны обращаться к своему слой персистентности, который может показаться вам слишком близким к вашей презентации, но это просто совпадение в этом типе приложений.
Презентация и постоянство составляют в основном весь тип приложения, которое, я думаю, вы описываете здесь.
Если вы подумаете о подобном приложении, в котором было много сложной бизнес-логики и обработки данных, я думаю, вы обнаружите, что можете представить, как это красиво отделено от презентационного ввода-вывода и постоянного ввода-вывода, так что ему ничего не нужно знать ни о том, ни о другом. Проблема, с которой вы столкнулись сейчас, - это просто проблема восприятия, вызванная попыткой найти решение проблемы в типе приложения, у которого нет такой проблемы с самого начала.
источник
Насколько я понимаю ваш вопрос (который я не могу, но думал, что я добавлю мои 2 цента), так как вы не обязательно имеете доступ к самим объектам, вам нужна собственная база данных объектов, которая самостоятельно истекает со временем).
В идеале сами объекты могут быть улучшены для хранения их состояния, поэтому, когда они будут «переданы», различные командные процессоры будут знать, с чем они работают.
Если это невозможно, (icky icky), единственный способ - это иметь какой-то общий DB-подобный ключ, который вы можете использовать для хранения информации в хранилище, которое настроено для совместного использования между различными командами - и, надеюсь, «откройте» интерфейс и / или код, чтобы другие авторы команд также переняли ваш интерфейс для сохранения и обработки метаинформации.
В области файловых серверов samba имеет различные способы хранения таких вещей, как списки доступа и альтернативные потоки данных, в зависимости от того, что предоставляет хост-операционная система. В идеале, samba размещается в файловой системе и предоставляет расширенные атрибуты для файлов. Пример 'xfs' в 'linux' - больше команд копируют расширенные атрибуты вместе с файлом (по умолчанию большинство утилит в linux "выросли" без дополнительных атрибутов).
Альтернативное решение, которое работает для нескольких процессов Samba от разных пользователей, работающих с общими файлами (объектами), заключается в том, что если файловая система не поддерживает подключение ресурса непосредственно к файлу, как с расширенными атрибутами, использует модуль, который реализует слой виртуальной файловой системы для эмуляции расширенных атрибутов процессов samba. Об этом знает только samba, но преимущество в том, что он работает, когда формат объекта не поддерживает его, но все же работает с различными пользователями samba (см. Процессоры команд), которые выполняют некоторую работу над файлом на основе своего предыдущего состояния. Он будет хранить метаинформацию в общей базе данных для файловой системы, которая помогает контролировать размер базы данных (и не
Это может оказаться бесполезным, если вам нужна дополнительная информация, относящаяся к реализации, с которой вы работаете, но концептуально одна и та же теория может быть применена к обоим наборам задач. Так что, если вы искали алгоритмы и методы, чтобы делать то, что вы хотите, это могло бы помочь. Если вам нужны более конкретные знания в какой-то конкретной среде, то, возможно, не очень полезны ... ;-)
Кстати, причина, по которой я упоминаю «саморазвитие», заключается в том, что неясно, знаете ли вы, какие объекты существуют и как долго они сохраняются. Если у вас нет прямого способа узнать, когда объект удаляется, вам придется обрезать свою собственную метаданную, чтобы она не заполнялась старой или древней метаинформацией, для которой пользователи давно удалили объекты.
Если вы знаете, когда срок действия объектов истек / удален, значит, вы опередили игру и можете одновременно вывести ее из своей метабазы, но было неясно, была ли у вас такая возможность.
Ура!
источник