In che modo il modello di utilizzo dei gestori dei comandi per gestire la persistenza si adatta a un linguaggio puramente funzionale, dove vogliamo rendere il codice relativo all'IO il più sottile possibile?
Quando si implementa la progettazione guidata dal dominio in un linguaggio orientato agli oggetti, è comune utilizzare il modello Command / Handler per eseguire i cambiamenti di stato. In questo progetto, i gestori dei comandi si trovano in cima agli oggetti del dominio e sono responsabili della noiosa logica relativa alla persistenza come l'utilizzo di repository e la pubblicazione di eventi di dominio. I gestori sono il volto pubblico del tuo modello di dominio; il codice dell'applicazione come l'interfaccia utente chiama i gestori quando deve cambiare lo stato degli oggetti di dominio.
Uno schizzo 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);
}
}
L' document
oggetto di dominio è responsabile per l'attuazione delle regole di business (come "l'utente deve avere il permesso di scartare il documento" o "non si può eliminare un documento che è già stato scartato") e per la generazione degli eventi dominio abbiamo bisogno di pubblicare ( document.NewEvents
sarebbe essere un IEnumerable<Event>
e conterrebbe probabilmente un DocumentDiscarded
evento).
Questo è un bel design - è facile da estendere (puoi aggiungere nuovi casi d'uso senza cambiare il tuo modello di dominio, aggiungendo nuovi gestori di comandi) ed è agnostico sul modo in cui gli oggetti sono persistenti (puoi facilmente scambiare un repository NHibernate per un Mongo repository o scambia un editore RabbitMQ con un editore EventStore) che semplifica il test usando falsi e beffe. Rispetta anche la separazione modello / vista: il gestore comandi non ha idea se viene utilizzato da un processo batch, una GUI o un'API REST.
In un linguaggio puramente funzionale come Haskell, potresti modellare il gestore dei comandi più o meno in questo modo:
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"
Ecco la parte che faccio fatica a capire. In genere, ci sarà una sorta di codice 'presentazione' che chiama nel gestore dei comandi, come una GUI o un'API REST. Quindi ora abbiamo due livelli nel nostro programma che devono fare IO - il gestore dei comandi e la vista - che è un grande no-no in Haskell.
Per quanto ne so, ci sono due forze opposte qui: una è la separazione modello / vista e l'altra è la necessità di persistere nel modello. È necessario un codice IO per rendere persistente il modello da qualche parte , ma la separazione modello / vista afferma che non possiamo inserirlo nel livello di presentazione con tutto l'altro codice IO.
Naturalmente, in un linguaggio "normale", l'IO può (e fa) accadere ovunque. Una buona progettazione impone che i diversi tipi di I / O siano tenuti separati, ma il compilatore non lo impone.
Quindi: come conciliare la separazione modello / vista con il desiderio di spingere il codice IO fino al limite del programma, quando il modello deve essere persistito? Come manteniamo separati i due diversi tipi di IO , ma ancora lontani da tutto il codice puro?
Aggiornamento : la taglia scade in meno di 24 ore. Non credo che nessuna delle risposte attuali abbia risposto alla mia domanda. Il commento di @ Ptharien Flame acid-state
sembra promettente, ma non è una risposta e manca di dettagli. Odio che questi punti vadano sprecati!
acid-state
sembra abbastanza bello, grazie per quel link. In termini di progettazione API sembra essere ancora legato IO
; la mia domanda è su come un framework di persistenza si adatta a un'architettura più ampia. Sei a conoscenza di applicazioni open source che utilizzano acid-state
insieme a un livello di presentazione e riescono a mantenere separati i due?
Query
e Update
sono abbastanza lontane da IO
, in realtà. Proverò a dare un semplice esempio in una risposta.
acid-state
sembra essere vicino a quello che stai descrivendo .