Domain Driven Design Krok Po Kroku Część IVa: Skalowalne Systemy W Kontekście DDD - Architektura Command-Query Responsibility Segregation (Stos Write)

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 5

INYNIERIA OPROGRAMOWANIA

Sawomir Sobtka

Domain Driven Design krok po kroku


Cz IVa: Skalowalne systemy w kontekcie DDD
- architektura Command-query Responsibility Se-
gregation (stos Write)
Czy moliwe jest stworzenie systemu, ktry bdzie charakteryzowa si otwartym
na rozbudow modelem, eleganckim, testowalnym i utrzymywalnym kodem, a jed-
noczenie bdzie przygotowany do skalowania? Czy narzdzia typu Object-relatio-
nal mapper s panaceum na wszystkie problemy persystencji w systemach bizneso-
wych? Czy baza relacyjna to zawsze najlepszy pomys na przechowywanie danych?
Na te i inne pytania odpowiemy sobie w kolejnej odsonie naszej serii.

WSTP Plan serii


Po omwieniu oglnej idei DDD, technik zarwno taktycznego, Niniejszy tekst jest czwartym artykuem z serii majcej na celu
jak i strategicznego modelowania oraz sposobw wykorzysta- szczegowe przedstawienie kompletnego zestawu technik mo-
nia popularnych frameworkw przyszed czas na przyjrzenie delowania oraz nakrelenie kompletnej architektury aplikacji
si architekturze systemu jako caoci. Zakadamy, e tworzy- wspierajcej DDD.
my system, co do ktrego postawiono nastpujce wymagania Cz I: Podstawowe Building Blocks DDD;
niefunkcjonalne: Cz II: Zaawansowane modelowanie DDD techniki strategicz-
ne: konteksty i architektura zdarzeniowa;
PP dostp poprzez wiele technologii klienckich: web (w tym Cz III: Szczegy implementacji aplikacji wykorzystujcej DDD
ajax), mobile, web service..., na platformie Java Spring Framework;
PP skalowanie poszczeglnych moduw (w sensie wydzielo- Cz IV: Skalowalne systemy w kontekcie DDD architektura CqRS;
Cz V: Kompleksowe testowanie aplikacji opartej o DDD;
nych jako produkty funkcjonalnoci) w celu zapewnienia
Cz VI: Behavior Driven Development Agile drugiej generacji.
wydajnoci,
PP odporno na awarie pojedynczych moduw system jako PP Java Spring
cao pozostaje stabilny, PP Java EJB 3.1
PP stworzenie platformy otwartej na rozszerzenia poprzez wpi- PP .NET - C#
nanie pluginw.
DWIE KLASY PROBLEMW
W celu spenienia tych wymaga:
Typowy system klasy enterprise obsuguje dwa typy operacji:
PP zrewidujemy tradycyjne podejcie do architektur warstwo-
wych, rozwijajc je w kierunku Architektury Porst & Adapters PP rozkazy wykonujce pewne operacje biznesowe co prowa-
(ramka W sieci), dzi zwykle do modyfikacji pewnej (relatywnie nieduej) iloci
PP zastanowimy si nad kwesti spjnoci danych kiedy jest danych, a w konsekwencji do koniecznoci utrwalenia tyche
ona krytyczna, a w jakich wypadkach moemy pozwoli so- zmodyfikowanych danych,
bie na chwilow niespjno, zyskujc skalowalno, PP kwerendy (w tym kontekcie niekoniecznie w sensie SQL)
PP przyjrzymy si miejscu, jakie Object-relational Mapper zaj- odpytujce cz danych skadowanych w systemie.
muje w systemie oraz kiedy jego wykorzystanie nie jest
racjonalne, Statystycznie rzecz biorc, ilo obsugiwanych rozkazw do ilo-
PP zastanowimy si nad odpowiednimi modelami danych i ich ci obsugiwanych kwerend (w jakim interwale czasu) ma si
formami, jak 1 do kilkustet (tysicy). Innymi sowy odczytw jest o kilka
PP powrcimy do zagadnienia zdarze omawianych w poprzed- rzdw wielkoci wicej ni zapisw miao moemy przyj,
nich czciach, aby wykorzysta ich peen potencja, e dla typowego systemu klasy enterprise moe by 5 rzdw
PP Projekt referencyjny. wielkoci.
Drugim aspektem obok statystycznego - ktry rni oba
Wszystkich tych Czytelnikw, ktrzy ju teraz chcieliby zapo- typy operacji jest aspekt jakociowy modelu danych. Model da-
zna si z kolejnymi zagadnieniami naszej serii, zapraszam do nych potrzebny do wykonania operacji na modelu biznesowym
odwiedzenia strony projektu DDD&CqRS Laven, ktrej adres oraz dane potrzebne do prezentacji wizualnej lub komunikacji z
znajduje si w ramce W sieci. Dostpne wersje: innymi moduami to zwykle inne struktury.

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

STOS WRITE (COMMAND)


Stos Write niejawnie omawialimy w poprzedni
czciach byy to nasze serwisy aplikacyjne. Na-
tomiast w niniejszym artykule bazujc na zaoe-
niach i wiedzy z poprzednich czci wprowadzimy
pewne rozszerzenia.
Stos Write moemy tworzy w dwch stylach:

PP klasycznym w postaci serwisw,


PP opartym na dostosowanym do architektury
klient-serwer Wzorcu Projektowym Command.

Dla projektantw posikujcych si frameworkiem


Spring (lub innym kontenerem wspierajcym Aspect
Oriented Programming) wybr stylu ma wymiar
gwnie estetyczny, poniewa przy pomocy AOP
mona osign w klasycznym podejciu wszystko
to, co oferuje podejcie oparte o styl Command.
Jeeli natomiast nie mamy dostpu do AOP, ww-
czas styl Commad daje nam znaczn przewag dziki moliwo- Rysunek 1. Architektura Command-query Responsibility Segregation dwa sto-
ci silnego odwracania kontroli co zobaczymy ju niebawem... sy warstw. Komponent Gate zosta w szczegach przedstawiony na Rysunku 2

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

Listing 3. Command pl.com.bottega.erp.sales.application.commands.handlers.SubmitOrderCommandHandler polecenie


zatwierdzenia zamwienia niosce parametry z warstwy prezentacji

@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());

Specification<Order> orderSpecification = generateSpecification(systemUser);


if (! orderSpecification.isSatisfiedBy(order))
throw new OrderOperationException("Order does not meet specification", order.getEntityId());

//Domain logic
order.submit();
//Domain service
Invoice invoice = invoicingService.issuance(order, generateTaxPolicy(systemUser));

orderRepository.save(order);
invoiceRepository.save(invoice);

return null;
}

Listing 4. Command pl.com.bottega.cqrs.command.impl.StandardGate wsplny punkt dostpowy

@Component
public class StandardGate implements Gate {

@Inject
private RunEnvironment runEnvironment;

private GateHistory gateHistory = new GateHistory();

@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);
}

private boolean isAsynchronous(Object command) {


if (! command.getClass().isAnnotationPresent(Command.class))
return false;

Command commandAnnotation = command.getClass().getAnnotation(Command.class);


return commandAnnotation.asynchronous();
}
}

Listing 5. rodowisko uruchomieniowe pl.com.bottega.cqrs.command.impl.RunEnvironment pozwalajce na wprowadzenie dodatko-


wych mechanizmw Odwracania Kontroli

@Component
public class RunEnvironment {

public interface HandlersProvider{


CommandHandler<Object, Object> getHandler(Object command);
}

@Inject
private HandlersProvider handlersProfiver;

public Object run(Object command) {
CommandHandler<Object, Object> handler = handlersProfiver.getHandler(command);
//You can add Your own capabilities here: dependency injection, security, transaction management, logging,
profiling, spying, storing commands, etc
Object result = handler.handle(command);
//You can add Your own capabilities here
return result;
}
}

/www.programistamag.pl/ 69
INYNIERIA OPROGRAMOWANIA

Rysunek 2. Komponent Gate stanowicy wsplny punkt dostpowy obsuga Command


wysyanych z aplikacji klienckich
Transakcyjno vs wydajno

Jedn z gwnych zasad projektowania skalowalnych


systemw z wykorzystaniem DDD jest zapisywanie
podczas transakcji jednego Agregatu. Odczyta (w
celu wykonania operacji biznesowej) moemy oczy-
wicie kilka Agregatw, ale zapisa tylko jeden.
Jest to oczywicie jedynie wskazwka, ktr mo-
emy zama w uzasadnionych przypadkach, ale za-
stanwmy si nad konsekwencj jej stosowania.
Jeeli zapisujemy w jednej transakcji kilka Agrega-
tw, to nie moemy sobie pozwoli na ich utrwalanie
w osobnych bazach danych. Zakadajc oczywiie, e
transakcje rozproszone (Double Phase Commit) nie
jest tym, czego potrzebuje architekt projektujcy wy-
sokowydajny i skalowalny system.

Zdarzenia

Co zatem zrobi, jeeli musimy zapisa wicej ni je-


den Agregat podczas obsugi Command? Jeeli wynika
Przyjrzyjmy si teraz Listingowi 5, ktry przedstawia rodo- to po prostu z wymaga. Rozwizaniem mog by Zdarzenia Do-
wisko uruchomienia polecenia. menowe, ktre omawialimy w poprzedniej czci naszej serii.
Odpowiedzialno rodowiska Uruchomieniowego jest pro- Przypomnijmy, e Agregaty emituj zdarzenia niosce informa-
sta: dopasowa do Command odpowiedni ComamndHandler i cje o istotnych w cyklu ycia Agregatw wydarzeniach bizneso-
wykona go. wych. Zdarzenia do tej pory stosowalimy w celu:
Dziki scentralizowanemu punktowi uruchamiania logiki apli-
kacji mamy moliwo dokonania cakowitego odwrcenia kon- PP komunikacji pomidzy osobnymi Bounded Context
troli, bez uycia kontenera typu Spring: PP odoeniu wykonania do kolejki operacji, ktrych natychmia-
stowe wykonanie nie jest krytyczne
PP Rozpoczcie transakcji przed uruchomianiem Hadlera oraz PP otwarciu systemu na Pluginy (ktrymi s Listenery Zdarze,
jej zatwierdzenie albo wycofanie w zalenoci od powodzenia jak rwnie Polityki DDD)
dziaania Handlera.
PP Sprawdzenie uprawnie do wykonania Handlera. Zdarzenia bd nam rwnie potrzebne, aby odwiea dane
PP Wstrzyknicie zalenoci do Handlera. w dane do odczytu w drugim stosie naszej Architektury CqRS.
PP Logowanie poczyna klientw. Zdarze moemy rwnie uywa jako Behwioralnego Modelu
PP Profilowanie dziaania systematu. Danych i zastpi nimi Relacyjn Baz w Stosie Write. Zdarze-
PP Zwrcenie Handlera odpowiedniego dla pracujcego w sys- nia mog by analizowane przez systemy CEP (Complex Events
temie klienta zagadnienie wersjonowania API i systemy Processing) oraz suy jako wektory uczce dla Sztucznych
wspierajce multi-tenant. Sieci Neuronowych. Ale o tym w kolejnej odsonie serii.
PP Dekorowanie Handlerw wzorzec Dekoratora.

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

Sawomir Sobtka slawomir.sobotka@bottega.com.pl

Programujcy architekt aplikacji specjalizujcy si w technologiach Java i efektywnym


wykorzystaniu zdobyczy inynierii oprogramowania. Trener i doradca w firmie Bottega IT
Solutions. W wolnych chwilach dziaa w community jako: prezes Stowarzyszenia Software
Engineering Professionals Polska (http://ssepp.pl), publicysta w prasie branowej i blogger
(http://art-of-software.blogspot.com).

70 /4 . 2012 . (4) /

You might also like