Як модель використання командних обробників для боротьби зі стійкістю вписується в суто функціональну мову, де ми хочемо зробити код, пов'язаний з 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 на монго сховище або замінити видавця RabbitMQ на видавця EventStore), що спрощує тестування за допомогою підробок і макетів. Він також підпорядковується розділенню моделі / перегляду - обробник команд не має поняття, використовується він пакетним завданням, графічним інтерфейсом або API REST.
На чисто функціональній мові, як 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 - обробник команд та view - що в Haskell є великим "no-no".
Наскільки я можу розібратися, тут є дві протилежні сили: одна - це модель / розділення перегляду, а друга - необхідність зберігати модель. Потрібно мати код IO, щоб десь зберігати модель , але розділення моделі / перегляду говорить про те, що ми не можемо помістити її у презентаційний шар разом з усіма іншими кодами IO.
Звичайно, в "нормальній" мові IO може (і справді) траплятися де завгодно. Хороший дизайн передбачає, що різні типи вводу-виводу повинні зберігатись окремо, але компілятор не застосовує їх.
Отже: як ми можемо узгодити поділ моделі / перегляду з бажанням підштовхнути код IO до самого краю програми, коли модель потребує збереження? Як ми можемо тримати два різних типу вводу-виводу окремо , але все ще далеко від чистого коду?
Оновлення : виплата закінчується менш ніж за 24 години. Я не відчуваю, що жодна з нинішніх відповідей взагалі не стосується мого питання. @ Коментар полум’я Птарієна про acid-state
здається багатообіцяючим, але це не відповідь і його бракує докладно. Мені б не хотілося, щоб ці моменти пішли на сміття!
acid-state
виглядає досить чудово, дякую за це посилання. Що стосується дизайну API, він все ще, здається, зобов'язаний IO
; моє запитання - про те, як система стійкості вписується в більшу архітектуру. Чи знаєте ви про будь-які програми з відкритим кодом, які використовують acid-state
поруч із презентаційним шаром, і їм вдається зберегти їх окремо?
Query
І Update
монади досить далеко від IO
, на самому ділі. Спробую дати простий приклад у відповідь.
acid-state
здається, близький до того, що ви описуєте .