CQRS, ovvero: 2 stack, uno per "leggere" e l'altro per "scrivere". Se per "scrivere" abbiamo l'imbarazzo della scelta (Domain Model, Command, Event Sourcing, ...) per leggere, invece, apparentemente c'è poco da dire. "Apparentemente", appunto. Parliamone :-)
1. Layered Expression Trees
feat.
CQRS
Andrea Saltarello
C.T.O. @ managed/designs
http://blogs.ugidotnet.org/pape
http://twitter.com/andysal74
http://creativecommons.org/licenses/by-nc-nd/2.5/
2. Andrea Saltarello! Chi era costui?
• C.T.O. di Managed Designs, alla ricerca della
«sostenibilità dello sviluppo software»
1. Il cliente «deve» essere soddisfatto e pagare
2. Il fornitore «deve» avere un margine ragionevole
3. Il team «deve» essere soddisfatto del proprio lavoro
• (Co)Fondatore e presidente di UGIdotNET: ho
bisogno di condividere esperienze a causa del
bullet precedente
• (Co)Fondatore e collo di bottiglia di GUISA
• (Co)Autore di .NET: Architecting Applications
for the Enterprise di Microsoft Press
• Il #NAAE è il mio giornale di bordo, nel quale (nel
2008) ho cercato di «sintetizzare» i punti 1 e 2
6. Blue Book: Architettura (2/2)
è una layered architecture
i layer Presentation e Infrastructure
compaiono «per sport» nel diagramma
i layer Application e Domain costituiscono
quella che tipicamente chiamiamo «business
logic»
Domain: logica invariante rispetto ai casi d’uso
Application: logica specifica ai casi d’uso
7. Domain layer: Key Concepts
Il Domain Layer contiene la domain logic ed è
composto da
Model: Entità (identità e stato) e Valori (solo stato)
Servizi
Il Model è «both data and behaviour» (cit.)
La persistenza del Model è gestita da Repository
Le istanze delle entità di dominio sono costruite da
Factory
Entità e Valori a runtime formano dei grafi di
oggetti. I grafi dotati di “dignità propria” sono
chiamati Aggregate e il sistema (es: i Repository) si
“impegna” a gestirli correttamente ed
atomicamente
8. Application Layer: in teoria
Application Layer: defines the jobs the software
is supposed to do and directs the expressive
domain objects to work out problems. The tasks
this layer is responsible for are meaningful to the
business or necessary for interaction with the
application layers of other systems. This layer is
kept thin. It does not contain business rules or
knowledge, but only coordinates tasks and
delegates work to collaborations of domain
objects in the next layer down. It does not have
state reflecting the business situation, but it can
have state that reflects the progress of a task for
the user or the program.
9. Application Layer: in pratica
E’ un catalogo di servizi in grado di effettuare
il mesh della logica presente nel domain layer
esponendola alla applicazione (es:
presentation layer) in una forma ad… Alta
digeribilità.
11. Command Query Responsibility
Segregation
Command and Query Responsibility Segregation (CQRS)
originated with Bertrand Meyer’s Command and Query
Separation Principle. Wikipedia defines the principle as:
It states that every method should either be a command
that performs an action, or a query that returns data to
the caller, but not both.
Command and Query Responsibility Segregation uses the
same definition of Commands and Queries that Meyer
used and maintains the viewpoint that they should be
pure. The fundamental difference is that in CQRS objects
are split into two objects, one containing the Commands
one containing the Queries.
[Fonte: http://cqrsinfo.com/documents/cqrs-introduction/ ]
13. CQRS, a.k.a. «Stato di Diritto»
CQRS introduce in DDD la «separazione dei
poteri»
• 1 stack per «leggere»
• 1 stack per «eseguire»
In pratica, significa non dover «indovinare» un
modello valido per entrambe le esigenze
In questa sessione ci interessa la «Q»
14. Scenario 1: e-commerce
Home page: visualizzare prodotti raccomandati
Considerati tutti i prodotti catalogati, dall’elenco di
quelli in vendita presentare i 3 aventi maggior
giacenza in magazzino.
Ergo:
• Prodotti catalogati->Prodotti in vendita (domain)
• Prodotti in vendita->3 prodotti aventi maggior
giacenza (application)
15. Scenario 2: ERP
Visualizzare fatture insolute di una business unit
Considerate tutte le fatture, estrarre quelle attinenti
una business unit e tra esse visualizzare tutte quelle
scadute e non pagate a distanza di 30 gg dalla
scadenza
Ergo:
• Fatture emesse -> Fatture della BU
• Fatture BU -> Fatture scadute
• Fatture scadute -> Fatture insolute
16. Scenario 3: CMS
Visualizzare gli ultimi 5 articoli
Considerati tutti gli articoli, dall’elenco di quelli
pubblicati estrarre i 5 più recenti
Ergo:
• Articoli -> Articoli pubblicati
• Articoli pubblicati -> 5 articoli più recenti
17. Scenario 4: CMS (Reloaded)
Ricerca articolo
Considerati tutti gli articoli, dall’elenco di quelli
pubblicati estrarre quelli che soddisfano i criteri di
ricerca
Ergo:
• Articoli -> Articoli pubblicati
• Articoli pubblicati -> articoli che soddisfano la
query
18. Scenario 5: e-commerce (strikes back)
Ricerca prodotti
Considerati tutti i prodotti catalogati, dall’elenco di
quelli in vendita estrarre quelli che soddisfano i criteri
di ricerca.
Ergo:
• Prodotti catalogati->Prodotti in vendita
• Prodotti in vendita-> prodotti che soddisfano la
query
19. La «Q» in CQRS
Cosa hanno in comune questi casi?
• Ogni «filtro» è una regola che «lavora» sul
grafo
• Composizione di regole
• Ogni regola può essere usata in differenti
parti dell’applicazione
Come fare… Senza un «botto» di DTO?
P.S.: Si, «botto» è un ordine di grandezza del S.I. Kilo-
>Mega->Giga->Tera->Peta->Botto
20. Layered Expression Trees (LET idiom)
Facciamo un gioco: facciamo che layer e
servizi si scambino degli
IQueryable<YourFavouriteDomainEntity>
facendo «emergere» la query e specificando
la proiezione solo all’ultimo momento?
21. LET idiom feat. Ubiquitous Language
LET aumenta la pervasività dell’Ubiquitous
Language nel codice:
recommendedProducts = (from p in
CatalogServices.GetAvailableProductsOnSale()
orderby p.UnitsInStock descending
select new ProductDescriptor
{
Id = p.Id,
Name = p.Name,
UnitPrice = p.UnitPrice,
UnitsInStock = p.UnitsInStock,
}).Take(3);
Questa query è «quasi» scritta nel linguaggio del
Domain Expert
25. The Good
Ridotto effort di realizzazione/gestione:
• Nessun DTO (ad eccezione del ViewModel,
che avremmo comunque)
• Modello «database like» facile da ottenere
rapidamente
• Strutturazione «DDD like» della logica
• Se il DBMS è SQL Server, funziona «out of the
box»
Performance:
• Estrazione «all at once» di tutti e soli i dati
necessari
26.
27. The Bad
Unit testing «acrobatico»:
• In pratica, LINQ 2 Objects (o
similari)
• In ogni caso, è un LINQ provider
differente :-/ #meh
28.
29. The Ugly
• NHibernate è praticamente
inutilizzabile: il LINQ provider
«scoppia»
• Ogni LINQ provider è una storia a sè
31. LET idiom, v2
Se:
• dovessimo realizzare una applicazione e
non un framework
• per noi gli idiomi fossero «belli belli belli
in modo assurdo» (cit.)
…Allora potremmo rimpiazzare i servizi
con degli extension methods ad hoc
32. LET idiom, v2
public static class OutboundInvoiceExtensions
{
public static IQueryable<OutboundInvoice> RetrieveExpiredOutboundInvoices(this
IQueryable<OutboundInvoice> query)
{
var expiredInvoices = from i in query.RetrieveUnpaidOutboundInvoices()
where i.PaymentDueDate < DateTime.Now
select i;
return expiredInvoices;
}
public static IQueryable<OutboundInvoice> RetrieveUnpaidOutboundInvoices(this
IQueryable<OutboundInvoice> query)
{
var unpaidInvoices = from i in query
where i.Status == "E"
select i;
return unpaidInvoices;
}
public static IQueryable<OutboundInvoice> FilterByBusinessUnit(this
IQueryable<OutboundInvoice> query, int businessUnitId)
{
var invoices = from i in query
where i.JobOrder.BusinessUnit.OrganizationID == businessUnitId
select i;
return invoices;
}
}
33. LET2 feat. Fluent Ubiquitous Language
Sembra Specification ma non è, serve a darti l’allegria! (semi cit.)
expiredOutboundInvoices = from i in
ReadModelFacade.OutboundInvoices
.FilterByBusinessUnit(businessUnitId)
.RetrieveExpiredOutboundInvoices()
orderby i.PaymentDueDate
select new
SummaryViewModel.OutboundInvoice
{
Id = i.ID,
CustomerName = i.Customer.Name,
PaymentDueDate = i.PaymentDueDate.Value,
TotalAmount = i.TotalPrice,
JobOrderName = i.JobOrder.Name,
Code = i.Code
};
35. Bibliografia “pratica”
[DDD] Domain Driven Design, Eric Evans ,
Addison-Wesley
[NAAE] Microsoft .NET: Architecting
Applications for the Enterprise, Andrea
Saltarello & Dino Esposito, Microsoft Press
CQRS, Task Based UIs, Event Sourcing agh!,
Greg Young
[NSK] NSK, http://nsk.codeplex.com
Mio blog ENG,
http://blogs.ugidotnet.org/mrbrightside