Professional Documents
Culture Documents
Domain Driven Design Krok Po Kroku Część IVa: Skalowalne Systemy W Kontekście DDD - Architektura Command-Query Responsibility Segregation (Stos Write)
Domain Driven Design Krok Po Kroku Część IVa: Skalowalne Systemy W Kontekście DDD - Architektura Command-Query Responsibility Segregation (Stos Write)
Domain Driven Design Krok Po Kroku Część IVa: Skalowalne Systemy W Kontekście DDD - Architektura Command-Query Responsibility Segregation (Stos Write)
Sawomir Sobtka
66 /4 . 2012 . (4) /
DOMAIN DRIVEN DESIGN KROK PO KROKU
Z uwagi na obszerno merytoryczn, czwart cz serii po- Jeeli natomiast chcemy wywietli na ekranie dane, np.
dzielono na dwa osobne artykuy: niniejszy powicony Rozka- dane przekrojowe w postaci tabelki (wszyscy wiemy, e dla
zom oraz cz, ktra ukae si w przyszym miesicu i bdzie klienta biznesowego najwaniejsze s tabelki, w ktrych mona
powicona Kwerendom. przestawia kolejno ich kolumn), to narzdzie typu ORM nie
jest najlepszym rozwizaniem tego problemu. W tym wypadku
POTRZEBA SEPARACJI na kadym etapie postpujemy nieracjonalnie:
Projektujc model danych, zwykle wybieramy podejcie Relacyj- PP Pobieramy z ORM list obiektw (zamapowanych na cae ta-
ne i skupiamy si na wsparciu dla modelu domenowego - czyli belki w bazie), gdy potrzebuj na ekranie jedynie kilku ko-
dymy do Trzeciej Postaci Normalnej. Posta ta, ze wzgldu na lumn z kadej tabelki (dla bazy nie robi to rnicy, ale w
brak redundancji, jest optymalna z punktu widzenia modyfikacji przypadku komunikacji sieciowej zaczniemy odczuwa skut-
danych, zatem poyteczna dla operacji typu Rozkaz. ki wydajnociowe tej decyzji),
Natomiast posta ta w przypadku operacji typu Kwerenda PP Mam moliwo korzystania z mechanizmu Lazy Loadingu,
skutkuje pojawianiem si iloczynw kartezjaskich (JOIN w ktry nie ma sensu dla operacji typu "pobierz dane do wy-
SQL). Dodatkowo Agregaty mog zawiera dane zupenie nie- wietlenia" i prowadzi do dramatycznego problemu z wydaj-
istotne w kontekcie danej Kwerendy. noci N+1 Select Problem,
PP Silnik mapera wykonuje niepotrzebne operacje zwizane ze
Stosowalno Object-relational Mapper wsparciem dla Lazy Loading i Dirty Checking, ktre nie bd
wykorzystywane,
wiat relacyjny na obiektowy mapujemy po to, aby: PP Zdradzam model biznesowy warstwie prezentacji. By
moe w prostych aplikacjach z prezentacj w technologii
PP Pobiera w wygodny sposb obiekty biznesowe - wraz z wy- webowej (ta sama maszyna pobiera i prezentuje dane) nie
godnymi mechanizmami typu Lazy Loading, jest to problem - dodatkowo zyskujemy produktywno w
PP Wykonywa na nich operacje biznesowe zmieniajce ich stan pracy. Ale jeeli klienty s zdalne (np. Android)? Zdradza-
- moemy tutaj tworzy zarwno anemiczne encje modyfi- nie modelu domenowego wie si z drastycznym spadkiem
kowane przez serwisy, jak rwnie projektowa prawdziwe bezpieczestwa (wsteczna inynieria) oraz z koniecznoci
obiekty modelujce reguy i niezmienniki biznesowe (styl koordynacji prac zespow pracujcych nad "klientem" i
Domain Driven Design), "serwerem", i zapewnianiem kompatybilnoci starszych
PP Utrwala stan obiektw biznesowych - stan, ktry zmieni wersji klientw. Co prawda jest to kwestia oczywista, ale
si w poprzednim kroku (korzystajc z wygodnych mechani- w materiaach ewangelizacyjnych popularnych platform
zmw wykrywania "brudzenia" i mechanizmu kaskadowego korporacyjnych mona znale nawoywanie do takich
zapisu caych grafw obiektw). uproszcze,
PP Pracujemy na puli pocze zestawionej w trybie READ-WRI-
Jeeli stosujemy ORM (np. Java Persistence API) do tych klas TE, gdy wystarczajcy jest tryb READ wikszo baz da-
problemw, to uywamy odpowiedniego motka do odpowied- nych optymalizuje dziaanie w tym trybie.
niej klasy problemu. Czyli pobieramy kilka obiektw bizneso-
wych (Agregatw), zmieniamy jego stan, zapisujemy go.
Piszc zmieniam stan, nie mam na myli "edytuj, podpina- WARSTWY S DOBRE, ALE DWA
jc pod formularz". Mam na myli logik aplikacji (modelujc
STOSY WARSTW S JESZCZE LEPSZE
Use Case/User Story), ktra modyfikuje mj obiekt biznesowy
(uruchamiajc jego metody biznesowe lub settery, jeeli jest W dotychczasowych artykuach opieralimy architektur aplika-
on anemiczny). Na Listingu 1 widzimy przykad z poprzedniej cji na stylu warstwowym, wprowadzajc warstwy:
czci serii przypomnijmy: Order i Invoice to persystentne
Agregaty: PP prezentacji,
PP logiki aplikacji,
Listing 1. Serwis Aplikacyjny pl.com.bottega.erp.sales.
PP logiki domenowej (z ew. podziaem na 4 poziomy modelu:
application.services.Purchase Service operujcy na kilku
persystentnych Agregatach Capability, Operations, Policy, Decission Support),
PP Infrastruktury.
public class PurchaseService{
//...
public void approveOrder(Long orderId) { Architektura Command-query Responsibility Segregation
Order order = orderRepository.load(orderId);
przedstawiona na Rysunku 1 rozdziela odpowiedzialno kodu,
//sample: Specification Design Pattern na dwa wyranie stosy warstw. Stos Write zawiera opisane
Specification<Order> orderSpecification =
powyej warstwy i obsuguje omawiane wczeniej polecenia
generateSpecification(systemUser);
if (!orderSpecification.isSatisfiedBy(order)) typu Command operacja biznesowa i zmiana stanu systemu.
throw new OrderOperationException("Order does not Drugi stos Read jest mniej zoony i zawiera serwisy wyszu-
meet specification", order.getEntityId());
kujce dane.
order.submit(); Separacja stosw nie dotyczy jedynie kodu. Problemy wy-
Invoice invoice = invoicingService.issuance(order, dajnociowe opisane w sekcji Potrzeba separacji mog (ale
generateTaxPolicy(systemUser)); nie musz o czym w dalszej czci) wymc decyzj o rozdzia-
invoiceRepository.save(invoice); le modelu danych. Model odpowiedni do operacji biznesowych
orderRepository.save(order); zwykle nie jest odpowiedni do szybkiego serwowania danych
}
} np. na potrzeby prezentacji.
/www.programistamag.pl/ 67
INYNIERIA OPROGRAMOWANIA
Styl Serwisowy
Styl Serwisowy omawialimy w poprzednich czciach serii, dla public boolean equals(Object obj) {
przypomnienia odsyam do Listingu 1. Transakcyjno i bez- if (obj instanceof SubmitOrderCommand) {
SubmitOrderCommand command = (SubmitOrderCommand) obj;
pieczestwo zostay wprowadzone poprzez mechanizmy AOP return orderId.equals(command.orderId);
dziki zastosowaniu Adnotacji interpretowanych przez Spring }
Framework. return false;
}
Styl Command & Command Handler @Override
public int hashCode() {
return orderId.hashCode();
Alternatyw dla kadej z metod Serwisowych jest para: Com- }
mand i Command Handler. Jak wida na Listingach 2 i 3 styl ten
polega na rozdzieleniu klasycznego Wzorca Projektowego Com- }
mand na 2 klasy. Command przesyany przez Tier Kliencki
zawiera jedynie parametry dania. Natomiast odpowiadajcy Wsplny punkt miejsce na odwracanie
mu Command Handler zawiera logik obsugujc Command. kontroli
Dziki temu w odrnieniu od klasycznego Wzorce Command,
aplikacje klienckie nie maj dostpu do kodu logiki aplikacyjnej Aplikacje klienckie komunikujc si z Serwerem, wysyaj
ma to oczywisty wpyw na bezpieczestwo (dekompilacja) i Command na wsplny punktu dostpowy, przedstawiony na Li-
oglny coupling moduw. stingu 4 oraz zilustrowany na Rysunku 2.
Oglne zasady tworzenia Command Handlerw pozostaj Wsplny punkt dostpowy pozwala na wprowadzenie intere-
takie same jak tworzenia metod Serwisw Aplikacyjnych oma- sujcych i poytecznych operacji dodatkowych wykonywalnych
wianych w poprzednich czciach. Przedstawiony na Listingu 3 podczas obsugi polecenia. Przykadowo:
Command Handler jest odpowiednikiem Serwisu Aplikacyjnego
z Listingu 1. PP Sprawdzenie, czy dany Command jest duplikatem (przyka-
dowo atak DOS) jeeli tak, to nastpuje jego odrzucenie.
Listing 2. Command pl.com.bottega.erp.sales.application.
Jako duplikat traktujemy Command oznaczony odpowiedni
commands.SubmitOrderCommand polecenie zatwierdzenia zam-
wienia niosce parametry z warstwy prezentacji adnotacj oraz przechowywany w rejestrze ostatnio obsugi-
wanych polece (ostatnio, czyli w czasie x milisekund lub w
@SuppressWarnings("serial")
@Command(unique=true)
obszarze x MB pamici lub w iloci x). Przykadowo dodanie
public class SubmitOrderCommand implements Serializable{ kilkakrotnie tego samego produktu do zamwienia nie b-
private final Long orderId; dzie traktowane jako duplikat (pozwlmy klientom wydawa
pienidze), ale zatwierdzenie tego samego zamwienia wi-
public SubmitOrderCommand(Long orderId) {
this.orderId = orderId; cej ni raz jest niepodane. Dziki mechanizmowi historii
} polece moemy odrzuca niepodane duplikaty bez po-
public Long getOrderId() { trzeby sigania do bazy po dane biznesowe.
return orderId; PP Sprawdzenie, czy dany Command jest oznaczony jako asyn-
}
chroniczny jeeli tak, to wwczas odkadamy jego wyko-
@Override nanie np. do Kolejki.
68 /4 . 2012 . (4) /
DOMAIN DRIVEN DESIGN KROK PO KROKU
@CommandHandlerAnnotation
public class SubmitOrderCommandHandler implements CommandHandler<SubmitOrderCommand, Void> {
@Inject
private OrderRepository orderRepository;
@Inject
private InvoiceRepository invoiceRepository;
@Inject
private InvoicingService invoicingService;
@Inject
private SystemUser systemUser;
@Override
public Void handle(SubmitOrderCommand command) {
Order order = orderRepository.load(command.getOrderId());
//Domain logic
order.submit();
//Domain service
Invoice invoice = invoicingService.issuance(order, generateTaxPolicy(systemUser));
orderRepository.save(order);
invoiceRepository.save(invoice);
return null;
}
@Component
public class StandardGate implements Gate {
@Inject
private RunEnvironment runEnvironment;
@Override
public Object dispatch(Object command){
if (! gateHistory.register(command)){
//TODO log.info(duplicate)
return null;//skip duplicate
}
if (isAsynchronous(command)){
//TODO add to the queue. Queue should send this command to the RunEnvironment
return null;
}
return runEnvironment.run(command);
}
@Component
public class RunEnvironment {
/www.programistamag.pl/ 69
INYNIERIA OPROGRAMOWANIA
Zdarzenia
W sieci
PP oficjalna strona DDD: http://domaindrivendesign.org
PP wstpny artyku powicony DDD: http://bottega.com.pl/pdf/materialy/sdj-ddd.pdf
PP przykadowy projekt Java (Spring i EJB): http://code.google.com/p/ddd-cqrs-sample/
PP przykadowy projekt .NET: http://cqrssample.codeplex.com
PP Architektura CqRS http://martinfowler.com/bliki/CQRS.html
PP Architektura Porst and Adapters: http://c2.com/cgi/wiki?PortsAndAdaptersArchitecture
70 /4 . 2012 . (4) /