CQRS che…?
per una esaustiva introduzione a CQRS potete leggere questo articolo di Martin Fowler.
Andrea nella sua sessione su CQRS al .NETCAMPUS presentando il pattern, e una possibile implementazione, ha detto una cosa importantissima, particolarmente vera quando si parla in generale di Domain Driven Design:
CQRS, Event Sourcing e DDD sono una metodologia di sviluppo che aggrega vari pattern architetturali, ognuno dei quali, in fase di implementazione, può essere declinato in modi diversi tutte egualmente giusti, in dipendenza del contesto in cui ci troviamo.
(non sono le esatte parole, ma rendono l’idea)
Questo per dire che non c’è un modo giusto e uno sbagliato di implementare CQRS proprio perché CQRS si limita a postulare alcuni concetti che assumono diverse possibili implementazioni quando calati nel contesto in cui stiamo lavorando.
Jason
Oggi vediamo una possibile implementazione della parte di comandi di CQRS introducendo un toolkit, Jason, che ho scritto proprio per dimenticarmi della parte infrastrutturale necessaria per supportare la parte di “commanding” di CQRS.
The big picture
Ad alto livello il nostro scenario è riassumibile, molto semplicemente, come segue:
Il comando
Un comando è una semplicissima classe il cui scopo è quello di esprimere un’intenzione:
public class ApproveOrder
{
public string OrderId { get; set; }
public string ApprovalReason { get; set; }
}
ovviamente se il client è ad esempio una “single page application” (SPA) possiamo esprimere la stessa cosa in json:
{
'$type': 'MyNamespace.ApproveOrder, MyAssembly',
orderId: 'orders/123',
approvalReason: 'the order is valid',
}
abbiamo quindi dei semplicissimi DTO che trasportano informazioni ed esprimono un’intenzione.
Il CommandHandler
Lato server la cosa è altrettanto triviale:
public class ApproveOrderHandler : AbstractCommandHandler<ApproveOrder>
{
readonly IRepositoryFactory repositoryFactory;
public ApproveOrderHandler( IRepositoryFactory repositoryFactory )
{
Ensure.That( repositoryFactory ).Named( () => repositoryFactory ).IsNotNull();
this.repositoryFactory = repositoryFactory;
}
protected override object OnExecute( ApproveOrder command )
{
using ( var repository = this.repositoryFactory.OpenSession() )
{
var order = repository.GetById<Order>( command.OrderId );
order.Approve( command.ApprovalReason );
repository.Save( order );
repository.CommitChanges();
return new OrderApproved() { OrderId = order.Id };
}
}
}
una classe che deriva da qualcosa dell’infrastruttura di Jason, classe che visto il tipo di costruttore viene risolta da un motore di Inversion of Control consentendoci di iniettare dipendenze, classe il cui unico ruolo è fare l’override del metodo OnExecute e gestire il comando in ingresso ritornando qualcosa al chiamante, qualcosa che fondamentalmente è un semplice ACK/NAK.
La configurazione
Se ad esempio la parte server fosse ospitata da WebAPI come ne faremmo il setup? e se fosse WCF? niente di più semplice e per i dettagli, anche perché questo posta sta già diventando troppo lungo, vi rimando a quello che ho ampiamente dettagliato sul mio blog in inglese: http://milestone.topics.it/2013/04/jason-v02-configuration-improvements.html
…e il comando come lo mandiamo?
Anche in questo caso nulla di più semplice, se ad esempio il client è una SPA possiamo sfruttare jQuery per fare una post verso l’endpoint WebAPI di Jason:
var command = {
'$type': 'MyNamespace.ApproveOrder, MyAssembly',
orderId: 'orders/123',
approvalReason: 'the order is valid',
};
$.ajax({
url: '/api/jason/post',
data: ko.toJSON(command),
type: 'POST',
contentType: 'application/json',
dataType: 'json'
}).done(function (ack, textStatus, jqXHR) {
//completed
});
Notate come tutta l’infrastruttura, e ce ne è tanta, non sia un problema nostro? A noi il semplice compito di:
- definire i comandi;
- definire che cosa deve succedere quando arrivano i comandi;
- inviare i comandi;
Quindi il nostro ruolo, finalmente, è solo quello di occuparci della logica di business e non dell’infrastruttura.
.m