You are on page 1of 141

Univerzitet u Nišu

Prirodno matematički fakultet


Departman za računarske nauke

Master rad

Razvoj modularnih aplikacija u .NET


okruženju

Student: Mentor:
Marko Milenković Prof. dr Marko Petković

Niš, Oktobar 2016.


Sadržaj

1 Uvod 5
1.1 Zahtevi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.2 Odabir tehnologija . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.2.1 Baza podataka . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.2.2 Korisnički interfejs . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.3 Arhitektura aplikacije . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6

2 Dizajn dirigovan domenom (eng. Domain Driven Design – DDD) 8


2.1 Arhitektura DDD Aplikacije . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.1.1 Slojevita arhitektura . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.2 Osnovni gradivni elementi . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.3 Paterni za modeliranje domena . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.3.1 Entiteti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.3.2 Vrednosni objekti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.3.3 Servisi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.3.4 Dogadaji domena . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.3.5 Moduli . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.4 Životni vek objekata u domenu . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.4.1 Agregati . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.4.2 Fabrike . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.4.3 Repozitorijum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

3 Razdvajanje odgovornosti komandi i upita (eng. Command Query Re-


sponsibility Segregation – CQRS) 26
3.1 Strana upita . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3.2 Komandna strana . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

4 Implementacija modela domena 30


4.1 Definisanje agregata . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
4.2 Dizajniranje sloja domena . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
4.2.1 Bazna klasa entiteta . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
4.2.2 Bazna klasa vrednosnih objekata . . . . . . . . . . . . . . . . . . . . 33
4.2.3 Bazna klasa agregata . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4.2.4 Agregat Faktura . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
4.2.5 Vrednosni objekat Novac . . . . . . . . . . . . . . . . . . . . . . . . . 39
4.2.6 Dogadaji domena . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40

5 Implementacija aplikacionog sloja 45


5.1 Komandna strana . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
5.1.1 Komande . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45

1
5.1.2 Hendleri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
5.1.3 Dispečer komande . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
5.2 Strana upita . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
5.2.1 Model podataka . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
5.2.2 Upiti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50

6 Sloj pristupa podacima - komandna strana 54


6.1 Repozitorijum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
6.1.1 Interakcije repozitorijuma . . . . . . . . . . . . . . . . . . . . . . . . 54
6.1.2 Repozitorijum za snimanje podataka . . . . . . . . . . . . . . . . . . 56
6.1.3 Povezivanje . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
6.1.4 Implementacija Repozitorijuma . . . . . . . . . . . . . . . . . . . . . 57
6.1.5 Implementacija bazne klase repozitorijuma . . . . . . . . . . . . . . . 58
6.1.6 Implementacija repozitorijuma ProizvodRepository . . . . . . . . . . . 60
6.2 Jedinica Rada (eng. Unit Of Work – UOW) . . . . . . . . . . . . . . . . . . 62
6.2.1 Implementacija jedinice rada . . . . . . . . . . . . . . . . . . . . . . . 63
6.3 Izlaz na tabelu podataka (eng. Table Data Gateway – TDG) . . . . . . . . . 67
6.3.1 Implementacija bazne TDG klase . . . . . . . . . . . . . . . . . . . . 68
6.3.2 Implementacija ProizvodGateway klase . . . . . . . . . . . . . . . . . 71
6.4 Fabrika . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
6.4.1 Implementacija bazne klasa fabrika . . . . . . . . . . . . . . . . . . . 73
6.4.2 Implementacija fabrike ProizvodFactory . . . . . . . . . . . . . . . . . 74

7 Sloj pristupa podacima - strana upita 76


7.1 Implementacija bazne klase hendlera upita . . . . . . . . . . . . . . . . . . . 76
7.1.1 Implementacija hendlera GetFakturaByIdQueryHandler . . . . . . . . 77
7.1.2 Implementacija bazne TDG klase . . . . . . . . . . . . . . . . . . . . 78
7.1.3 Implementacija FakturaDtoGateway TDG klase . . . . . . . . . . . . 79

8 Prezentacioni sloj – Model-View-ViewModel (MVVM) patern 80


8.1 Uvod . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
8.2 Istorija . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
8.3 Odgovornosti klasa i njihove karakteristike . . . . . . . . . . . . . . . . . . . 80
8.3.1 View . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
8.3.2 View model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
8.3.3 Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
8.4 Saradnja izmedu klasa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
8.4.1 Povezivanje podataka (eng. Data binding) . . . . . . . . . . . . . . . 83
8.4.2 Implementiranje interfejsa INotifyPropertyChanged . . . . . . . . . . 83
8.4.3 Komande . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
8.5 Validacija podataka i izveštavanje o greškama . . . . . . . . . . . . . . . . . 86
8.5.1 Implementiranje interfejsa IDataErrorInfo . . . . . . . . . . . . . . . 86
8.6 Konstrukcija i povezivanje objekata . . . . . . . . . . . . . . . . . . . . . . . 88

2
8.6.1 Povezivanje objekata deklarativno . . . . . . . . . . . . . . . . . . . . 88
8.6.2 Upotreba Šablona . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
8.6.3 Povezivanje objekata programski . . . . . . . . . . . . . . . . . . . . 90
8.7 Implementacija MVVM klasa . . . . . . . . . . . . . . . . . . . . . . . . . . 91
8.7.1 Klasa BasePropertyChanged . . . . . . . . . . . . . . . . . . . . . . . 91
8.7.2 Klasa BaseViewModel . . . . . . . . . . . . . . . . . . . . . . . . . . 92
8.7.3 Klasa BaseNavigableViewModel . . . . . . . . . . . . . . . . . . . . . 92
8.7.4 Klasa BaseNavSearchViewModel . . . . . . . . . . . . . . . . . . . . . 93
8.7.5 Klasa BaseNavEditViewModel . . . . . . . . . . . . . . . . . . . . . . 95
8.7.6 Klasa BaseEditEntityModel . . . . . . . . . . . . . . . . . . . . . . . 97
8.7.7 Klasa VMProizvodSel . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
8.7.8 Klasa VMProizvodNavEdt . . . . . . . . . . . . . . . . . . . . . . . . 99
8.7.9 Klasa MProizvod . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
8.7.10 Klasa CommandQueryService . . . . . . . . . . . . . . . . . . . . . . 101

9 Podela aplikacije u module 103


9.1 Modularne aplikacije . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
9.2 Razvoj modularne aplikacije upotrebom Prism biblioteke . . . . . . . . . . . 105
9.2.1 Osnovni gradivni elemenat modularnih aplikacija - IModule interfejs . 105
9.2.2 Životni ciklus modula . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
9.2.3 Katalog modula . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
9.2.4 Integracija modula u aplikaciju . . . . . . . . . . . . . . . . . . . . . 107
9.2.5 Komunikacija izmedu modula . . . . . . . . . . . . . . . . . . . . . . 107
9.3 Ubrizgavanje zavisnosti (eng. Dependency Injection – DI) . . . . . . . . . . . 107
9.3.1 Upotreba kontejnera - osnovni scenariji . . . . . . . . . . . . . . . . . 108
9.4 Paterni za ubrizgavanje zavisnosti . . . . . . . . . . . . . . . . . . . . . . . . 109
9.4.1 Princip inverzije zavisnosti (eng. Dependency inversion principle – DIP)109
9.4.2 Inverzija kontrole (eng. Inversion of Control – IoC) . . . . . . . . . . 110
9.4.3 Ubrizgavanje zavisnosti (eng. Dependency Injection – DI) . . . . . . 111
9.4.4 Uklapanje pojmova . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
9.5 Implementiranje modula u aplikaciji . . . . . . . . . . . . . . . . . . . . . . . 112
9.5.1 Horizontalni slojevi aplikacije . . . . . . . . . . . . . . . . . . . . . . 112
9.5.2 Definisanje horizontalnih modula aplikacije . . . . . . . . . . . . . . . 114
9.5.3 Vertikalni moduli aplikacije . . . . . . . . . . . . . . . . . . . . . . . 115
9.6 Inicijalizacija aplikacije i povezivanje modula . . . . . . . . . . . . . . . . . . 116
9.6.1 Inicijalizacija modula . . . . . . . . . . . . . . . . . . . . . . . . . . . 117

10 Korisnički interfejs 119


10.1 Koncepti rasporedivanja UI elemenata . . . . . . . . . . . . . . . . . . . . . 119
10.1.1 Shell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
10.1.2 View . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
10.1.3 Kompozitni view . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
10.1.4 Konstruisanje view objekata . . . . . . . . . . . . . . . . . . . . . . . 120

3
10.2 Navigacija . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
10.3 Prism navigacija . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
10.3.1 Statusno bazirana navigacija . . . . . . . . . . . . . . . . . . . . . . . 121
10.3.2 View bazirana navigacija . . . . . . . . . . . . . . . . . . . . . . . . . 122
10.3.3 Implementacija Regiona . . . . . . . . . . . . . . . . . . . . . . . . . 123
10.3.4 Osnovna navigacija u regionu . . . . . . . . . . . . . . . . . . . . . . 124
10.3.5 Uloga view i view model objekta u navigaciji . . . . . . . . . . . . . . 125
10.3.6 Prosledivanje parametara tokom navigacije . . . . . . . . . . . . . . . 126
10.4 Interakcija sa korisnikom . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
10.4.1 Komunikacija sa korisnikom upotrebom interakcionog servisa . . . . . 128
10.5 Implementacija korisničkog interfejsa u aplikaciji Prodavnica . . . . . . . . . 130
10.5.1 Regioni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
10.5.2 Navigacija . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131

11 Zaključak 133

4
1 Uvod
Tokom studiranja izvučavali smo predmete koji su se bavili različitim aspektima softvera.
Takode, u toku izučavanja tih predmeta, imali smo dosta projekata gde je bilo potrebno
izraditi različite aplikacije. Uglavnom, to su bile male aplikacije, koje je izradivao jedan ili
nekolicina studenata. Mene kao studenta zanimalo je kako izgledaju aplikacije iz “stvarnog
sveta”, koje se koriste u ozbiljnim kompanijama. Shodno tome, osnovna ideja ovog rada jeste
izučavanje arhitekture modernih enterprajz aplikacija kroz praktičnu izradu aplikacije na
koju se primenjuju izučeni koncepti. Za potrebe našeg rada izradena je aplikacija Prodavnica
na kojoj su primenjeni principi opisani u njemu. Pretpostavlja se da je čitalac upoznat sa
osnovama jezika c#, osnovnim konceptima objektno orijentisanog programiranja kao i sa
konceptom dizajn paterna.
Rad se sastoji iz više celina u kojima se opisuju različiti delovi arhitekture aplikacije.
Svaka celina u radu sastoji se, uglavnom, iz dva dela: prvi deo zasnovan je na teorijskom
opisu koncepata, a drugi deo na načinu na koji su ti koncepti implementirani u projekat
Prodavnica. Na početku našeg rada definisaćemo zahteve za izradu aplikacije, a potom ćemo
objasniti odabir korišćenih tehnologija za njen nastanak.

1.1 Zahtevi
Pre izrade bilo koje aplikacije potrebno je definisati zahteve. Zahtevi se definišu razgo-
vorom sa klijentom.
Funkcionalni zahtevi su:

• Evidencija podataka o dobavljačima (unos, pregled, izmena);

• Evidencija proizvoda (unos, pregled, pretraga i izmena);

• Evidencija faktura (unos faktura, pregled unetih faktura i izmena faktura, pre nego
što budu kalkulisane);

• Evidencija kalkulacija (kalkulisanje faktura, listanje kalkulacija).

• Kucanje racuna (prva verzija bez povezivanja kase);

• Statistike (top 10 najprodavanijih proizvoda, top 5 dobavljaca, iznos pazara za zadati


period - grafički prikaz).

Tehnički zahtevi su:

• U prvoj fazi izrade potrebno je kreirati desktop aplikaciju koja treba da se izvršava
na lokalnom računaru. Nakon toga, kreira se serverska aplikacija koja će opsluživati
različite zahteve klijenta (desktop, web, mobilne aplikacije...).

• Windows je okruženje u kome se aplikacija izvršava.

5
Naravno, u stvarnom svetu, zahtevi su mnogo složeniji jer sadrže mnogo više detalja za
svaku iznetu stavku. Takode, obično se komunicira sa osobom koja daje zahteve kako bi se
na taj način izbegle moguće nejasnoće tokom rada, i kako bi klijent bio zadovoljan.

1.2 Odabir tehnologija


Kada korisnik iznese svoje zahteve, bira se najpogodnija tehnologija za izradu aplikacije.
S obzirom na to da se radi o izradi desktop aplikacije koja treba da se izvršava na Windows
okruženju (tako da je moguće po potrebi uključiti i isključiti različite module aplikacije), mi
smo odabrali .NET tehnologiju i c# kao jezik za izradu aplikacije.

1.2.1 Baza podataka


Kako je primarna svrha ove aplikacije rad sa podacima, ona, potencijalno, može da radi
sa velikim brojem različitih tipova podataka. Pred nama je zahtev da se pronade način na
koji će se skladištiti podaci sa kojima korisnik radi. Za potrebe našeg rada, odlučili smo da
podatke smeštamo u bazu podataka. Za upravljanje bazom podataka izabrali smo softver
MySql. Razlozi su sledeći: besplatan je, ima veliku bazu korisnika, ne zahteva previše resursa
i postoji veliki broj alata za rad koji olakšavaju izradu aplikacije.

1.2.2 Korisnički interfejs


.NET biblioteka sadrži dve velike biblioteke za izradu korisničkog interfejsa. Windows
Forms je starija biblioteka, ali je dalje zastupljena. Samim tim što je biblioteka starija, znači
da je stabilna i da sadrži malo bagova. Forme se prave ili prevlačenjem kontrola na dizajn
forme ili pisanjem programskog koda.
WPF je novija tehnologija za izradu Windows desktop aplikacija. Forme se mogu praviti
pisanjem programskog koda ili upotrebom XAML koda. XAML predstavlja XML specifi-
kaciju specijalno namenjenu za definisanje korisničkog interfejsa u WPF aplikacijama, koji
olakšava dizajniranje korisničkog interfejsa aplikacije (Postoji specijalna aplikacija za kre-
iranje korisničkog interfejsa koju mogu koristiti dizajneri za izcrtavanje formi – Microsoft
Blend). WPF koristi hardversko ubrzanje za iscrtavanje korisničkih komponenti i veliki broj
novina koje nisu prisutne u staroj biblioteci. Kako je WPF novija biblioteka, korisnički
interfejs aplikacije izraden je upotrebom ove biblioteke.

1.3 Arhitektura aplikacije


U daljem radu, bilo je potrebno definisati arhitekturu aplikacije. Aplikacija mora da
podržava pristup bazi podataka ali takode treba i da bude nezavisna od konkretnog softvera
za rad sa bazom podataka, što se tiče korisničkog interfejsa početni zahtev je desktop aplika-
cija, ali korisnik je izjavio da bi, možda, u budućnosti želeo web aplikaciju. Stoga, aplikacija
treba da ima centralizovanu logiku i slojevitu arhitekturu. Deo za pristup podacima, kao i
deo za prikaz korisničkog interfejsa, treba držati modularnim i opcionim. U ovom delu rada,

6
naišli smo na sledeće probleme: kako definisati slojeve aplikacije, kako ih implementirati i
kako ih, na kraju, povezati. S obzirom na to da se dizajn dirigovan domenom pokazao kao
izrazito popularna filozofija razvoja aplikacija, odručili smo da baš nju proučavamo, a potom
i praktično primenimo prilikom izrade aplikacije Prodavnica.

7
2 Dizajn dirigovan domenom (eng. Domain Driven
Design – DDD)
Dizajn dirigovan domenom (DDD) je filozofija razvoja aplikacija definisana od strane
Erika Evansa [1]. DDD je pristup razvoju softvera koji omogućava timovima efikasnu kon-
strukciju i održavanje kompleksnog softvera. Glavni fokus DDD-a je mapiranje biznis kon-
cepata u softverski kôd. DDD predstavlja veliku temu o kome je napisano više knjiga. U
daljem tekstu opisaćemo samo najvažnije koncepte potrebne za izradu aplikacije.

2.1 Arhitektura DDD Aplikacije


Razvoj aplikacija praćenjem DDD principa ne zahteva upotrebu nijedne specifične ar-
hitekture aplikacije, ali jedna stvar koju arhitektura mora da podrži jeste izolacija logike
domena.
Kako bismo izbegli da kôd preraste u veliki zamršeni špageti kôd, i zbog toga oslabi
integritet i na kraju upotrebljivost modela domena, veoma je bitno da struktura aplikacije
omogućava odvojeno rešavanje tehničkih problema od problema domena. Prezentaciona,
skladišna i logika domena u aplikaciji menjaju se različitom brzinom i iz različitih razloga.
Arhitektura koja razdvaja ove probleme može da se prilagodi promenama bez velikog napora
i neželjenih efekata na nepovezane delove koda.
Dodatno, arhitektura aplikacije mora da abstraktuje kompleksnosti domena izlaganjem
skupa servisa koji skrivaju implementacione detalje domena. Apstraktovanjem na viši nivo
sprečava se da promene u sloju domena utiču na prezentacioni sloj i obrnuto.
Postoji veliki broj načina na koje softverski sitem može da se podeli, ali kroz iskustvo
i konvenciju, industrija je konvergirala, prema slojevitim arhitekturama i, posebno prema
definisanju nekoliko standarnih slojeva. Metafora “raslojavanje” se tako rasprostranjeno
koristi da je njeno značenje intuativno za većinu programera. Osnovni princip je da bilo koji
elemenat u sloju zavisi jedino od drugih elemenata u istom sloju ili od elemenata u slojevima
koji se nalaze “ispod” tog sloja. Komunikacija “na gore” mora da se vrši kroz neki indirektni
mehanizam.
Vrednost slojeva je ta da se svaki specijalizuje na odredeni deo programa. Ova speci-
jalizacija dozvoljava kohezivan dizajn svakog dela i čini ih jednostavnijim za interpretaciju.
Naravno, bitno je izabrati slojeve koji izoluju najvažnije koncepte dizajna. Ponovo, iskustvo i
konvencija su konvergirale u odredeni pravac. Istina, postoji mnogo varijacija. Najuspešnije
arhitekture koriste neku varijaciju sledeća četiri konceptualna sloja:

• UI (prezentacioni sloj). Verovatno, najednostavniji za razumevanje, ovaj sloj je od-


govoran za prikaz informacija korisniku i tumačenje korisnikovih komandi. Ponekada,
umesto čoveka, korisnik može biti druga aplikacija ili sistem.
• Aplikacioni sloj. Definiše zadatke koje aplikacija može da odradi i koordiniše objek-
tima domena kako bi se kompletirao biznis zadatak. Ovaj sloj treba da bude veoma
tanak, ne treba da sadrži biznis pravila, niti znanje o domenu, niti stanje koje je

8
Dijagram 1: Grafički prikaz slojevite arhitekture

potrebno snimiti na izvor podataka, jer su ovo sve zadaci domena. Aplikacioni sloj
upravlja biznis zadatkom i delegira akcije prema domenu. Ne sadrži stanje biznis en-
titeta, ali može da sadrži stanje koje prati trenutni zadatak. Veoma je bitno da se
aplikacioni sloj ne meša sa modelom domena.

• Sloj domena. Ovo je mesto gde biznis logika aplikacije živi i predstavlja srž softvera.
Zadužen je za predstavljanje biznis koncepata, informacija o biznis situacijama i biznis
pravila. Stanje koje reflektuje biznis situaciju koristi se i kontroliše u ovom sloju, dok
se snimanje stanja na izvor podataka prepušta infrastrukturnom sloju.

• Infrastrukturni sloj. Ovo je mesto gde se nalazi sav generalni tehnički i instalacioni
kôd, kao što je: implementacija snimanja objekata u bazu podataka, slanje poruka,
logovanje internog stanja aplikacije i ostali generalni zadaci.

2.1.1 Slojevita arhitektura


Za razliku od tipičnih pogleda na slojevitu arhitekturu, dijagram 2 prikazuje da se u srcu
arhitekture nalazi sloj domena koji sadrži svu biznis logiku. Okružuje ga aplikacioni sloj koji
apstraktuje detalje domena iza aplikacionog interfejsa koji predstavlja upotrebne slučajeve
aplikacije. Logika domena i aplikacioni slojevi su izolovani i zaštićeni od kompleksnosti
klijenata, eksternih biblioteka i infrastrukturnih zadataka.
Kako bi se obezbedilo razdvajanje odgovornosti, sloj domena i aplikacioni sloj (koji se
nalaze u centru arhitekture), ne zavise od drugih slojeva. Sve zavisnosti se kreću unapred,
na taj način da se sloj domena nalazi u srcu aplikacije i nije zavisan od čega drugog, foku-
sirajući se samo na zadatke domena. Aplikacioni sloj jedino zavisi od domena. On upravlja
procesiranjem korisničkog zahteva, delegirajući posao sloju domena.
Naravno, stanje objekata domena mora da se snimi negde. Kako bi se ovo postiglo bez
uplitanja tehničkog koda u sloj domena, aplikacioni sloj definiše interfejse koji omogućavaju

9
Dijagram 2: Sloj domena u centru arhitekture

snimanje objekata domena. Ovi interfejsi se pišu iz perspektive aplikacionog sloja jezikom
i stilom koji su jasni bez specifičnih biblioteka i tehničkih žargona. Infrastrukturni slojevi
onda implementiraju te interfejse. Upravljanje transakcijama zajedno sa ostalim zadacima
(kao što su sigurnost i logovanje) pruža se na isti način.

2.2 Osnovni gradivni elementi


Paterni dobijeni iz najboljih DDD praksi obično se nazivaju taktički DDD paterni. Mnogi
paterni nisu novi, ali Evans je prvi koji ih je grupisao na ovaj način i pomogao programerima
da kreiraju efektivne modele domena. U daljem tekstu opisujemo taktičke paterne koji
predstavljaju gradivne elemente DDD-a.
Uloga taktičnih paterna u DDD-u je da pojednostave upravljanje kompleksnostima koje
proističu iz biznis zahteva i da osiguraju jasnoću ponašanja u okviru modela domena. Ovi
paterni se izgraduju oko čvrstih objektno orijentisanih principa, i veći deo njih nalazi se u
široko rasprostranjenim knjigama o dizajnu [2] i [16].
Svaki patern je dizajniran tako da se njegova odgovornost može svesti na sledeće: može
predstavljati koncept u domenu, kao što su to entiteti ili vrednosni objekati, ili može da
postoji kako bi osigurao da koncepti u domenu ne budu prenatrpani logikom životnog ciklusa
objekata, kao što su to fabrike i repozitorijumi.

2.3 Paterni za modeliranje domena


Sledeći paterni čine logiku i politiku implementacije sloja domena. Oni predstavljaju veze
izmedu objekata, pravila u modelu i vezuju biznis detalje za kôd implementacionog modela.
Ovo su paterni koji predstavljaju elemente modela u kodu.

10
2.3.1 Entiteti
Veliki broj objekata nije fundamentalno definisan svojim atributima, već tokom identi-
teta. Osoba ima svoj identitet, koji se prostire od rodenja do smrti, pa čak i posle smrti.
Fizičke osobine osobe se menjaju, i na kraju nestaju. Lično ime i adresa mogu se promeniti,
pa, čak, i matični broj. Dakle, ne postoji osobina osobe koja je nepromenljiva, a, ipak,
postoji identitet.
Neki objekti nisu primarno definisani po svojim atributima. Oni predstavljaju tok iden-
titeta koji se kreće kroz vreme i često kroz različite reprezentacije. Nekada konceptualno
takav objekat može biti isti sa drugim objektom, iako im se atributi razlikuju. Objekat se
mora razlikovati od ostalih objekata iako je moguće da on ima iste atribute kao što imaju i
neki drugi objekti. Pogrešan identitet može voditi do oštećenja podataka.
Posebnu pažnju treba posvetiti modeliranju i dizajniranju entiteta. Oni imaju životni
vek tokom koga se njihov oblik i sadržina mogu radikalno promeniti, ali identitet mora biti
očuvan. Njihov identitet mora biti definisan tako da možemo efektivno da ih pratimo.
Većina entiteta u softverskom sistemu ne predstavlja osobu ili entitet u uobičajenom smi-
slu te reči. Entitet je svaki objekat koji ima kontinuitet kroz vreme i identifikuje se nezavisno
od atributa koji su bitni za korisnika aplikacije. Zavisno od domena problema, entiteti mogu
predstavljati osobu, grad, automobil, bankovnu transakciju ili neki drugi pojam.
Sa druge strane, nisu svi objekti entiteti. Ovo može biti zbunjujuće za OOP programere
zbog činjenice da objektno orijentisani jezici ugraduju operacije identiteta u svaki objekat
(operator “==”). Ove operacije odreduju da li dve reference pokazuju na isti objekat tako
što uporeduju njihove lokacije u memoriji ili po nekom drugom mehanizmu. U tom smislu,
svaka instanca objekta ima svoj identitet. U domenu .NET runtime okruženja svaka instanca
objekta može biti entitet, ali ovaj mehanizam identiteta ne znači mnogo u drugim domenima
aplikacije(na primer, kada se entitet snimi u bazu podataka ili fajl).
Razmotrimo transakcije u bankarskoj aplikaciji. Dve uplate sa istom količinom novca,
na isti račun, istog dana i dalje predstavljaju dve različite transakcije, tako da one imaju
svoj identitet i predstavljaju entitete. Sa druge strane, iznosi za dve transakcije su verovatno
instance nekog novčanog objekta. Ove vrednosti nemaju identitet jer se ne dobija nikakva
korist ukoliko možemo da razlikujemo dve različite novčane instance koje imaju istu vrednost.
Dva objekta mogu imati isti identitet i ukoliko nemaju iste atribute i čak ne moraju da
predstavljaju istu klasu. Kada korisnik svodi račun svoje čekovne knjižice sa izvodom koji je
dobio iz banke, njegov zadatak je upariti transakcije iz čekovne knjižice i bankovnog izvoda
koje imaju isti identitet, iako su one unešene od strane različitih ljudi, u različito vreme. Broj
čeka služi kao jedinstveni identifikator, bilo da se uparivanje vrši kompjuterskim programom
ili ručno. Uplate i isplate novca, koje nemaju identifikacioni broj, mogu biti varljive, ali isti
princip važi: svaka transakcija je entitet (koja se pojavljuje u najmanje dva oblika).
Uobičajeno je da identitet bude korišćen izvan odredenog softverskog sistema, kao u
slučaju sa bankovnim transakcijama. Nekada je identitet bitan samo u kontekstu sistema,
kao što je to u primeru identiteta procesa operativnog sistema.
Kada se objekat izdvaja po svom identitetu, radije nego po svojim atributima, potrebno
je ugraditi ovo pravilo u definiciju modela. Takode, potrebno je definisati operaciju, koja

11
garantovano vraća jedinstveni rezultat za svaki objekat, verovatno dodavanjem simbola, koji
je garantovano jedinstven. Ovo znači da identifikator može doći izvan sistema.
Identitet nije unutrašnja stvar u sistemu. To je veštački uvedeno značenje, jer je korisno.
U stvari, ista stvar u realnom životu može biti, ali i ne mora, biti predstavljena kao entitet
u modelu domena.
Aplikacija za rezervisanje sedišta na stadijumu može tretirati sedišta i goste kao entitete.
U slučaju kada se kupcu (korisniku) dodeljuje broj sedišta, ono postaje entitet. Njegov
identifikator je u tom slučaju broj sedišta koji je jedinstven na stadijumu. Sedište može da
ima ostale atribute, kao što je njegova lokacija, da li je pogled zaklonjen, cena i tako dalje, ali
jedino broj sedišta, ili jedinstveni red i pozicija, mogu da se koriste za njegovu identifikaciju.
Sa druge strane, ukoliko je dogadaj tipa generalni ulaz, u smislu da vlasnik karte sedi
gde god ima slobodnog mesta, nema potrebe razlikovati pojedinačna sedišta. Jedino je bitan
ukupan broj sedišta. Iako je broj sedišta fizički i dalje urezan na sedištima, nije potrebno
vršiti evidenciju o njima kompjuterskim softverom. U tom slučaju, sedišta ne predstavljaju
entitete i jedinstveni identifikator nije potreban.

2.3.2 Vrednosni objekti


Veliki broj objekata nema konceptualni identitet. Ovi objekti opisuju karakteristike nekog
objekta.
Kada dete crta, njega zanima boja markera koja mu je potrebna ili koliko je vrh markera
oštar. Ali, ukoliko postoje dva markera iste boje i istog oblika, detetu neće biti bitno koji
marker koristi od ta dva. Ukoliko mu se dogodi da marker izgubi, te zameni drugim iste boje,
iz novog paketa, ono može nastaviti sa crtanjem ne brinući o tome što je marker zamenjen.
Ukoliko pitamo dete o crtežima na frižideru, dete brzo može da odvoji njegove crteže od
crteža koje je nacrtala njegova sestra. On i njegova sestra imaju korisne identitete, kao i
njihovi završeni crteži. Zamislimo koliko bi komplikovano bilo ukoliko bi dete moralo da zna
za svaku liniju na crtežu kojim markerom je nacrtana. Crtanje više ne bi bilo dečja igra.
Zato što su najupadljiviji objekti u modelu obično entiteti, i zato što je bitno pratiti iden-
titet svakog entiteta, prirodno je razmotriti opciju dodeljivanja identiteta svakom objektu u
domenu. Postoje neka radna okruženja koja upravo to i rade.
Sistem treba da se nosi sa praćenjem identiteta, i zbog toga nije moguće uvesti veliki broj
optimizacija performansi. Potreban je analitički napor za definisanje smislenih identiteta i
razraditi sigurne načine za praćenje identiteta objekata u distribuiranim sistemima ili u bazi
podataka. Jednako važno, postavljanje veštačkih identiteta dovodi u zabludu i komplikuje
model, forsirajući sve objekte u isti kalup.
Praćenje identiteta entiteta je esencijalno, ali dodavanje identiteta na objekte kojima
to nije potrebno može škoditi performansama sistema i može zamutiti model čineći da svi
objekti izgledaju isto.
Ukoliko razmišljamo o ovim objektima kao o objektima koji nemaju identitet, onda ih
možemo dodati u listu alata. U stvari, ovi objekti imaju svoje karakteristike i značaj u
modelu. Ovo su objekti koji opisuju stvari.

12
Objekat koji predstavlja opisan aspekt domena bez konceptualnog identiteta zove se
VREDNOSNI OBJEKAT. Vrednosni objekti se instanciraju kako bi predstavili elemente
dizajna koji su nama bitni ne u smislu koga ili šta predstavljaju, već u smislu njihove namene.
Boje su primer vrednosnih objekata koji se nalaze u osnovnim bibliotekama skoro svih
razvojnih sistema. Takodje, stringovi i brojevi su isto vrednosni objekti. Ovi osnovni pri-
meri su jednostavni, ali vrednosni objekti ne moraju da budu takvi. Na primer, program
koji sadrži paletu za mešanje boja može imati bogat model u kome objekti boje mogu biti
kombinovani kako bi dobili nove boje. Ovi objekti mogu imati kompleksne algoritme za
medusobnu saradnju koji omogućavaju kreiranje novih rezultujućih vrednosnih objekata.
Vrednosni objekat može da bude sačinjen od drugih objekata. U softveru za dizajniranje
planova kuća, objekat može biti kreiran za svaki stil prozora. Stil prozora može biti element
objekta prozora, zajedno sa visinom i dužinom, kao i pravila koja upravljaju načinom pro-
mena vrednosti atributa i njihovim kombinovanjem. Prozori predstavljaju vrednosne objekte
sačinjene od drugih vrednosnih objekata. Takode, oni se ugraduju u veće elemente plana,
kao što su objekti zidovi.
Vrednosni objekti mogu referencirati entitete. Na primer, ukoliko upitamo servis mapa za
rutu puta od Niša do Beograda, moguće je izvesti objekat ruta povezivanjem Niša i Beograda
autoputem E75. Objekat ruta predstavlja vrednosni objekat, iako referencira tri objekata i
svaki je entitet.
Vrednosni objekti se često prosleduju kao parametri u porukama izmedu objekata. Oni
su često prolazni – kreiraju se za neku operaciju i, potom, uništavaju. Vrednosni objekti
često se koriste kao atributi entiteta. Osoba može biti modelirana kao entitet sa identitetom,
ali ime osobe predstavlja vrednost.
Atributi koji čine vrednosni objekat trebalo bi da čine konceptualnu celinu. Na primer,
ulica, grad i poštanski kod ne bi trebalo da budu posebni atributi objekta osoba. Oni su
svi deo objekta adresa, što dalje pojednostavljuje objekat osoba i čini vrednosni objekat
povezanim.
Vrednosni objekti trebalo bi da budu nepromenljivi. Ukoliko je potrebno promeniti neki
atribut u vrednosnom objektu, potrebno je, onda, kreirati novi vrednosni objekat koji je
klon postojećeg objekta sa izmenjenom vrednošću. Ovo pojednostavljuje programski kôd i
rukovanje vrednosnim objektima.

2.3.3 Servisi
U nekim situacijama, najčistiji i najpraktičniji dizajn uključuje operacije koje konceptu-
alno ne pripadaju nijednom objektu. Umesto da forsiramo problem, možemo pratiti njegove
konture i uvesti servise u modelu.
Postoje bitne operacije u domenu koje ne mogu naći prirodno mesto u entitetu ili vred-
nosnom objektu. Neke od njih suštinski su aktivnosti ili akcije, nisu objekti, ali kako je
objektna paradigma najviše u upotrebi, programeri pokušavaju da ih smeste u objekte.
Česta greška je prerano odustajanje od ugradivanja ponašanja u odgovarajući objekat, jer
se time postepeno klizi prema proceduralnom programiranju. Takode, kada se operacija, koja
ne odgovara definiciji objekta forsirano ubaci u objekat, on gubi svoju konceptualnu jasnoću

13
i postaje teško razumljiv i održiv. Kompleksne operacije mogu lako pretrpati jednostavne
objekte skrivajući njegovu pravu ulogu. Zato što te operacije često okupljaju više objekata
domena, uključuju njihovo koordinisanje i puštanje u pogon, dodatna odgovornost kreira
dodatne zavisnosti izmedu svih tih objekata, komplikujući koncepte koji bi trebalo da budu
nezavisno shvaćeni.
Ponekada se servisi maskiraju kao objekti modela, pojavljuju se kao objekti bez drugog
značenja osim što predstavljaju operaciju koju izvršavaju. Ovakvi “izvršioci” obično dobijaju
nazive koji se završavaju sa “Manager”. Oni nemaju stanje niti bilo kakvo značenje u domenu
izvan operacije koju sadrže. Ipak, ovo rešenje, barem, daje dom različitim ponašanjima bez
mešanja u objekte realnog modela.
Neke koncepte iz domena nije prirodno modelirati kao objekte. Forsiranjem da odgova-
rajuća funkcionalnost bude odgovornost entiteta ili vrednosnih objekata narušava se definicija
objekta i rezultuje dodavanjem besmislenih veštačkih objekata.
Servis je operacija, koja se nudi kao interfejs koji je nezavisan u modelu, bez enkapsuli-
ranja stanja. Servisi su uobičajen patern u tehničkim radnim okruženjima, ali oni, takode,
mogu da se primene i na sloj domena.
Ime servisa označava vezu sa drugim objektima. Za razliku od entiteta i vrednosnih
objekata, servisi su definisani u terminima akcija koje mogu izvrše za klijenta. Preciznije,
servisima se daju imena na osnovu aktivnosti koje oni vrše, i to je razlog što su oni, najčešće,
naslovljeni glagolom, a ne imenicom. Servis bi i dalje trebalo da ima definisanu odgovoronost,
te da zajedno sa njom i interfejsom, koji ga čine, bude definisan kao deo modela domena.
Imena operacija treba da proističu iz biznis-jezika ili ukoliko taj termin ne postoji potrebno
ga je, onda, dodati u biznis jezik. Parametri i rezultati su objekti domena.
Servise treba koristiti promišljeno kako ne bi dopustili curenje logike iz entiteta i vred-
nosnih objekata. Kada je operacija bitan koncept u domenu, servis predstavlja prirodni deo
dizajna. Deklarisanjem operacije kao servisa, umesto kreiranja lažnog objekta u domenu
koji zapravo ne predstavlja ništa, čini kôd jednostavnim za razumevanje.
Dobro definisani servis ima sledeće tri osobine:
1. Operacija se odnosi na koncept u domenu koji nije prirodni deo entiteta ili vrednosnog
objekta.
2. Interfejs je definisan u terminima elemenata modela domena.
3. Operacija nema stanje.
Bez stanja, ovde znači da bilo koji klijent može koristiti bilo koju instancu odredenog
servisa, bez brige o istoriji njenog korišćenja. Izvršavanje servisa koristi informacije koje
su globalno dostupne, servisi mogu čak i promeniti globalne informacije, ali servisi ne drže
stanje koje može uticati na njihovo ponašanje, kao što može većina objekata u domenu.

2.3.3.1 Upotreba servisa i različite vrste servisa


Do sada smo se fokusirali samo na servise koji se koriste u sloju domena, ali servisi se
koriste i na drugim mestima. Potrebno je voditi računa i razlikovati servise koji pripadaju

14
u sloju domena od onih u drugim slojevima i potrebno je rasporediti odgovornosti kako bi
to razdvajanje bilo oštro.
Većina servisa koji su navedeni u litaraturi su čisti tehnički servisi i pripadaju infrastruk-
turnom sloju. Servisi domena i aplikacioni servisi saraduju sa infrastrukturnim servisima.
Na primer, banka može imati aplikaciju koja šalje e-mail klijentu kada njegov balans padne
ispod odredene granice. Interfejs koji okružuje e-mail sistem, predstavlja servis u infrastruk-
turnom sloju.
Može biti teže razlikovati aplikacione servise od servisa domena. Bankarska aplikacija
može biti odgovorna za transfer novčanih sredstava. Ukoliko je servis osmišljen da napravi
odgovarajuća zaduženja i uplate sredstava u transferu, ta odgovornost pripada sloju domena.
Transfer novčanih sredstava ima značenje u biznis jeziku banke i uključuje fundamentalnu
biznis logiku. Tehnički servisi ne bi trebalo da imaju nikakvo biznis značenje.
Veliki broj servisa domena i aplikacionih servisa je izgradeno nad populacijom entiteta
i vrednosnih objekata, ponašajući se kao skripte koje organizuju podencijal domena kako
bi se odradila neka konkretna akcija. Entiteti i vrednosni objekti često su previše dobro
gradeni kako bi obezbedili konvencionalni pristup mogućnostima sloja domena. Tako dobi-
jamo veoma lepu liniju razdvajanja odgovornosti izmedu sloja domena i aplikacionog sloja.
Na primer, ukoliko bankarska aplikacija ima mogućnost konvertovanja i eksportovanja trans-
akcija u eksel fajl, onda je za taj eksport zadužen aplikacioni servis. Ne postoji biznis značaj
da formiranje eksel fajlova bude u domenu bankarstva, i ne postoje biznis pravila uključena
u to.
Sa druge strane, opcija za prenos nočanih sredstava sa jednog računa na drugi račun
pripada servisu domena zato što sadrži značajna biznis pravila (zaduživanje i uplaćivanje
novčanih sredstava sa jednog na drugi račun) i zato “transfer sredstava” predstavlja smisleni
bankarski termin. U ovom slučaju, servis ne sadrži mnogo logike. Potrebno je samo izvršiti
instrukcije nad dve instance objekta računa koje obavljaju većinu posla. Ali, ako bismo
stavili operaciju transfera u objekat računa bilo bi čudno, zato što operacija uključuje dva
računa i još neka globalna pravila.
Moguće je kreirati objekat novčanog transfera kako bismo predstavili dva entiteta plus
pravila i istoriju transfera. Ali, i dalje nam ostaju pozivi servisa u medubankarskoj mreži.
Povrh toga, u većini razvojnih sistema, čudno je napraviti direktan interfejs izmedu objekata
domena i eksternih resursa. Možemo prerušiti takve eksterne servise fasadom koja preuzima
ulaze u terminima modela i vraća objekte transfera novca kao rezultat. Ali, koje god po-
srednike da koristimo, čak i da oni pripadaju internom sistemu, ti servisi nose odgovornost
van domena transfera novca.
Podela servisa u slojeve na osnovu bankarskog primera:
• Aplikacioni sloj - Aplikacioni servis za transfer novčanih sredstava obavlja sledeće
zadatke.

– Parsira ulaz (npr. XML zahtev).


– Šalje poruku servisu domena za obavljanje zadatka.
– Čeka na potvrdu.

15
– Odlučuje da li je potrebno poslati poruku upotrebom infrastrukturnog servisa.

• Sloj domena - Servis domena zadužen za transfer novčanih sredstava obavlja sledeće
zadatke:

– Saraduje sa objektima računa tako što pravi odgovarajuća zaduženja i uplate.


– Vraća poruku o rezultatu operacije (transfer je dozvoljen ili nije).

• Infrastrukturni sloj - Infrastrukturni servis šalje poruku sa obaveštenjem.

– Šalje e-mail, pismo, i ostale vrste komunikacije odredene od strane aplikacije.

2.3.3.2 Granularnost
Ovaj patern, takode, je vredan u smislu kontrolisanja granularnosti pristupa interfejsima
sloja domena, kao i razdvajanja klijenata od entiteta i vrednosnih objekata.
Srednje granulisani servisi bez stanja mogu se lako ponovo upotrebiti u velikim sistemima
zato što enkapsuliraju značajnu logiku iza jednostavnog interfejsa. Detaljno granulisani
servisi mogu voditi ka neefikasnom prosledivanju poruka u distribuiranim sistemima i mogu
doprineti curenju znanja iz domena u aplikacioni sloj. Nasuprot tome, razumno uvodenje
servisa domena može pomoći održavanju jasne linije izmedu slojeva.

2.3.4 Dogadaji domena


Ovaj patern nije originalno uveden u knjigu Erika Evansa [1], ali i on je sam rekao da
žali zbog toga i da ovaj patern, takode, predstavlja osnovni gradivni element DDD-a.
DDD prakticionisti su otkrili da mogu bolje razumeti domen problema učenjem o dogadajima
koji se dešavaju u okviru domena.
Odgovornost entiteta je praćenje njegovog stanja kroz njegov životni vek. Ali, ukoliko
je potrebno znati uzrok promene stanja, može biti teško objasniti kako je sistem došao do
trenutnog stanja. Audit tragovi mogu omogućiti praćenje, ali oni, obično, nisu namenjeni za
upotrebu u implementaciji biznis logike sistema. Istorija promena entiteta može dopustiti
pristup prethodnim stanjima, ali one ignorišu značenje tih promena.
Poseban, povezan skup problema izranja u distribuiranim sistemima. Stanje distribuira-
nih sistema ne može da bude konzistentno sve vreme. Agregati se drže čvrsto sve vreme, dok
se ostale promene dešavaju asinhrono. Kako se promene propagiraju kroz čvorove u mreži,
može biti teško rešiti višestruka ažuriranja koja stižu izvan redosleda ili iz različitih izvora.
Dogadaji domena označavaju nešto što se dogodilo u domenu, a što je bitno za biznis.
Moguće je koristiti dogadaje kako bi se snimile promene na modelu u audit stilu, ili kori-
stiti dogadaje za komunikaciju izmedu agregata. Često operacija na jednom agregatu može
rezultovati bočnim efektima koji su izvan granica agregata. Drugi agregati u modelu mogu
osluškivati odredene dogadaje i preduzeti odredene akcije.
Na primer, razmotrimo korpu u okviru sajta za elektronsku kupovinu. Svaki put kada
kupac smesti stavku u korpu, bitno je ažurirati preporučene proizvode koji su prikazani na

16
sajtu. U ovom scenariju, kada se dogadaj domena podigne sa detaljima korpe, preporuke za
kupca se menjaju. Bez dogadaja u domenu, potrebno je eksplicitno spojiti modul korpe sa
modulom preporučenih proizvoda. Dogadaji u domenu daju prirodniji način komunikacije i
fokusiraju se na vreme.
Dogadaji su obično nepromenljivi, budući da su oni samo zapis nečega što se dogodilo u
prošlosti. Dodatno, dogadaji domena, tipično, sadrže podatak vremenu stvaranja dogadaja
i identitete entiteta uključenih u dogadaj.
Kada se dogadaji isporuče zainteresovanim strankama, lokalnim ili eksternim sistemima,
oni se koriste kako bi se ispunila eventualna konzistentnost u nekim sistemima. Upotrebom
dogadaja moguće je ukloniti potrebu za globalnim transakcijama kada se komunicira sa
eksternim sistemima.
Dogadaji domena obično se identifikuju prilikom razgovora sa biznis ekspertom. Bitno
je obratiti pažnu na reči koje počinju sa: “Kada...”, “Ukoliko se dogodi...”, “Obavesti me
ukoliko...”, i slične. Zavisno od organizacione kulture moguće je imati drugačije fraze.

2.3.5 Moduli
Moduli su stari, dobro utvrdeni dizajn-elementi. Postoje i tehnički razlozi za uvodenje
modularnosti, ali primarna motivacija za to je kognitivno preopterećenje. Moduli dozvolja-
vaju dva pogleda na model: moguće je pogledati detalje modula kao samostalnu celinu, ali i
videti relacije izmedu modula bez zadiranja u detalje svakog od njih. Moduli u sloju domena
treba da nastanu kao smisleni deo modela, pričajući o domenu na većem obimu.
Svi koriste module, ali mali broj programera ih tretira kao punopravne članove modela.
Kôd se razbija na različite načine u kategorije, od pogleda na tehničku arhitekturu sistema
do pogleda na konkretne programerske zadatake. Čak i programeri, koji dosta refaktorišu
kôd teže da module, kreirane u ranoj fazi projekta, ostave netaknutim.
Istina, trebalo bi imati slabu zavisnost izmedu modula i veliku saradnju unutar njih.
Zahtev za slabu meduzavisnost izmedu modula i veliku saradnju objekata unutar modula,
možda, zvuči kao tehnička metrika koja bi se odredivala mehanički zasnovano na raspodelama
veza i medusobnim interakcijama. Ipak, ne deli se samo kôd u module, već se dele i koncepti.
Postoji granica o tome koliko stvari osoba može imati u glavi u istom trenutku (zbog toga
je i zahtev za slabu meduzavisnost).
Slaba meduzavisnost i velika saradnja su generalni dizajn-principi koji se primenjuju na
individualne objekte i module, ali oni su posebno bitni za modeliranje na višem nivou.
Kadgod se dva elementa u modelu razdvoje u različite module, veza izmedu njih postane
manje direktna, što povećava poteškoće pri razumevanju njihovog mesta u dizajnu. Mala
zavisnost izmedu modula minimizira ove poteškoće i omogućava analiziranje sadržaja jednog
modula sa minimalnim referenciranjem na druge module sa kojima saraduje.
U isto vreme, elementi dobrog modela imaju sinergiju, i dobro odabrani moduli spajaju
zajedno elemente modela sa bogatim konceptualnim vezama. Ova velika saradnja objekta
sa povezanim odgovornostima omogućava da se posao modeliranja i dizajniranjdizajniranja
koncentriše unutar jednog modula, skala kompleksnosti kojom ljudski mozak može lako da
rukuje.

17
Moduli i manji elementi trebalo bi da zajedno evoluiraju, ali, tipično, to nije slučaj.
Moduli se biraju kako bi organizovali rani izgled objekata. Kasnije, objekti teže da se pro-
mene tako da i dalje ostanu u granicama definicije modula. Refaktorisanje modula pred-
stavlja više posla i opasnije je od refaktorisanja klasa i ,verovatno, ne može biti tako često
sprovodeno. Kao što objekti modela teže da startuju naivno i konkretno, gde se onda poste-
peno transformišu kako se znanje proširuje, tako moduli mogu postati suptilni i apstraktni.
Dopuštanjem da moduli odslikavaju promene razumevanja domena daje se više slobode
objektima da evaluiraju unutar modula.
Kao i sve drugo u DDD-u moduli predstavljaju mehanizam komuniciranja. Značenje
da su objekti razdvojeni, mora diktirati izbor njihovih modula. Kada stavimo neke klase
zajedno u modulu, mi poručujemo sledećem programeru koji gleda u dizajn, da misli o njima
u celini. Ako model pripoveda priču, onda moduli predstavljaju poglavlja u toj priči. Naziv
modula saopštava njegovo značenje. Imena modula su obično zasnovana na biznis terminima,
na primer: klijentski modul.

2.4 Životni vek objekata u domenu


Svaki objekat ima životni vek. Objekat se stvara, prolazi kroz razna stanja i na kraju se
uništava tako što se arhivira ili briše. Naravno, veliki broj njih su jednostavni prolazni objekti
kreirani jednostavnim pozivom njihovih konstruktora, upotrebljeni u nekom izračunavanju i
na kraju obrisani iz memorije. Ne postoji potreba za komplikovanjem takvih objekata, ali
neki objekti imaju duži vek trajanja i veći deo svog života ne provode aktivni u memoriji. Oni
imaju kompleksne meduzavisnosti sa ostalim objektima i prolaze kroz različita stanja tokom
svog životnog veka. Rad sa takvim objektima predstavlja izazov koji može lako izbaciti iz
koloseka programera koji primenjuje DDD.

Dijagram 3: Životni vek objekta u aplikaciji

Izazove možemo podeliti u dve kategorije:

18
1. Održavanje integriteta kroz životni vek.

2. Sprečavanje da model bude “progutan” komplikacijama održavanja njegovog životnog


veka.
U daljem tekstu opisujemo rešenje ovih problema kroz tri paterna. Prvo, agregati ve-
zuju model definisanjem vlasništva i granica, izbegavanjem implementiranja umršene mreže
objekata. Ovaj patern je krucijalan za održavanje integriteta u svim fazama životnog veka.
Sledeće, upotrebom fabrika za kreiranje i rekonstituisanje kompleksnih objekata i agre-
gata, kao i čuvanjem interne strukture skrivenom od klijenta, fokus se stavlja na početak
životnog veka.
Na kraju, repozitorijumi adresiraju sredinu i kraj životnog ciklusa, pružajući sredstva za
pronalaženje i preuzimanje uskladištenih objekata skrivajući infrastrukturu iza svog inter-
fejsa.

2.4.1 Agregati
Recimo da brišemo objekat osoba iz baze podataka. Zajedno sa osobom, nestaje ime,
datum rodjenja i opis posla. Ali, sta je sa adresom? Mogu postojati i druge osobe sa istom
adresom. Ukoliko obrišemo adresu, ostali objekti klase osoba imaće reference na izbrisani
objekat. Ukoliko ipak ne obrišemo adresu, onda akumuliramo nepotrebne podatke o adre-
sama u bazi podataka. Automatsko sakupljanje smeća može eliminisati nepotrebne adrese,
ali to je tehnička popravka, i ukoliko je dostupno u sistemu za upravljanje bazom podataka,
ignoriše se osnovni problem modeliranja. Čak i kada posmatramo izolovanu transakciju,
mreža relacija tipičnog objektnog modela ne daje jasnu granicu potencijalnog efekata pro-
mene. Nije praktično ponovo učitati svaki objekat u sistemu (za svaki slučaj), radi ponovne
provere, ukoliko postoji neka veza sa izmenjenim objektom. Problem je akutan u sistemu
sa konkurentnim pristupom istim objektima od strane više klijenata. Kako više korisnika
istovremeno koriste i ažuriraju različite objekte u sistemu moramo zabraniti istovremene pro-
mene meduzavisnih objekata. Postavljanje pogrešnog obima promene može imati ozbiljne
posledice.
Teško je garantovati konzistentnost promena nad objektima u modelu sa kompleksnim
medusobnim vezama. Invarijante moraju da se održavaju na taj način da se primenjuju
na usko povezane grupe objekata, a ne samo na pojedinačne objekte. Takode, šeme za-
ključivanja mogu izazvati medusobno ometanje korisnika i zbog toga je moguće da sistem
bude nestabilan.
Pogledajmo to iz drugog ugla, kako znamo gde počinje i gde se završava objekat koji
je sačinjen od drugih objekata? U svakom sistemu sa trajnim snimanjem podataka, mora
postojati okvir za transakciju koja menja podatke i način za održavanje konzistentnosti
podataka (tj. održavanje njihovih invarijanti). Baze podataka dopuštaju različite šeme
zaključavanja i moguće je kreirati testove kako bi se osiguralo da sve radi. Ali, takva brzinska
rešenja samo skreću pažnju sa modela, i ubrzo se vraćamo hakovanju sistema.
U stvari, potrebno je dublje razumevanje domena od strane programera kako bi se
pronašlo balansirano rešenje za ovakve vrste problema dodavanjem faktora kao što je učestalost

19
promena instanci odredenih klasa. Potrebno je pronaći model koji ostavlja tačke slabe sa-
radnje labavim i čini invarijante zategnutijim. Rešenje treba da učini model jednostavnijim
za razumevanje i da pojednostavi komunikaciju u dizajnu. Kako ne postoje jasno definisane
granice modela, problem, koji se sada javlja, odnosi se na formiranje transakcija.
Razvijeni su razni obrasci za definisanje vlasničkih relacija u modelu. Sledeći jednostavan
ali rigorozan sistem, destiliše koncepte, uključuje skup pravila za implementiranje transakcija
koje mogu promeniti svojstva objekata i njihovih vlasnika.
Prvo potrebna nam je apstrakcija za enkapsulaciju referenci u okviru modela. Agregat
je grupa povezanih objekata koje tretiramo kao jedinica u svrsi izmene podataka. Svaki
agregat ima svoj koren i granicu. Granica definiše šta je unutar agregata. Postoji samo jedan
koren agregata, tj. poseban entitet odreden kao agregat. Koren je jedini član agregata koji
spoljašnji elementi mogu da referenciraju, objekti u okviru granice mogu imati medusobne
reference. Entiteti koji nisu koren agregata imaju lokalni identitet, ali taj identitet važi
jedino u okviru agregata, zato što spoljni objekti nikada ne mogu da ih vide izvan konteksta
korena agregata.
Model automobila može biti upotrebljen u softveru za mehaničarsku radnju. Kola pred-
stavljaju entitet sa globalnim identitetom. Mi želimo da razlikujemo taj auto od ostalih
automobila na svetu, čak, i sa onima koji su veoma slični. U ovom slučaju, možemo upo-
trebiti broj šasije. Možda želimo da pratimo istoriju rotiranja guma kroz 4 pozicije, možda,
želimo da znamo kilometražu i iskorišćenost svake gume. Kako bismo razlikovali gume, i one
moraju predstavljati entitete, takode. Ali, veoma je malo verovatno da želimo da brinemo
o identitetu tih guma izvan konteksta specifičnog automobila. Ukoliko zamenimo gume i
pošaljemo stare na recikliranje, ili naš softver to neće pratiti, ili će one postati anonimni
članovi neke gomile guma. Niko neće brinuti o njihovoj istoriji rotiranja. Čak, i onda kada
su ugradene na auto, niko neće vršiti upit nad sistemom kako bi prvo našao odredenu gumu,
a nakon toga pogledao u kojim kolima je guma ugradena. Korisnici će izvršiti upit u bazu
da pronadju auto i onda će pitati za prolaznu referencu na gume. Stoga, automobil predsta-
vlja koren agregata, čije granice obuhvataju gume. Sa druge strane, blokovi motora imaju
serijske brojeve ugravirane na njima i ponekada se prate nezavisno od automobila. U nekim
aplikacijama, motor može biti koren svog sopstvenog agregata.
Invarijante, koje predstavljaju pravila doslednosti moraju biti održavane kadgod se pro-
mene podaci, uključujući veze izmedu članova agregata. Za pravila koja se prostiru izmedu
agregata ne možemo očekivati da budu ažurna sve vreme. Procesiranjem dogadaja, serijskim
procesiranjem, ili upotrebom drugih mehanizama ažuriranja, zavisnosti mogu biti rešene u
okviru nekog predodredenog vremenskog perioda. Ali, invarijante primenjene u okviru agre-
gata moraju biti kompletirane u okviru jedne transakcije.
Ukoliko konceptualni agregat prevedemo u implementaciju, postoje pravila koja moraju
da se ispune prilikom svake transakcije:
• Entiteti koji se nalaze u korenu agregata imaju globalni identitet i odgovorni su za
proveravanje invarijanti.
• Entiteti u korenu imaju globalni identitet. Entiteti unutar granice imaju lokalni iden-
titet, jedinstveni su jedino u okviru agregata.

20
• Ništa izvan granice agregata ne može držati referencu ni na jednom objektu unutar
agregata, izuzev korena agregata. Koren agregata može prepustiti reference internih
entiteta drugim objektima, ali ti objekti mogu da ih koriste jedino prolazno, i oni ne
mogu držati prave reference. Koren može vratiti kopiju u formi vrednosnog objekta
drugom objektu, i nije bitno šta može da mu se desi, zato što je to samo vrednosni
objekat i više nema nikakvu povezanost sa agregatom.

• Kao posledica prethodnog pravila, jedino koreni agregata mogu biti preuzeti direktno
iz baze podataka. Svi ostali objekti moraju biti preuzeti kroz asocijacije.

• Objekti u okviru agregata mogu imati reference na druge agregate.

• Operacija brisanja mora obrisati sve u okviru granice agregata odjednom.

• Kada se promena nad bilo kojim objektom unutar agregata komituje, sve invarijante
celine agregata moraju biti ispunjene.

2.4.2 Fabrike
Kada kreiranje objekta, ili celog agregata, postane komplikovano ili previše otkriva stuk-
turu, fabrike pružaju dobro rešenje za enkapsulaciju.
Veliki deo snage objekata sastoji se u složenoj konfiguraciji njihovih internih delova i
njihovih medusobnih veza. Problemi nastaju kada se kompleksni objekti preopterete logikom
za svoje kreiranje.
Motor automobila je komlikovana mašinerija. On sadrži nekoliko desetina delova, koji
saraduju zajedno, kako bi odradili odgovornost motora – da okreće radilicu. Možemo zami-
sliti motor koji može sam da ugradi svećice na blok motora, ubaci klipove u svoje cilindre
i da sve to zašrafi zajedno. Ali, izgleda neverovatno da će takva komplikovana mašinerija
biti pouzdana ili efikasna kao današnji tipični motori. Umesto toga, mi prihvatamo da nam
nešto ili neko drugi sastavi delove motora zajedno. Možda je to mehaničar ili industrijski
robot. I robot i mehaničar su u stvari kompleksniji od samog motora kojeg oni sastavljaju.
Posao sastavljanja delova je kompletno nepovezan sa poslom okretanja radilice. Funkcija
montažera traje jedino dok se proizvodi automobil, nije nam potreban robot ili mehaničar
kada je automobil u voznom stanju. Zato se automobili nikada ne sastavljaju i voze u isto
vreme. Ne postoji vrednost u kombinovanju obe funkcije u isti mehanizam. Isto tako, sasta-
vljanje kompleksnog objekta je posao za koji bi najbolje bilo da bude odvojen od funkcije
objekta koju će on imati kada se konstruiše.
Pomeranje odgovornosti kreiranja na klijentski objekat, vodi ka većim problemima. Kli-
jent zna koji posao treba da obavi i oslanja se na objekte domena kako bi odradili neophodna
izračunavanja. Ukoliko klijent treba da kreira/konstruiše objekat domena koji mu je potre-
ban, onda mora nešto znati i o internoj strukturi tog objekta. Kako bi osigurao da invarijante
budu zadovoljene, klijent mora znati i neka pravila objekta. Čak i pozivanje konstruktura
vezuje klijenta na konkretne klase objekta koje on kreira. Nije moguće promeniti objekat
domena bez promene u klijentu, čineći refaktorisanje i održavanje težim.

21
Klijent koji preuzima odgovornost kreiranja objekta postaje nepotrebno komplikovan i
to zamagljuje njegovu odgovornost. Probija enkapsulaciju objekata domena i agregata koga
kreira. Čak i gore, ukoliko je klijent deo aplikacionog sloja, onda odgovornosti domena cure u
aplikacioni sloj. Ovo čvrsto oslanjanje aplikacije na detalje implementacije domena odbacuje
najvećim delom dobrobiti koje dobijamo apstrakcijom u sloju domena i čini buduće promene
veoma skupim.
Kreiranje objekta može biti posebna operacija, ali kompleksne operacije kreiranja nisu
odgovornost kreiranih objekata. Kombinovanje tih odgovornosti može proizvesti nezgrapan
dizajn koji je teško razumeti. Ukoliko klijent direktno konstruiše objekte, onda zamaglju-
jemo klijenta, kršimo enkapsulaciju objekta koji se pravi i previše vezujemo implementaciju
klijenta na implementaciju kreiranog objekta.
Kreiranje kompleksnih objekata je odgovornost sloja domena. Ipak taj zadatak ne pri-
pada objektima koji predstavljaju model. Postoje slučajevi u kojima je kreiranje objekta i
njegovo konstruisanje veoma bitno za domen, kao što je otvaranje bankovnog računa. Ali,
kreiranje objekta i njegovo konstruisanje obično nema nikakvo znanje u domenu; neophodno
je jedino za implementaciju koda. Kako bismo rešili problem, moramo dodati nove konstruk-
cije u domenu. Ovo odstupa od definicije prethodnih elemenata i veoma je bitno naglasiti
i održati poentu čistom: u dizajn dodajemo elemente koji ne odgovaraju ničemu u modelu,
ali koji su i, pored toga, deo odgovornosti sloja domena.
Svaki objektno orijentisani jezik pruža mehanizam za kreiranje objekata (konstruktori),
ali potreban je absktraktniji mehanizam konstrukcije koji nije vezan za postojeće objekte.
Element programa, čija je odgovornost kreiranje drugih objekata, nazivamo Fabrika.

Dijagram 4: Osnovna interakcija sa fabrikom

Kao što interfejs objekta skriva njegovu implementaciju, dopuštajući klijentu da koristi
ponašanja objekta bez znanja kako je to učinjeno, tako fabrika sakriva znaje potrebno za
kreiranje kompleksnih objekata ili agregata. Fabrika pruža interfejs koji zadovoljava ciljeve
klijenta i pruža apstraktni pogled na kreirani objekat.
Postoji više načina dizajniranja i implementiranja fabrika. Nekoliko paterna specijalno
dizajniranih za kreiranje objekata (Factory Method, Abstract Factory i Builder) temeljno su
proučeni u knjizi o dizajn-paternima [16]. Ta knjiga detaljno izučava paterne čak i za najteže
probleme konstrukcije objekata. Trenutno, cilj nije ići duboko u dizajniranje fabrika, već je
pokazati da su fabrike bitne komponente domena. Pravilna upotreba fabrika može pomoći
održavanju DDD dizajna u pravom smeru.

22
Dva osnovna zahteva za bilo koju dobro dizajniranu fabriku su:
1. Svaki konstruktorski metod je atomičan i zahteva da sve invarijante kreiranog objekta
budu zadovoljene. Fabrika bi trebalo da kreira samo objekte koji se nalaze u konzi-
stentnom stanju. Za entitet, ovo znači kreiranje celog agregata gde su sve invarijante
zadovoljene. Za nepromenljive vrednosne objekte, ovo znači da su svi atributi ini-
cijalizovani na njihovo pravo i finalno stanje. Ukoliko nije moguće kreirati korektno
zahtevani objekat, onda je potrebno baciti izuzetak ili implementirati neki drugi me-
hanizam, koji bi se pozvao, ne bi li se osigurao povratak korektnog objekta.

2. Povratna vrednost fabrike treba da bude apstrakcija tipa, koju klijent zahteva, a ne
konkretna klasa, koja se kreira.

2.4.3 Repozitorijum
Ukoliko želimo da radimo nešto sa objektom, potrebno je da imamo referencu na njega.
Kako dobiti referencu? Jedan način je kreirati objekat, kako bi operacija kreiranja mogla
da vrati referencu na novi objekat. Obilaskom objekta, takode, možemo dobiti reference.
Startujemo sa objektom, koga već znamo i pitamo za povezane objekte. Svaki objektno
orijentisani program radi mnogo ovakvih stvari. Te veze daju objektnim modelima puno
izražajne snage. Ali, potrebno je dobiti prvi objekat.
Pretraga po bazi podataka je globalno dostupna i omogućava direktan pristup objektu.
Nema potrebe da svi objekti budu medusobno povezani, što nam omogućava da držimo
mrežu objekata pogodnom za rukovanje. Bilo da koristimo obilazak ili koristimo pretragu,
to je biznis odluka. Da li bi trebalo da objekat korisnika drži kolekciju svih kupovina koje
je napravio? Ili bi trebalo da narudžbine budu pronadene iz baze podataka, sa pretragom
po ID kupca? Odgovarajuća kombinacija pretrage i udruživanja čini dizajn razumljivim.
Nažalost, programeri obično ne razmišljaju mnogo o takvim finoćama u dizajnu, zato što oni
plivaju u moru mehanizama koji omogućavaju skladištenje objekata, njihovo vraćanje nazad
i, eventualno, brisanje iz baze podataka.
Sa tehničke strane gledano, preuzimanje uskladištenog objekta je stvarno podskup krei-
ranja zato što se podaci iz baze koriste za kreiranje novog objekta. Ali, konceptualno, ovo je
sredina životnog veka objekta. Objekat korisnika ne predstavlja novog korisnika zato što smo
ga mi uskladištili u bazu podataka i opet preuzeli. Kako bismo ovo predstavili različito u
mislima, bolje je uvesti termin za kreiranje instance iz skladišta kao što je to rekonstituisanje.
Cilj DDD-a je kreirati bolji softver fokusiranjem na model domena pre nego na tehnolo-
giju. Dok programer konstruiše SQL upit, prosledi ga servisu za upite u infrastrukturnom
sloju, dobije redove tabele kao rezultat, preuzme neophodne informacije i prosledi ih kon-
struktoru ili fabrici, fokus na model nestaje. Postaje prirodno razmišljati o objektima kao
kontejnerima za podatke koje upiti obezbeduju. Tada, ceo dizajn se pomera prema stilu
procesiranja podataka. Detalji o tehnologiji variraju, ali problem ostaje da se klijent bavi
tehnologijom, umesto konceptima modela. Infrastruktura kao što su slojevi mapiranja poda-
taka dosta pomaže, praveći jednostavniju konverziju rezultata upita u objekte, ali programer
i dalje misli o tehničkim mehanizmima, a ne o domenu. Još gore, klijentski kôd koristi bazu

23
podataka direktno, programeri pokušavaju da zaobidu karakteristike modela kao što su agre-
gati, ili čak enkapsulaciju objekata. Umesto toga direktno pričaju i rade sa podacima koji su
im potrebni. Sve više i više pravila domena postaju ugradena u kôd upita ili se jednostavno
gube. Objektne baze podataka eliminišu problem konverzije, ali mehanizmi pretrage su,
obično, i dalje mehanički pa programeri, često, dolaze u iskušenje da preuzmu objekte koje
oni žele.
Klijentu je potrebno praktično znanje kada zahteva referencu na postojeće objekte do-
mena. Ukoliko infrastruktura to omogućava, programeri klijentstkog koda lako mogu dodati
nove objekte bazirane na vezama sa preuzetim objektima, što na kraju samo još više zama-
gljuje model. Sa druge strane, oni mogu koristiti upite kako bi dobili tražene podatke koji
su im potrebni iz baze podataka, ili mogu da preuzmu nekoliko specifičnih objekata, umesto
da koriste navigaciju kroz agregate. Na taj način, logika domena seli se u upite i klijentski
kôd, entiteti i vrednosni objekti postaju samo kontejneri podataka. Tehnička kompleksnost
primene pristupa bazi podataka brzo zamenjuje klijentski kôd, što programere navodi na to
da od sloja domena naprave dubrište. Sve to, na kraju, čini model domena nebitnim.
Izvlačenjem dizajn principa, o kojima smo vodili polemiku do sada, uz pretpostavku
da smo pronašli metod za pristup koji održava fokus na model dovoljno oštar da uposli
ove principe, uočavamo da način smanjenja područja problema pristupa objektima postoji.
Za početak, nije potrebno da brinemo o prolaznim objektima. Prolazni objekti (tipično
vrednosni objekti) imaju kratak vek, i koriste se u klijentskoj operaciji, koja ih kreira i
onda odbacuje. Takode, nisu nam potrebni upiti za uskladištene objekte koje je lakše naći
obilaskom objekta. Na primer, adresa osobe zahteva se iz samog objekta osoba. I najbitnije,
bilo kom objektu koji je interni za agregat zabranjen je pristup izuzev obilaskom počevši od
korena agregata.
Uskladišteni vrednosni objekti obično se nalaze obilaskom, kada se krene od odredenog
entiteta, koji se ponaša kao koren agregata, koji ga enkapsulira. U stvari, globalna pretraga
vrednosnih objekata često je besmislena, zato što je traženje vrednosnog objekta po njegovim
atributima ekvivalentno kreiranju nove instance, sa tim istim atributima. Ipak ima izuzetaka.
Na primer, kada korisnik planira putovanje na internetu, ponekada želi da sačuva nekoliko
potencijalnih maršuta i vrati se nazad kasnije kako bi jednu od njih rezervisao. Te maršute
predstavljaju vrednosne objekte, ali one su povezane sa korisničkim imenom i vraćaju se
korisniku netaknute. Drugi slučaj je nabrajanje, u slučaju kada je tip striktno limitiran
u preodredeni skup mogućih vrednosti. Globalni pristup vrednosnim objektima je mnogo
manje uobičajen nego što je to slučaj sa entitetima. Ukoliko se, ipak, nademo u takvoj
situaciji, potrebno je dobro razmotriti da li je reč o entitetu koji, možda, nismo uspeli da
prepoznamo.
Iz prethodne diskusije, zaključujemo da se velikom broju objekata ne pristupa globalnim
pretraživanjem. Sada problem možemo izraziti preciznije.
Podskup uskladištenim objektima mora biti globalno dostupan kroz pretragu baziranu
na atribute tih objekata. Takav pristup je potreban za korene agregata kojima nije pogodno
pristupiti obilaskom. To su obično entiteti, ponekad vrednosni objekti sa kompleksnom in-
ternom strukuturom i ponekad nabrojive vrednosti. Pružanjem pristupa drugim objektima

24
zamagljuju se važna razaznavanja. Slobodni upiti baze mogu, zapravo, prekršiti enkapsula-
ciju objekata domena i agregata. Izlaganje tehničke infrastrukture i mehanizama pristupa
bazi klijentu komplikuje rukovanje i, takode, zamagljuje dizajn.
Repozitorijum predstavlja sve objekte odredenog tipa kao skup. Ponaša se kao kolekcija,
plus ima dodatni mehanizam upita. Objekti odgovarajućeg tipa mogu da se dodaju i obrišu,
mašinerija implementirana unutar repoziturijma radi stvarno ubacivanje i brisanje podataka.
Ova definicija okuplja kohezivan skup odgovornosti za pružanje pristupa korenima agregata
kroz njihov životni vek.
Klijent zahteva objekte od repozitorijuma upotrebom metode upita. Vraćeni objekti ba-
zirani su na kriterijume koji su zadati od strane korisnika. Tipično, to su vrednosti odredenih
atributa. Repozitorijum vraća zahtevane objekte, skrivajući mašineriju pravljenja i izvršenja
upita i mapiranja iz baze podataka. Repozitorijumi mogu implementirati raznovrsne upite
koji selektuju objekte bazirane na različitim kriterijumima koje klijent odabere. Oni čak
mogu vratiti i rezime izračunavanja, kao što su totali kroz sve objekte koji zadovaljaju
uslove po nekom numeričkom atributu.
Repozitorijum skida veliki teret klijentu, koji sada može imati jednostavan interfejs koji
otkriva njegovu nameru i može zahtevati objekte u terminima modela. Kako bi se podržalo
sve ovo, potrebno je dosta kompleksnog infrastrukturnog koda. Na kraju dobijamo interfejs
koji je jednostavan i konceptualno povezan na model domena.
Za svaku klasu, za koju je potreban globalni pristup, kreiramo objekat, koji pruža iluziju
kolekcije svih objekata tog tipa u memoriji. Obavljamo sledeće funkcije: pružamo pristup
kroz dobro poznati globalni interfejs, pružamo metode za dodavanje i brisanje objekata
koji skrivaju tehničko znanje o dodavanju i brisanju objekata, kao i metode koje selektuju
(prema zadatom kriterijumu), potpuno instancirane objekte ili kolekciju objekata (čije vred-
nosti atributa zadovoljavaju kriterijum skrivanjem stvarne tehnologije smeštanja podataka),
obezbedujemo repozitorijume samo za korene agregata kojima je zapravo potreban direktan
pristup i držimo klijenta fokusiranim na model (delegiranjem logike pristupa bazi repozito-
rijumima).
Repozitorijumi imaju više prednosti medu kojima su:

• Daju klijentu jednostavni model za preuzimanje uskladištenih objekata i rukovanje


njihovim životnim vekom.

• Razdvajaju aplikaciju i dizajn domena od tehnologije pristupa podacima (moguće je


impelementirati višestruke baze podataka).

• Komuniciraju dizajnerskim odlukama o pristupu objektima.

• Omogućavaju jednostavnu zamenu lažnom implementacijom (za potrebe testranja).

25
3 Razdvajanje odgovornosti komandi i upita (eng. Com-
mand Query Responsibility Segregation – CQRS)
U okviru DDD cilj programera je razbiti znanje o domenu i implementirati ga kroz mrežu
medusobno povezanih objekata, koji imaju odredena stanja i ponašanja. Model je jedinstven,
a namera je potpuno opisati biznis-domen. U početku je to izgledalo kao stvar koju je lakše
reći nego uraditi.
Neki projekti koji su prihvatili DDD bili su uspešni, drugi, ipak, nisu. Na teorijskom
delu deluje da ima uspeha, ali dosta ljudi i dalje veruje da je DDD teško implementirati,
iako, njegova korist može biti značajna. Poenta je da ljudi misle da su benefiti koje DDD
može doneti mnogo manji od problema koji mogu nastati prilikom implementiranja DDD-a
u slučaju neuspeha.
Analitički deo DDD-a ima malo veze sa kodom i softverskim dizajnom. Sve je u osmišljavanju
arhitekture najvišeg nivoa upotrebom alata (kao što je opšteprisutan jezik Ubiquitis langu-
age). Ovo je odličan pristup za bilo koji projekat. U kompleksnim scenarijima, razumevanje
šire slike pomaže pri rasporedivanju modula i servisa. U jednostavnim scenarijima, svodi se
na jedan kontekst i jedan modul.
Najveći broj poteškoća, sa kojima su se suočili rani usvojioci DDD-a, svodi se na problem
što se dizajn jednog modela brine za sve aspekte modela. Naime, isti model za čitanje i
za upis vodi kompleksnim modelima, koje je teško održavati i optimizovati. Specijalno za
veoma komplikovane biznis scenarije, jedan model ubrzo postaje neodrživ. Ne samo da raste
eksponencijalno po veličini i kompleksnosti, već ne obavlja posao na pravi način. Nije moguće
kreirati optimalno rešenje za pretragu, izveštavanje i procesiranje transakcija upotrebom
jednog modela. Uvedimo sada patern koji razdvaja model domena na dva modela, postižući
više od jednostavnog razdvajanja odgovornosti.
CQRS je skraćenica za “razdvajanje odgovornosti komandi i upita” (Command Query
Responsibility Segregation). Većina ljudi misli da je CQRS arhitektura, ali to je pogrešno.
CQRS je mali patern. Njega su uveli Gregory Young i Udi Dahan. Dobili su inspiraciju iz
Command Query Separation principa, koji je definisao Bertrand Meyer. Glavna ideja CQS
principa je: “Metod bi trebalo ili da menja stanje objekta ili samo da vrati rezultat, ali
ne oba”. Drugim rečima, postavljanje pitanja ne bi trebalo da promeni odgovor. Formalno,
metodi bi trebalo da vrate vrednost jedino ako nemaju sporedne efekte. Zbog ovoga možemo
podeliti metode u dva skupa:
• Komande - menjaju stanje sistema ili objekata, ne vraćaju rezultat izuzev status-koda
ili potvrde o izvršenju.
• Upiti - vraćaju rezultate i ni na koji način ne menjaju stanje sistema ili objekta.
U realnim situacijama, veoma je jednostavno razlikovati metode i odrediti njihov skup.
Upiti deklarišu povratni tip, dok komande ne vraćaju rezultat. Ovaj patern je široko pri-
menljiv i čini razumevanje objekata jednostavnijim.
CQRS koristi dva različita domena umesto samo jednog. Razdvajanje se postiže grupi-
sanjem operacija koje predstavljaju upite u jedan sloj i operacija koje su komande u drugi

26
sloj. Svaki sloj, onda, ima i svoju arhitekturu i svoj skup servisa, posvećenih samo upitima
i komandama. Sledeći dijagram prikazuje razlike:

Dijagram 5: Vizueno poredenje modela domena i CQRS arhitektura

Možda žvuči iznenadujuće, ali jednostavno raspoznavanje da su komande i upiti dve


različite stvari ima veliki uticaj na celokupnu arhitekturu sistema. Upiti su sada samo
podaci koje je potrebno prikazati na korisički interfejs, tako da im nije potreban model
domena. Komande se ne izvršavaju nad podacima koji su dobijeni iz upita. Kao takvi,
agregati postaju jednostavniji. Model na strani upita može biti jednostavno kolekcija DTO
objekata. Prateći ovo razmatranje, servisi postaju samo klase koje implementiraju biznis
logiku koristeći anemični model domena.
Ovo razdvajanje pojačava pojam da komandna strana i strana upita imaju različite po-
trebe. Arhitekturna svojstva, vezana sa upotrebnim slučajevima svake strane veoma su
drugačija. U sledećem tekstu opsujemo odredene razlike:

• Konzistentnost

– Komanda: Mnogo je lakše procesirati transakcije sa konzistentnim podacima


nego baratati eventualnom konzistentnošću u komandnom sloju.
– Upit: Većina sistema mogu biti eventualno konzistentni na strani upita.

• Skladištenje podataka

– Komanda: Komandna strana kao procesor transakcija želi da skladišti podatke


na normalizivani način u relacionoj strukturi – treća normalna forma.
– Upit: Strana upita želi podatke na denormalizovani način kako bi se minimizirao
broj spajanja tabela za dobijanje željenog skupa podataka – prva normalna forma.

27
• Skalabilnost

– Komanda: Kod većine sistema, specijalno web sistema, komandna strana gene-
ralno procesira veoma mali broj transakcija u odnosu na ukupan broj transakcija.
Skalabilnost zbog toga nije uvek bitna.
– Upit: Kod većine sistema, specijalno web sitema, strana upita generalno procesira
veliki broj transakcija u odnosu na ukupan broj transakcija. Skalabilnost je,
najverovatnije, potrebna na strani upita.

Prava snaga ova dva paterna je u mogućnosti razdvajanja metoda u one koje menjaju
stanje od onih koje to ne čine. Ovo razdvajanje je veoma korisno u situacijama kada imamo
problema sa performansama. Moguće je optimizovati stranu upita sistema odvojeno od
komandne strane.
Druga korist ovog paterna je kod velikih aplikacija. Moguće je razdvojiti programere na
manje timove koji rade na različite strane sitema (čitanje i upis) bez obzira na saznanje kako
je implementirana druga strana. Na primer, programeri koji rade na strani upita, ne moraju
da razumeju model domena.
Imati potpunu implementaciju modela domena na strani upita nije praksa. Strana upita
je specijalizovana za izveštavanje. Generalno, upiti bi trebalo da budu ekstremno jednostavni.
Dodatno, moguće je imati odvojene baze podataka za stranu upita i stranu komandi.

3.1 Strana upita


Kao što smo napomenuli, strana upita jedino sadrži metode za preuzimanje podataka. Iz
originalne arhitekture ovo mogu da budu sve metode koje vraćaju DTO objekte koje klijent
koristi za prikaz na ekranu.
U originalnoj arhitekturi izgradnja DTO objekta postiže se projekcijom objekata domena
na DTO objekte. Ovaj proces može izazvati dosta poteškoća. Veliki izvor poteškoća prepo-
znajemo u tome što su DTO objekti različiti po strukturi od objekata domena, i zbog toga
je potrebno uvesti mapiranje izmedu njih. Najveći problem je taj što je optimizacija upita
ekstremno teška. Zato što svi upiti operišu nad objektnim modelom, a onda se transformišu
u DTO objekte.
DTO objekte je potrebno optimalno napraviti, kako bi odgovarali prikazima na ekranu
klijenta i kako bi sprečili višestruke pozive ka serveru. U slučajevima kada treba raditi sa
mnogo različitih klijenata, posao bi, možda, bio olakšan ako bismo napravili univerzalni
model koji bi svi oni koristili. U svakom, slučaju DTO model je veoma različit od modela
domena koji je izgraden za procesiranje transakcija.
Upotrebom CQRS paterna moguće je izbeći te projekcije. Uvodi se novi način projekto-
vanja DTO objekata. Zaobilazi se model domena i DTO objekti se preuzimaju direktno iz
baze podataka. Kada aplikacija zahteva podatke, ovo može biti postignuto jednim pozivom
strane upita, koja vraća jedan DTO sa svim potrebnim podacima.
Strana upita nije kompleksni deo koda i može biti dosadan za održavanje. Takode,
dobra ideja je koristiti procedure u bazi podataka za čitanje, što opet zavisi od tima i

28
nefunkcionalnih zahteva sistema. Direktna veza sa izvorom podataka, upite čini veoma
jednostavnim za pisanje, održavanje i optimizovanje. Ima smisla i denormalizovati podatke.
Razlog za to je što se upiti nad podacima, obično, mnogo više puta izvršavaju, nego što se
podaci ažuriraju. Denormalizacija može povećati performanse aplikacije.

3.2 Komandna strana


Sveukupno komandna strana ostaje veoma slična tipičnoj DDD arhitekturi. Glavna
razlika je ta što je domen sada koncentrisan na ponašanje, umesto na centralizovani pristup
podacima.
U tipičnoj DDD arhitekturi domen je zadužen za rad sa upitima i komandama. Ovo
uzrokuje veliki broj problema u domenu. Neki od problema su:

• Veliki broj metoda za čitanje u repozitorijumima.

• Unutrašnje stanje objekata domena se izlaže kroz geter metode kako bi se izgradili
rezultujući DTO objekti.

• Učitava se više agregata kako bi se sagradio rezultujući DTO objekat što uzrokuje
neoptimalno zadavanje upita za izgradnju modela podataka.

Kada se strana upita razdvoji od modela domena, domen se onda fokusira na procesiranje
komandi. Ranije pomenuti problemi odjednom nestaju. Objekti domena odjednom nemaju
potrebe za izlaganjem internog stanja, repozitorijumi imaju veoma malo ili nemaju ni jedan
metod pored GetById metode, i agregati se fokusiraju na ponašanje.
U poredenju sa originalnom arhitekturom, ova promena je uradena jeftino ili bez troškova.
U mnogo slučajeva, razdvajanje smanjuje troškove, kako je optimizacija SQL upita jedno-
stavnija. U najgorem slučaju, troškovi bi trebalo da budu jednaki. Sve što je potrebno
uraditi, jeste pomeranje odgovornosti. Moguće je, takode, imati stranu upita koja, koristi
objekte domena.

29
4 Implementacija modela domena
Kod DDD-a izrada projekta počinje dizajniranjem modela domena. Dizajniranje nije
jednostavan proces, i u realnim slučajevima treba vremena i iskustva za dizajniranje dobrog
modela. Na osnovu specifikacija, dizajniran je sledeći model:

Dijagram 6: Dijagram koji predstavlja model domena projekta prodavnica

Kao što vidimo iz dijagrama, centralna stavka u modelu je proizvod. On može da bude
ubačen u grupu proizvoda radi lakše pretrage i označavanja. Takode, on mora da ima
proizvodaca.
Sledeću bitnu stavku modela predstavljaju fakture, kojima se (kada se kreiraju) zadaje
dobavljač i koje se sastoje iz više stavki. Takode, kada se faktura kreira, moguće je fak-
turu kalkulisati, odredujući cenu, pri tom, za svaku njenju stavku zasebno. Takode, kada
se kalkuliše faktura, moguće je promeniti cenu proizvoda (ukoliko je to potrebno), a kako
kalkulacija sadrži referencu na veći broj proizvoda, moguće je promeniti cenu većem proju
proizvoda. To se postiže tako što se prvo kreira nivelacija, a onda se promeni cena proizvoda.
Takode, bitna stavka aplikacije je ukucavanje prodajnih računa, gde jedan račun može
sadržati referencu na više proizvoda.

30
4.1 Definisanje agregata
Sada kada imamo dizajnirani model domena, potrebno je odabrati agregate i definisati
njihove granice.

Dijagram 7: Dijagram koji predstavlja model domena podeljen na agregate

Za dizajniranje agregata potrebno je, isto, iskustvo upotrebe DDD-a, ali i znanje o do-
menu. Prethodni dijagram prikazuje definisane agregate1 u projektu Prodavnica, sledi i malo
objašnjenja:
Proizvod je centralni objekat u modelu, veoma je bitan za dalje referenciranje i upo-
trebu, stoga se on definiše kao poseban agregat. Moguće je definisati grupu proizvoda, grupe
proizvoda je moguće nezavisno pretraživati i rukovati sa njima, stoga i grupa proizvoda
predstavlja poseban agregat. Medutim, članstvo proizvoda u grupi nema smisla pretraživati
1
Pored agregata prikazanih na slici, u implementaciji projekta Prodavnica dodata su još dva agregata, koji
su deo objekta Proizvod. To su ProizvodCena i StanjeProizvodaUProdajnomObjektu, razlog za definisanje
ovih dodatnih agregata jesu performanse. Kako se često menja stanje, a i cena proizvoda, bez promene
ostalih atributa proizvoda, i kako se ove dve vrednosti nezavisno menjaju, moguće je definisati posebne
agregate za njih.

31
bez konteksta proizvoda ili grupe proizvoda. Kako se članstvo više posmatra iz perspek-
tive proizvoda, a ne iz perspektive grupe proizvoda, ono je smešteno u agregatu proizvoda.
Proizvodač je, takode, poseban agregat, s obzirom na to da se posebno vrši evidencija o
proizvodačima u projektu.
Faktura predstavlja još jedan koren agregata. Ona se sastoji od liste stavki gde se nalaze
proizvodi. Pretraživanje stavki fakture, bez znanja o kojoj fakturi se radi, nema mnogo smi-
sla, tako da stavka fakture pripada agregatu fakture. Takode, za svaku fakturu je potrebno
zadati dobavljača, pošto je potrebno vršiti evidenciju o dobavljačima, i moguće je imati više
faktura sa istim dobavljačem, a on predstavlja još jedan koren agregata.
Postoji zahtev za ukucavanje računa, kako bi se omogućila prodaja proizvoda. Zbog toga
postoji još jedan agregat, gde su zadati prodajni račun i njegove stavke.

4.2 Dizajniranje sloja domena


Kada su definisani agregati, može se krenuti sa implementacijom. Sa tim, se, opet, kreće
iz sloja domena, gde se prvo definišu bazne klase. Kako agregati, entiteti i vrednosni objekti
žive u ovom sloju, definiše se bazna klasa za svaki od ovih tipova. U daljem tekstu videćemo
najvažnije detalje tih klasa. Bazne klase nisu deo logike domena. One su samo pomoćno
sredstvo koje olakšava rad sa objektima domena. Zbog, toga, sve ove klase smeštaju se u
infastrukturnom kodu.

4.2.1 Bazna klasa entiteta


Bazna klasa entiteta predstavlja osnovnu klasu entiteta koju sve klase, koje predstavljaju
entitet, nasleduju. U projektu je to abstraktna klasa sa nazivom EntityBase. Svi ID-evi u
bazi su definisani celim brojem. Tip long može da pokrije veliki skup vrednosti celih brojeva
i, zbog toga, je polje “id” tipa long. Služi samo za čitanje i inicijalizuje se prilikom kreiranja
objekta.

Listing 1: Osnovna svojstva bazne klase entiteta


public abstract class EntityBase
{
private readonly long _id;

protected EntityBase(long id)


{
if (id == 0) throw new ValidationException("Id mora biti zadat");
_id = id;
}

public long Id
{
get { return _id; }
}
...

Sledeća odgovornost ove klase je definisati metode za ispitivanje jednakosti dva objekta.

32
Kako se svaki entitet poredi po vrednosti id-a, zgodno je to implementisati u ovoj klasi.
Ove metode se kasnije koriste za poredenje u kolekcijama, gde je potrebno naći odgovarajući
element. Generalno, koriste se svuda i, zbog toga, veoma je bitno definisati ove metode
korektno.

Listing 2: Metode za poredenje entiteta


public override bool Equals(object entity)
{
if (ReferenceEquals(entity, null) || !(entity is EntityBase))
{
return false;
}
return (this == (EntityBase) entity);
}

public static bool operator ==(EntityBase base1, EntityBase base2)


{
if (ReferenceEquals(base1, null) && ReferenceEquals(base2, null))
{
return true;
}
if (ReferenceEquals(base1, null) || ReferenceEquals(base2, null))
{
return false;
}
if (base1.Id != base2.Id)
{
return false;
}
return true;
}
public override int GetHashCode()
{
return _id.GetHashCode();
}

Ove metode su, takode, bitne za poredenje, sortiranje i uparivanje entiteta. Takode, ovo
predstavlja infrastrukturni kôd, jer se ne bavi problemom domena, a entitete ostavljamo
slobodne od ovakvog tehničkog koda.

4.2.2 Bazna klasa vrednosnih objekata


Za razliku od entiteta, vrednosni objekti nemaju identitet, i zbog toga, ne mogu imati
atribut ID. Svaki vrednosni objekat ima svoj poseban skup promenljivih i strategiju za
poredenje. Postoje primeri na internetu gde se logika za poredenje implementira u baznu
klasu vrednosnih objekata, ali kako svaki vrednosni objekat ima svoj poseban skup promen-
ljivih i strategiju poredenja, nije dobro generalizovati celu logiku poredenja u baznoj klasi.
Takode, veoma je lako izostaviti metode Equals i GetHashCode za svaki vrednosni objekat
posebno. Zbog toga bazna klasa sadrži apstraktne metode EqualsCore i GetHashCodeCore
koje moraju da implementiraju sve ostale klase.

33
Listing 3: Metode za poredenje vrednosnih objekata
public abstract class BaseValueObject<T> where T : BaseValueObject<T>
{

public override bool Equals(object obj)


{
var valueObject = obj as T;

if (ReferenceEquals(valueObject, null))
{
return false;
}
if (!HasValue && !valueObject.HasValue) return true;
if (!HasValue || !valueObject.HasValue) return false;
return EqualsCore(valueObject);
}

public override int GetHashCode()


{
return GetHasCodeCore();
}

protected abstract int GetHashCodeCore();


protected abstract bool EqualsCore(T valueObject);

public static bool operator ==(BaseValueObject<T> a, BaseValueObject<T> b)


{
if (ReferenceEquals(a, null) && ReferenceEquals(b, null))
return true;

if (ReferenceEquals(a, null) || ReferenceEquals(b, null))


return false;

return a.Equals(b);
}

Kao što možemo da vidimo, metode za poredenje vrednosnih objekata su implementirane


apstraktno. Ovde se centralizuje kôd za poredenje referenci i provere da nije poslat null
objekat, kako se ta logika ne bi kopirala u svaki vrednosni objekat. Kada se sve provere
referenci završe, poziva se apstraktna metoda EqualsCore, koja vrši poredenje parametra
i daje konačan rezultat. Takode, implementirana je i metoda GetHashCode, koja samo
prosleduje poziv apstraktnoj metodi GetHashCodeCore, kako bi se odredio heš kod.
Bazna klasa vrednosnih objekata, moze da sadrži i drugu logiku, koja se tiče svih vred-
nosnih objekata.
Kako se veoma često dešava da vrednosni objekti predstavljaju null vrednost i kako bi se
smanjila količina koda potrebna za održavanje null vrednosti u entitetima, dodata je podrška
za vrednosne objekte, koji predstavljaju null vrednost. Prvo se u konstruktoru prosleduje
parametar, koji indikuje da li postoji vrednost u trenutnoj instanci vrednosnog objekta.
Ova vrednost se prosleduje iz klasa, koje nasleduju ovu baznu klasu proveravanjem svojih
vrednosti u konstruktoru.

34
Listing 4: Konstruktor
protected BaseValueObject(bool hasValue)
{
_hasValue = hasValue;
}

Kako je u nekim slučajevima poželjno dozvoliti konstrukciju nevalidnog vrednosnog


objekta koji se onda kasnije validira, dodate su metode za validaciju koje podržvaju ovaj
scenario.

Listing 5: Validacija
public bool Validate(bool checkForNull = true)
{
return GetErrorMessage(checkForNull, null) == null;
}

public void ValidateWithException(bool checkForNull, string fieldName)


{
DomainException.ThrowIfHasMessage(GetErrorMessage(checkForNull, fieldName));
}

public string GetErrorMessage(bool checkForNull, string fieldName)


{
if (!checkForNull && !HasValue) return null; // if it is null value and null check is disabled
if (_isValid) return null; // if it is already validated then return null
if (!HasValue) return "Vrednost nije zadata";
var errorMsg = GetErrorMessageCore();
_isValid = errorMsg == null;
if (errorMsg != null && fieldName != null)
{
return fieldName + " : " + errorMsg;
}
return errorMsg;
}

Naravno, moguće je sprovoditi validaciju odmah prilikom konstrukcije objekta, što je,
uglavnom, i poželjno, ali, i u tom slučaju, ove metode ne gube na značaju. Zbog toga,
moguće je upotrebiti ih za validaciju prilikom konstruisanja objekta.

4.2.3 Bazna klasa agregata


Ova klasa nasleduje baznu klasu entiteta, jer su svi koreni agregata, u stvari, entiteti.
Kako ne postoji nikakva specijalna logika koja je vezana samo za agregate, za sada, je ova
klasa prazna. Videćemo kasnije da je u ovoj klasi implementirana logika za procesiranje
dogadaja u domenu.

35
Listing 6: Bazna klasa agregata
public abstract class AggregateRoot : EntityBase
{
}

4.2.4 Agregat Faktura


Sada, kada imamo infrastrukturni kôd za postavljen na mestu, moguće je početi sa im-
plementiranjem agregata. Agregate smo već definisali, a sada prikazujemo primere koda o
načinu implementiranja konkretnog agregata Faktura, kako umnogome oslikava koncepte o
kojima se diskutovalo u teorijskom delu.

Dijagram 8: Dijagram zavisnosti klasa agregata Faktura

Kao što se može videti iz dijagrama, klasa Faktura nasleduje baznu klasu agregata Ag-
gregateRoot, koja, pak, nasleduje baznu klasu entiteta EntityBase. Faktura sadrži različite
atribute. Za neke od njih koriste se vrednosni objekti. Tako, faktura je zavisna od klasa
vrednosnih objekta Procenat, Novac i BrojFakture. Takode, faktura sadrži listu stavki fak-
ture pa je zavisna i od klase FakturaStavka. Zavisnosti na vrednosne objekte možemo takode,
videti i iz koda, ukoliko pogledamo listu promenljivih:

36
Listing 7: Promenljive agregata fakture
public Procenat FakturaRabat { get; private set; }
public BrojFakture BrojFakture { get; private set; }
public DateTime DatumFakture { get; private set; }
public DateTime? DatumValute { get; private set; }
public long DobavljacId { get; private set; }
public long KorisnikId { get; private set; }
private IList<FakturaStavka> FakturaStavkaList { get; set; }

Iz prethodnog koda, takode, možemo zapaziti da su svim promenljivima seteri privatni. Ra-
zlog je taj što agregat upravlja stanjem svih ovih promenljivih i što je njihove vrednosti
moguće promeniti samo kroz specijalizovane metode.
Takode, promenljiva FakturaStavkaList je privatna, čak, nema ni javni geter. Razloge
za to valja tražiti u zabrani izmene sadržaja ove kolekcije, bez posredstva agragata Faktura
(jer je agregat dužan da upravlja svojim internim stanjem). Definisan je poseban properti u
klasi, koji vraća kolekciju klijentu isključivo za čitanje:

Listing 8: Pristup stavkama fakture


public IEnumerable<FakturaStavka> StavkeFakture
{
get { return FakturaStavkaList; }
}

Pogledajmo sada konstruktor.

Listing 9: Konstruktor
protected Faktura(long fakturaId, string brojFakture, DateTime datum, DateTime? datumValute,
long dobavljacId, long korisnikId, bool isKalkulisana,
double? rabatProcenat, IList<FakturaStavka> stavke)
: base(fakturaId)
{
if (dobavljacId == 0) throw new DomainException("Dobavljac mora biti zadat");
if (korisnikId == 0) throw new DomainException("Korisnik nije zadat");

BrojFakture = new BrojFakture(brojFakture);


BrojFakture.ValidateWithException(true, "Broj Fakture");

DatumFakture = datum;
DatumValute = datumValute;
DobavljacId = dobavljacId;
KorisnikId = korisnikId;
IsKalkulisana = isKalkulisana;
FakturaRabat = new Procenat(rabatProcenat);

FakturaStavkaList = new List<FakturaStavka>(stavke);


}

Konstruktor je zadužen za inicijalizaciju i kreiranje početnog stanja objekta. Takode,


konstruktor mora da obezbedi postavljanje objekta u konzistentno početno stanje. Zbog

37
toga, na početku, dodate su provere za obavezne parametre. Konvencija u programu je da
ukoliko id polje ima vrednost 0, to znači da vrednost nije inicijalizovana, pa se ta provera
radi za polja dobavljacId i korisnikId. Ukoliko vrednost nekog od obaveznih polja nije zadata,
baca se DomainException, klasa koja označava sve izuzetke, koje se dešavaju u sloju domena.
Iz koda konstruktora, takode, može se primetiti da je vidljivost konstruktora protected.
To je zato što direktan poziv konstruktora nije dozvoljen, već se objekat inicijalizuje pozivom
statičkih metoda fabrike, koje su definisane u samoj klasi:

Listing 10: Fabričke metode


public static Faktura CreateNew(long fakturaId, string brojFakture, DateTime datumFakture,
DateTime? datumValute, long dobavljacId, long korisnikId, double? rabatIznos)
{
return new Faktura(fakturaId, brojFakture, datumFakture,
datumValute, dobavljacId, korisnikId, rabatIznos, new List<FakturaStavka>());
}

public static Faktura Reconstituite(long fakturaId, string brojFakture, DateTime datumFakture,


DateTime? datumValute, long dobavljacId, long korisnikId, double? rabatIznos,
IList<FakturaStavka> stavke)
{
return new Faktura(fakturaId, brojFakture, datumFakture, datumValute,
dobavljacId, korisnikId, rabatIznos, stavke);
}

Kako za sada postoji samo jedan scenario za izmenu vrednosti fakture, dodata je posebna
metoda koja to omogućava.

Listing 11: Izmena podataka


public void IzmenaFakture(string brojFakture, DateTime datumFakture, DateTime? datumValute,
long dobavljacId, long korisnikId, double? rabatIznos, IList<FakturaStavka> noveStavke)
{
if (dobavljacId == 0) throw new DomainException("Dobavljac mora biti zadat");
if (korisnikId == 0) throw new DomainException("Korisnik mora biti zadat");

BrojFakture = new BrojFakture(brojFakture);


BrojFakture.ValidateWithException(true, "Broj Fakture");

DatumFakture = datumFakture;
DatumValute = datumValute;
DobavljacId = dobavljacId;
KorisnikId = korisnikId;
FakturaRabat = new Procenat(rabatIznos);
FakturaRabat.ValidateWithException(false, "Procenat rabata za fakturu");

FakturaStavkaList = noveStavke;
}

Kao što sto se iz priloženog koda vidi, prilikom promene vrednosti promenljivih, postoje
obavezna polja i za njih se vrši validacija. Klasa sadrži i ostale funkcije rad sa instancom
fakture. Tako postoji: metoda DodajStavku koja dodaje novu stavku u fakturi, metoda
GetIznosFakture, koja izračunava vrednost fakture sumirajući iznose svojih stavki, metoda
RabatUkupno, koja vraća ukupan rabat za fakturu sumirajući rabate stavki fakture i tako

38
dalje.

4.2.5 Vrednosni objekat Novac


Vrednosni objekat tipa Novac se najviše koristi u projektu, takode, novac se često koristi
u literaturi prilikom objašnjavanja vrednosnih objekata. Zbog toga, nalazimo da je on dobar
kandidat za opis.

Listing 12: Promenljive i konstruktor


public class Novac : BaseValueObject<Novac>
{
private const double PragOsetljivosti = 0.01;
private const int DecimalPlaces = 2;
public static readonly Novac NulaNovac = new Novac(0);
private readonly double? _iznos;

public Novac(double? iznos) : base(iznos.HasValue)


{
if (iznos.HasValue)
_iznos = Math.Round(iznos.Value, DecimalPlaces);
}

Vidimo da Novac nasleduje baznu klasu vrednosnih objekata BaseValueObject. Na


početku klase definisane su konstante koje se koriste u okviru klase Novac, ali, takode,
postoje i konstante koje se mogu koristiti i van klase kao što je to NulaNovac. Prilikom
konstruisanja, baznoj klasi se salje parametar koji indikuje da li je zadata vrednost za novac
i postavlja se vrednost vrednosnog objekta. Vrednosni objekti predstavljaju sjajno mesto
za centralizovanje logike oko nekog pojma, za koji programeri obično misle da nije potrebno
imati posebnu klasu. Takve metode su na primer:

Listing 13: Utilty metode


public string OznakaValute
{
get { return DinarOznaka; }
}

public static string DinarOznaka


{
get { return "Din."; }
}

public bool IsNulaIznos()


{
return Math.Abs(_iznos) < PragOsetljivosti;
}

public static string GetErrorMessage(double iznos)


{
return iznos < 0 ? "Iznos novca mora biti pozitivan" : null;
}

39
Ukoliko ne bi postojala ova klasa, ove provere i zakucane vrednosti bile bi svuda po kodu,
što bi otežalo održavanje koda, uvelo potencijalne greške, itd. U najboljem slučaju, bile bi
u nekoj Utility klasi, što, i dalje, nije najbolje rešenje.
Kako su potrebne matematičke operacije sa novcem, klasa Novac, dalje, sadrži operatore
sa rad za različitim tipovima podataka.

Listing 14: Operatori


public static Novac operator +(Novac n1, Novac n2)
{
return new Novac(n1.Iznos + n2.Iznos);
}

public static Novac operator +(Novac n, double d)


{
return new Novac(n.Iznos + d);
}

Nisu sve metode operatora prikazane gore, budući da one predstavljaju standardnu im-
plementaciju operatora. Na kraju, implementirane su apstraktne metode iz baznog tipa.
Metod GetErrorMessageCore vrši validaciju objekta, metod GetHashCodeCore vraća heš
kod i metod EqualsCore poredi dva objekta.

Listing 15: Validacija


protected override string GetErrorMessageCore()
{
return Iznos < 0 ? "Iznos novca mora biti pozitivan" : null;
}

protected override int GetHashCodeCore()


{
return (GetHashCode()*397) ˆ _iznos.GetHashCode();
}

protected override bool EqualsCore(Novac valueObject)


{
return Math.Abs(IznosChecked - valueObject.IznosChecked) < PragOsetljivosti;
}

4.2.6 Dogadaji domena


Dogadaji domena treba da rade kao i svi ostali tipovi dogadaja, sa kojima se srećemo
u programiranju. Tipičan scenario je da se kreira novi tip dogadaja u klasi koja sadži
neophodne detalje za izazivanje dogadaja. Nakon toga, potrebno je napisati kôd koji prihvata
i obraduje dogadaj.
Implementiranje počinje definisanjem interfejsa, koji predstavlja sve dogadaje u domenu.
Interfejs trenutno služi samo za označavanje svih objekata koji predstavljaju dogadaje.

40
Listing 16: Bazni interfejs za sve klase dogadaja
public interface IDomainEvent
{
}

Sledeći korak je definisati interfejs za sve klase koje obraduju dogadaje domena. Interfejs
sadrži jednu metodu, koja prihvata dogadaj kao parametar i nema povratni parametar.

Listing 17: bazni interfejs za sve klase hendlera


public interface IDomainEventHandler<T> where T : IDomainEvent
{
void Handle(T domainEvent);
}

Pošto smo definisali interfejse za sve klase koje predstavljaju dogadaje domena, i za
klase koje obraduju dogadaje, potrebno je implementirati kôd, koji omogućava izazivanje
tih dogadaja. Kako je patern dogadaja u domenu prilično generički patern, postoje različiti
načini na koje ova funkcionalnost može da se implementira. Ovde se koristi verzija gde se
dogadaji domena izazivaju kroz baznu klasu agregata. U baznoj klasi agregata dodaje se
sledeći kôd:

Listing 18: Integracija sa agregatima


public abstract class AggregateRoot : EntityBase
{
private readonly IList<IDomainEvent> _domainEvents;

protected AggregateRoot(long id)


: base(id)
{
_domainEvents = new List<IDomainEvent>();
}

public IList<IDomainEvent> Events


{
get { return _domainEvents; }
}

protected virtual void AddDomainEvent(IDomainEvent newEvent)


{
_domainEvents.Add(newEvent);
}

public virtual void ClearEvents()


{
_domainEvents.Clear();
}
}

Prethodni kôd pruža infrastrukturu klasama agregata za izazivanje dogadaja pozivom


metode AddDomainEvent, gde je parametar konkretni objekat, koji sadrži detalje dogadaja.

41
Takode, obezbeduje se interfejs za rad dispečeru dogadaja kroz kolekciju dogadaja Events i
metode ClearEvents, koja prazni listu dogadaja nakon njihove obrade.

4.2.6.1 Dispečer dogadaja


Sada je potrebno implementirati dispečer dogadaja. Zadatak dispečera je da za sve
dogadaje u agregatu pozove odgovarajuće metode za obradu, koje se nalaze u objektima
registrovanim za obradu tih dogadaja. Takode, dispečer treba da ponudi interfejs za regi-
strovanje svim klasama, koje žele da obraduju dogadaje. Stoga, potrebno je kreirati klasu
koja implementira sledeći interfejs.

Listing 19: Bazni interfejs dispečera


public interface IDomainEventsRegistrator
{
void RegisterHandler<TEventType, THandlerType>() where TEventType : IDomainEvent
where THandlerType : IDomainEventHandler<TEventType>;
}

Interfejs deklariše metodu RegisterHandler<TEventType, THandlerType> koja služi za


registrovanje klase za obradu dogadaja.
Za dati agregat, dispečer mora da prode kroz sve dogadaje koji su generisani u okviru
agregata. Za svaki dogadaj potrebno je pronaći odgovarajuću klasu (ili klase) koje su regi-
strovane za obradu trenutnog dogadaja. Nakon toga, mora da kreira instance klasa hendlera
i pozove metodu za obradu dogadaja. Ovo se obično postiže refleksijom, a kako se u projektu
koristi Prism biblioteka, implementacija dispečera potpomognuta je Unity kontejnerom.

Listing 20: Implementacija dispečera


public class DomainEventsDispatcher : IDomainEventsDispatcher
{
private readonly IUnityContainer _container;
private readonly IDictionary<Type, IList<Type>> _eventHandlers =
new Dictionary<Type, IList<Type>>();

public DomainEventsDispatcher(IUnityContainer container)


{
_container = container;
}

public void RegisterHandler<TEventType, THandlerType>() where TEventType : IDomainEvent


where THandlerType : IDomainEventHandler<TEventType>
{
var key = typeof (TEventType);
IList<Type> handlers = null;
if (_eventHandlers.ContainsKey(key))
{
handlers = _eventHandlers[key];
handlers.Add(typeof (THandlerType));
}
else
{
handlers = new List<Type>();

42
handlers.Add(typeof (THandlerType));
_eventHandlers.Add(key, handlers);
}
}

public void DispatchEvents(AggregateRoot item)


{
foreach (var domainEvent in item.Events)
{
Dispatch(domainEvent);
}
item.ClearEvents();
}

public void Dispatch(IDomainEvent domainEvent)


{
foreach (var eventHandler in _eventHandlers[domainEvent.GetType()])
{
dynamic handler = _container.Resolve(eventHandler);
handler.Handle((dynamic) domainEvent);
}
}
}

Na početku klase definisane su promenljive koje su potrebne za normalno funkcionisanje


dispečera. container koristi se prilikom kreiranja nove instance klase za obradu dogadaja,
dok heš mapa eventHandlers čuva listu registrovanih klasa za obradu dogadaja.
RegisterHandler metoda ima dva tipovska argumenta, gde prvi predstavlja tip dogadaja,
a drugi tip hendlera. Kako u listu dodaje samo tip hendlera za dati dogadaj, a onda se ta
lista smešta u heš mapu eventHandlers, implementacija ove metode je prilično jednostavna.
DispatchEvents metoda prolazi kroz sve dogadaje u agregatu i poziva metodu Dispatch,
koja je odgovorna za poziv svih hendlera prosledenog objekta dogadaja. Na kraju metode
brišu se svi dogadaji iz agregata, kako su svi obradeni.
Kako bi se obradio dogadaj, Dispatch metoda prihvata dogadaj kao parametar, identifi-
kuje sve hendlere datog dogadaja, kreira instancu klase hendlera uz pomoć Unity kontejnera
i poziva metodu Handle.

4.2.6.2 Pozivanje dispečera prilikom procesiranja transakcije


Na kraju obrade inicijalne akcije, potrebno je ugraditi poziv dispečera na kraju obrade
inicijalne akcije. Većina implementacija UnitOfWork paterna podržava mogućnost poziva
metoda na kraju ili na početku transakcije, pre nego što se komituju promene. To je, u
ovom slučaju, upravo i potrebno. Dogadaje je moguće procesirati i izvan jedne transakcije,
ali u tom slučaju je potrebno imati kôd koji obraduje slučaj kada se desi greška prilikom
procesiranja dogadaja. Zbog tih kompleksnosti, ovde je cilj procesirati dogadaje u istoj
transakciji. Kako se u projektu ne koristi nijedna gotova implementacija paterna UnitO-
fWork, već je ovaj patern implementiran manuelno, lako je dodati kôd za poziv dispečera na
početku transakcije.

43
Listing 21: Integracija sa jedinicom rada
public void Commit()
{
// Process Domain Events before transaction is started
ProcessDomainEvents();
// Persist data...
}

private void ProcessDomainEvents()


{
// process domain events
int loopCnt = 0;
bool processDomainEvents = true;

while (processDomainEvents)
{
var aggregatesWithEvents =
_uowPersistActions.Where(action => action.Aggregate.Events.Count > 0).ToList();

processDomainEvents = aggregatesWithEvents.Count > 0;

foreach (var action in aggregatesWithEvents)


{
_dispatcher.DispatchEvents(action.Aggregate);
}

loopCnt++;
if (loopCnt == MaxDomainEventsDepth)
throw new Exception("Maximum number of cycles " +
"for domain events reached: LoopCount:" + loopCnt);
}
}

U prethodnom kodu vidimo da se poziva metoda ProcessDomainEvents, pre nego što


se agregati snime u bazu podataka. U okviru metode za procesiranje dogadaja domena
izdvajaju se svi agregati u okviru kojih postoje kreirani dogadaji. Zatim, za svaki od tih
agregata poziva se dispečer dogadaja koji pretražuje odgovarajuće klase za njihovu obradu.
Ovaj process ponavlja se sve dok postoje agregati sa neprocesiranim dogadajima ili dok se
ne dostigne maksimalni broj iteracija (u tom slučaju se baca izuzetak).

44
5 Implementacija aplikacionog sloja
5.1 Komandna strana
Infrastruktura komande sastoji se iz tri osnovne komponente:

• Komande predstavljaju objekte koji sadrže podatke i koji se predaju na izvršavanje.

• Hendleri predstavljaju objekte koji sadrže biznis logiku potrebnu za obradu komandi.

• Dispečer predstavlja objekat koji je zadužen za povezivanje komandi i njihovih hen-


dlera, kao i za instanciranje potrebnih objekata.

5.1.1 Komande
Najpre, potrebno je kreirati Command interfejs:

Listing 22: Osnovni interfejs komande


public interface ICommand
{
}

Intefejs ICommand ne definiše nikakve metode za implementaciju, zato što su komande


samo obični DTO objekti koji nose podatke. Ipak interfejs ima dosta značaja, jer čini da
kôd bude čitljiviji i jednostavniji za razumevanje. Sledi primer konkretnog objekta komande,
koji služi za brisanje fakture:

Listing 23: Primer komande


public class BrisanjeFaktureCommand : ICommand
{
public BrisanjeFaktureCommand(long fakturaId)
{
FakturaId = fakturaId;
}

public long FakturaId { get; private set; }


}

Kao što vidimo iz primera, komande su prosti DTO objekti bez ponašanja čija je svrha
logički predstaviti komandu koja je naložena od strane korisnika i prenos podataka do
objekta, koji je zadužen za obradu te komande. Komande sadrže atribute, koji se inicijali-
zuju na strani klijenta, dok se njihove vrednosti koriste na strani hendlera. Nije dozvoljeno
menjati vrednosti atributa komandnih objekata u hendleru.
U konkretnom primeru, umesto parametra FakturaId komandni objekat može da sadrži
referencu na fakturu koju je potrebno obrisati. Medutim, takva praksa, generalno, smatra
se nepoželjnom, budući da se prilikom kreiranja i izvršavanja komande stvaraju zavisnosti,

45
koje su suvišne. Komande treba da budu prosti DTO objekti, koji sadrže atribute osnovnih
tipova podataka.

5.1.2 Hendleri
Zadatak hendlera je da obrade i procesiraju komandu. Oni predstavljaju implementaciju
servisa, koji se nudi klijentima kroz komande. Interfejs hendlera definišemo na sledeći način:

Listing 24: Osnovni interfejs hendlera


public interface ICommandHandler<in TCommand>
where TCommand : ICommand
{
void Handle(TCommand command);
}

Svaki hendler mora da sadrži metod Handle, koji prihvata komandu kao parametar. Sledi
primer hendlera komande za brisanje fakture:

Listing 25: Primer hendlera


public class BrisanjeFaktureCommandHandler : ICommandHandler<BrisanjeFaktureCommand>
{
private readonly IFakturaRepository _repository;

public BrisanjeFaktureCommandHandler(IFakturaRepository repository)


{
_repository = repository;
}

public void Handle(BrisanjeFaktureCommand command)


{
var faktura = _repository[command.FakturaId];
faktura.ValidirajBrisanje(true);
_repository.Remove(faktura);
}
}

Komande i hendleri u aplikaciji, zavisno od mesta u kome su definisani, mogu da predsta-


vljaju različite vrste servisa. Kako se hendler nalazi u aplikacionom sloju, on ne sme da sadrži
biznis logiku. Njegov zadatak je da koordinira procesom, a biznis odluke treba da prepusti
agregatima ili servisima domena. U ovom slučaju, hendler prepušta validaciju za brisanje
agregatu fakture. Sve zavisnosti, koje su potrebne za izvršavanje komande pribavljaju se
kroz konstruktor.

5.1.3 Dispečer komande


Sada kada su definisane komande i hendleri za komande moguće je krenuti sa njihovim
implementiranjem u projektu. Medutim, da bi ceo proces radio kako treba, potreban nam
je dispečer. Dispečer je odgovoran za instanciranje i poziv metode za obradu komande
odgovarajućeg hendlera, za komandu koju dobije na ulazu.

46
Listing 26: Osnovni interfejs dispečera
public interface ICommandDispatcher
{
void Execute<TCommand>(TCommand command)
where TCommand : ICommand;
}

Dispečer definiše generički metod Execute koji prihvata i izvršava komandu zadatog tipa.
Ne definiše povratni rezultat, jer komande uglavnom ne vraćaju rezultat. Izuzetak su krei-
rajuće komande, gde je rezultat definisan u samoj komandi.
Dispečer ima istu namenu kao i aplikacioni servisi. Medutim, dok je njihova namena ista,
izmedu njih primećujemo razlike. Aplikacioni servisi su komplikovaniji zato što grupišu više
povezanih zadataka, što znači da je klasa veća, tj. zavisna od većeg broja drugih objekata i
da nosi veliku odgovornost.
Implementiranje dispečera je jednostavnije zato što svaka komanda ima svoj jedinstveni
hendler. Svaki hendler bavi se obradom samo jedne komande, i samim tim, kôd je jednostav-
niji. Sa druge strane, nekada može biti komplikovanije implementirati i koristiti dišpečer.
Takve situacije su kada dispečer ne vraća nikakvu povratnu vrednost nakon izvršavanja
komande (kao što je to na primer ID novokreiranog objekta).
Kao što je ranije napomenuto, zadatak dispečera je da poveže komandu za njenim hendle-
rom i pozove hendler kako bi on izvršio tu komandu. Prethodna arhitektura nam omogućuje
veoma lako razrešavanje hendlera preko tipa komande, jer je svaki hendler vezan za jednu
komandu. Kako bi to implementirali u praksi, uključujemo Unity kontejer koji umnogome
pojednostavljuje implementaciju. Definišemo UnityCommandDispatcher na sledećin način:

Listing 27: Unity dispečer


public class UnityCommandDispatcher : ICommandDispatcher, IInternalCommandDispacher
{
private readonly IUnityContainer _container;

public UnityCommandDispatcher(IUnityContainer container)


{
_container = container;
}

public void Execute<TCommand>(TCommand command) where TCommand : ICommand


{
if (command == null)
{
throw new CommandProcessingException("Command object is null");
}

var handler = _container.Resolve<ICommandHandler<TCommand>>();

if (handler == null)
{
throw new CommandProcessingException("Command handler not found, type:"
+ typeof (TCommand));
}

47
try
{
handler.Handle(command);
}
catch (DomainException e)
{
throw new CommandProcessingException("Domain processing error" + e.Message);
}
}
}

Prvo se proverava da li je uopšte zadata komanda, onda se razrešava hendler i na kraju


se hendler poziva. Ukoliko dode do greške iz domena, ona se obraduje tako što se izaziva
greška procesiranja komande (kako bi se sloj domena zaštitio od klijenta).
Ovakva implementacija veoma je ekstenzibilna. Na primer, ovde je potrebno kreirati
transakcije prilikom procesiranja komande. To se radi bez promene originalne klase dispečera
– kreiranjem dekoratora:

Listing 28: Transakcioni dispečer


public class TransactionalCommandDispatcher : ICommandDispatcher
{
private readonly ICommandDispatcher _dispatcher;
private readonly IUnitOfWork _unitOfWork;

public TransactionalCommandDispatcher(ICommandDispatcher dispatcher, IUnitOfWork unitOfWork)


{
_dispatcher = dispatcher;
_unitOfWork = unitOfWork;
}

public void Execute<TCommand>(TCommand command) where TCommand : ICommand


{
using (_unitOfWork)
{
_dispatcher.Execute(command);
_unitOfWork.Commit();
}
}
}

5.2 Strana upita


Strana upita aplikacije razlikuje se od komandne strane. Na strani upita potrebno je
koncentrisati se na podatke koji su potrebni klijentu, tj. aplikaciji za prikaz. Model podataka
i upiti za pretragu podataka implementirani su u aplikacionom sloju.

5.2.1 Model podataka


Klase modela ne sadrže biznis logiku, već samo podatke koji su potrebni aplikaciji za
prikaz. Takode, kako služe samo za čitanje, nema potrebe brinuti o granicama agregata.

48
Iako model služi samo za čitanje, poželjno je razlikovati objekte koji nose identitet (tj.
predstavljaju entitete) od ostalih objekata. U tu svrhu, kreirana je bazna klasa Identi-
tyCarier, koja dosta podseća na baznu klasu entiteta BaseEntity, s tim što se u ovoj klasi
ne forsira zadavanje vrednosti id atributa prilikom kreiranja objekta. Kako je ovo jedina
razlika, detalji klase neće biti prikazani.
Strana upita fokusira se na pretragu podataka i vraćanje rezultata. Kako je na prikazu
potrebno pretraživati objekte nekog agregata, a zatim omogućiti detaljniji prikaz odredenog
agregata, u aplikaciji postoje dva modela prikaza podataka. Prvi model koristi se za sumarni
prikaz (prikazuje najosnovnije podatke o agregatu i koristi se prilikom pretraživanja i listanja
objekata). Drugi je detaljni model, koji se koristi za detaljni prikaz objekta agregata.

5.2.1.1 Sumarni model


Kako je skoro uvek potrebno pretražiti elemente, a zatim odabrati odredeni elemenat,
kako bi se smanjilo vreme procesiranja, pogodno je u tom slučaju vratiti samo osnovne i
sumarne detalje o agregatima. Takode, nije ni potrebno prikazati sve informacije korisniku
jer bi se prikaz previše iskomplikovao. Sledi primer klase za listanje faktura.

Listing 29: Primer sumarnog objekta


public class FakturaFilterResultItem : IdentityCarier
{
Konstruktori...

public string BrojFakture { get; set; }


public DateTime Datum { get; set; }
public DateTime? DatumValute { get; set; }
public double Iznos { get; set; }
public string ValutaOznaka { get; set; }
public long DobavljacId { get; set; }
public string DobavljacNaziv { get; set; }
public long KorisnikId { get; set; }
public string KorisnickoIme { get; set; }
public bool Kalkulisana { get; set; }
}

Kao što vidimo na osnovu primera fakture, model sadrži osnovne sumarne informacije
vezane za stavku fakture. Takode, uključeni su podaci o dobavljaču i korisniku koji je primio
fakturu, jer je potrebno prikazati te informacije prilikom listanja faktura.

5.2.1.2 Detaljni model


Kada se korisnik odluči koji objekat želi detaljno da pregleda ili izmeni, potrebno je pri-
kazati detaljne informacije njemu. Ovde je potrebno prikazati sve moguće detalje objekta,
i, često, je potrebno uključiti informacije iz različitih agregata, kako bi forma bila pregled-
nija. Iz tih razloga, detaljniji model ne sadrži samo ID atribute koji pokazuju prema drugim
agregatima, kao što je to slučaj na komandnoj strani, već se povlače sve neophodne infor-
macije i iz drugih agregata. Kako implementacija ovih klasa ne sadrži biznis logiku, a klase,

49
uglavnom, liče na DTO objekte, jer uglavnom samo sadrže podatke, izlaže se samo dijagram
klasa agregata fakture kao predstavnik detaljnog modela.

Dijagram 9: Dijagram klasa modela za prikaz detalja fakture

Na osnovu prethodnog dijagrama, možemo da zaključimo da se model sačinjava iz po-


dataka koji se nalaze u vise od jednog agregata. Takode, vidimo da sve klase koje sadrže
identitet nasleduju apstraktnu klasu IdentityCarier i to je uglavnom zbog jednostavnijeg
procesiranja u prezentacionom sloju. Takode, vidimo da klase sadrže samo propertije i ne
sadrže nikakve metode koje uključuju biznis logiku, što ih čini veoma nezanimljivim.

5.2.2 Upiti
Upit predstavlja zahtev za podacima iz izvora podataka koji dodatno sadrži filtere i druge
dodatne podatke, koji olakšavaju pretragu podataka i definišu način vraćanja rezultata. Iako
imaju veoma različitu ulogu od komandi, implementacija baznih klasa upita veoma je slična
implementaciji baznih klasa komandi. Jedina veoma bitna razlika je u tome što se handleri
upita implementiraju u sloju pristupa podacima, umesto u aplikacionom sloju, kao što je to
slučaj na komandnoj strani.
Počinje se definisanjem baznih interfejsa, koji nasleduju svi upiti. Interfejs jedino definiše
tip rezultata koji upit vraća.

50
Listing 30: Osnovni interfejs upita
public interface IQuery<TResult>
{
}

Kako u aplikaciji često postoji zahtev za zadavanjem upita, koji vraća više od jednog
elementa, ukupan broj elemenata i podržava straničenje, definisana je apstraktna klasa upita
BaseFilterQuery koju nasleduju svi upiti te vrste.

Listing 31: Osnovna klasa filter upita


public abstract class BaseFilterQuery<T> : IQuery<BaseFilterResult<T>>
{
Konstruktori ...
public int Limit { get; set; }
public int Offset { get; set; }
}

Ova klasa, takode, je generička i nasleduje interfejs IQuery sa parametrom tipa Base-
FilterResult, koji sadrži listu elemenata tipa T i ukupan broj elemenata, koji zadovolja-
vaju kriterijume upita. Takode, klasa sadrži i parametre koji ograničavaju rezultat. Limit
ograničava broj elemenata u rezultatu, dok Offset služi za straničenje rezultata.

Listing 32: Objekat za filter rezultate


public class BaseFilterResult<T>
{
public uint TotalCount { get; set; }
public IList<T> Results { get; set; }
}

Implementacija upita i hendlera za BaseFilterQuery vrstu upita neće biti opisana u da-
ljem tekstu, s obzirom na to da je implementacija interfejsa BaseFilterQuery analogna imple-
mentaciji intrefejsa IQuery. Sledeći korak je definisanje interfejsa za sve klase koje obraduju
upit.

Listing 33: Osnovni interfejs hendlera upita


public interface IQueryHandler<in TQuery, out TResult> where TQuery : IQuery<TResult>
{
TResult Handle(TQuery query);
}

Svaka klasa koja obraduje upit mora da zada dva generička parametra interfejsu IQue-
ryHandler. Prvi parametar TQuery predstavlja tip upita koji obraduje, dok drugi parametar
TResult predstavlja tip rezultata koji se vraća. Definisan je metod Handle, koji je zadužen
za obradu upita i koji ima povratni tip TResult.

51
Sada definišemo osnovni interfejs za sve dispečere upita.

Listing 34: Osnovni interfejs dispečera upita


public interface IQueryDispatcher
{
TResult Execute<TQuery, TResult>(TQuery query)
where TQuery : IQuery<TResult>;
}

Intefejs IQueryDispatcher sadrži metodu Execute koja je generička sa dva parametra,


TQuery, koji predstavlja tip upita i nalazi se kao ulazni parametar metode i TResult, koji
predstavlja tip rezultata obrade upita. Konkretna implementacija interfejsa postignuta je
upotrebnom Unity kontejnera.

Listing 35: Unity dispačer upita


public class UnityQueryDispatcher : IQueryDispatcher
{
private readonly IUnityContainer _container;

public UnityQueryDispatcher(IUnityContainer container)


{
_container = container;
}

public TResult Execute<TQuery, TResult>(TQuery query) where TQuery : IQuery<TResult>


{
if (query == null)
{
throw new ArgumentNullException("query");
}
var handler = _container.Resolve<IQueryHandler<TQuery, TResult>>();

if (handler == null)
{
throw new QueryHandlerNotFoundException(typeof (TQuery));
}

return handler.Handle(query);
}
}

Kao i za komandni dispečer, zahteva se referenca na Unity kontejner, koji se kasnije


koristi kod razrešavanja tipa klase i za kreiranje njene instance, kako bi se obradio upit. U
metodi Execute prvo se proverava da li je uopšte upit poslat, ukoliko jeste, onda se zahteva od
kontejnera da kreira instancu klase koja obraduje upit. Ukoliko je uspešno kreirana instanca
klase za obradu upita, poziva se njegova metoda za obradu upita.
Sledi implementacija objekta upita koji se koristi za preuzimanje detalja o fakturi preko
jedinstvenog identifikatora FakturaId.

52
Listing 36: Primer upita
public class GetFakturaByIdQuery : IQuery<FakturaEdit>
{
public long FakturaId { get; set; }
}

Upit GetFakturaByIdQuery služi za pretragu fakture po ID-u fakture. Implementira


direktno interfejs IQuery sa generičkim tipom rezultata FakturaEdit.
Kako je implementacija hendlera upita komplikovana, i kako se hendleri implementiraju
u sloju pristupa podacima, njihova implementacija opisuje se kasnije.

53
6 Sloj pristupa podacima - komandna strana
U današnje vreme, izvor podataka za aplikaciju gotovo je uvek baza podataka. Za imple-
mentiranje sloja pristupa izvoru podataka koriste se, obično, gotove biblioteke koje skrivaju
svu logiku kreiranja upita nad bazom podataka. Takve biblioteke su NHibernate i Enti-
tyFramework. Problem kod ovih biblioteka je što je sva implementirana logika sakrivena od
programera. Često se dešava da su one optimizovane za odredeni softver za rad sa bazom
podataka, dok za ostale imaju bagove, koje je, obično, vrlo teško otkriti i ispraviti, potrebno
je vreme za upoznavanje sa bibliotekom, a dešava se da novije verzije biblioteka nisu kom-
patibilne sa starijim verzijama. Cilj našeg rada je upoznati se sa arhitekturom pristupa
bazi podataka, tako da u projektu nije upotrebljana nijedna biblioteka. U nastavku tek-
sta, opisaćemo paterne koji učestvuju u izgradnji sloja za pristup izvoru podataka (u ovom
slučaju je to baza podataka), njihovu medusobnu interakciju i način na koji su implementi-
rani u samom projektu.

6.1 Repozitorijum
Za ovaj patern može se reći da predstavlja kičmu sloja za pristup podacima. Prethodno,
ovaj patern opisali smo iz ugla DDD-a, sada ćemo opisati sam patern i način na koji je
implementiran u projektu.
Repozitorijum posreduje izmedu biznis sloja i sloja pristupa podacima, pružajući inter-
fejs biznis sloju nalik interfejsu kolekcije objekata u memoriji. Kôd za pristup izvoru poda-
taka sakriven je u implementaciji repozitorijuma. Repozitorijum enkapsulira skup objekata
sačuvanih na nekom medijumu i dozvoljene operacije nad njima, na objektno orijentisani
način. Repozitorijum, takode, podržava čisto razdvajanje biznis sloja, od sloja pristupa
podacima i, takode, omogućava jednosmernu zavisnost sloja domena prema sloju pristupa
podacima.

6.1.1 Interakcije repozitorijuma


Dijagram 10 prikazuje interakcije repozitorijuma sa klijentom i izvorom podataka.

Dijagram 10: Interakcija repozitorijuma sa klijentom i izvorom podataka

54
Repozitorijum predstavlja most izmedu podataka i operacija nad podacima koji se na-
laze u različitim slojevima. Repozitorijum mapira podatke iz domena gde su podaci slabo
tipizirani (kao što je baza podataka), u domen gde su podaci strogo tipizirani, (kao što su
agregati modela domena). Repozitorijum izvršava odgovarajuće upite nad izvorom podataka
i mapira rezultate u objekte. Repozitorijumi često koriste paterne Izlaz na tabelu podataka
(eng. Table data gateway) i Fabriku (eng. Factory) kako bi mapirali podatke iz tabela baze
podataka u objektni model i kreirali agregate.
Klijentski kôd koristi repozitorijum na isti način, kao što koristi kolekciju objekata u
memoriji. Kolekcija sadži agregate koje je moguće filtrirati, ubaciti novi, ažurirati ili obrisati
postojeće. Činjenica da objekti, tipično, nisu smešteni direktno u repozitorijum nije izložena
kodu klijenta. Naravno, kôd koji koristi repozitorijum treba da bude svestan da kolekcija
može da se mapira na tabelu u bazi podataka sa stotinama hiljada redova, tako da poziv
metode All nad takvim repozitorijumom može napraviti velike probleme.
Tipični klijent repozitorijuma je aplikacioni servisni sloj. Repozitorijum definiše metode
za pristup podacima koje su potrebne servisnom sloju za obavljanje biznis zadatka. Repozi-
torijum obično se implementira upotrebom neke ORM (Object relational mapper) biblioteke
koja obično radi sav težak i dosadan posao mapiranja podataka. Klijenti nikada ne treba da
misle o SQL-u i mogu pisati kôd čisto u terminima objekata.
Situacije sa više mogućih izvora podataka, predstavljaju upotrebne slučajeve, gde vidimo
da repozitorijum dolazi do izražaja. Izvor podataka za repozitorijum ne mora biti samo re-
laciona baza podataka. Iz ovog razloga repozitorijum može biti veoma koristan u sistemima
sa većim brojem izvora podataka. Nekada smo zainteresovani da koristimo jednostavnu ko-
lekciju podataka u memoriji (kada želimo da testiramo zbog boljih performansi). Takode,
razumljivo je čuvati neke objekte u memoriji u toku izvršavanja aplikacije, (na primer, de-
finicioni podaci kojima se često pristupa, a koji se retko menjaju). Još jedan primer gde
repozitorijum može biti koristan je kada se kao izvor podataka koristi internet servis. Ser-
vis podatke može slati repozitorijumu u XML ili Json formatu, gde bi bio implementiran
XML/Json repozitorijum koji bi pravio biznis objekte iz tog izvora.
Objekti u sloju domena mogu biti slični ili isti modelu podataka, ali konceptualno oni
su veoma razlititi. Model domena je apstrakcija biznis problema, bogat je biznis logikom
i opisuje ponašanje kroz metode. Model podataka jednostavno je struktura skladištenja za
snimanje stanja objekta domena u zadato vreme. Zadatak repozitorijuma je držati ova dva
modela odvojenim i ne dozvoliti im da se pomešaju u jedan.
Poenta repozitorijuma nije napraviti testiranje jednostavnijim ili jednostavnije promeniti
tehnologiju skladištenja podataka. Poenta je, već držati biznis sloj razdvojen od tehničke
implementacije sloja pristupa podacima, tako da biznis model može nezavisno evoluirati bez
da bude pogoden tehnologijom kojom je sloj pristupa podacima implementiran.
Repozitorijum pruža funkcionalnost koja je slična funkcionalnosti koju pruža obična ko-
lekcija objekata, ali, takode, uključuje i funkcionalnost pravljenja upita nad kolekcijom.
Postoje dva načina na koji se mogu vršiti upiti nad repozitorijumom:
1. Može mu se proslediti objekat koji predstavlja upit (Specifikacija ili Objekat Upit).
2. Repozitorijum može izložiti metode koje odreduju specifični biznis kriterijum; klijent

55
poziva specijalizovani metod nad repozitorijumom, a repozitorijum formira upit u ime
klijenta.

6.1.1.1 Repozitorijum za preuzimanje podataka


Repozitorijum može funkcionisati u dva smera: preuzimanje i snimanje podataka.

Dijagram 11: Interakcije repozitorijuma prilikom preuzimanja podataka

Kada se koristi za preuzimanje objekata, repozitorijumu se prosleduje upit. Ovaj upit


može biti specifičan objekat ili parametrizovani metod. Repozitorijum je odgovoran za
izvršavanje tih upita. Kada se takav metod pozove, Repozitorijum kontaktira TDG kako
bi preuzeo sirove podatke iz baze podataka. Moguće je da repozitorijum pozove više TDG
objekta za preuzimanje podataka kako bi bio u mogućnosti da izgradi finalni rezultat. TDG
pruža sirove objektne podatke (DTO objekti ili mapa sa vrednostima). Repozitorijum onda
vrši neophodne transformacije tih podataka i poziva odgovarajuće metode fabrika kako bi se
konstruisao rezultat.

6.1.2 Repozitorijum za snimanje podataka


Drugi način na koji repozitorijum radi je pružanje logike potrebne za snimanje objekata.
Snimanje može biti jednostavno, kao što je serijalizacija objekta i njegovo slanje na TDG
kako bi se izvršila serijalizacija u fajl ili sofisticirano, kao što je kreiranje niza informacija sa
svim poljima i stanjem objekta.
Kada se koristi za snimanje podataka, klijentska klasa direktno komunicira sa fabrikom,
izvrši se kôd logike obrade zahteva i onda se objekat šalje repozitorijumu na snimanje. Re-
pozitorijum snima objekte upotrebom TDG objekata i opciono kešira objekte u memoriju.

6.1.3 Povezivanje
Dijagram 13 predstavlja pogled visokog nivoa na način implementacije repozitorijuma i
medusobnu saradnju klasa u sloju pristupa podacima.

56
Dijagram 12: Interakcije repozitorijuma prilikom snimanja podataka

Dijagram 13: Zavisnosti repozitorijuma

U centru se nalazi repozitorijum. Na levoj strani, nalaze se TDG objekti koji se dalje
vezuju na izvor podataka. Na desnoj strani, nalazi se fabrika čiji je zadatak kreiranje objekata
domena. Repozitorijumi se često implementiraju tako da zavise od interfejsa TDG objekata
i fabrika. Sa gornje strane, nalazi se klijentska klasa koja zahteva uslugu od repozitorijuma.

6.1.4 Implementacija Repozitorijuma


Postoje različite implementacije repozitorijuma i postoji dosta članaka i debata o načinu
implementiranja repozitorijuma. Žestoka rasprava vodi se o tome da li je dozvoljeno genera-
lizovati repozitorijum. Na primer, ne podržavaju sve klase repozitorijuma brisanje podataka.
Ukoliko bi se generalizovala implementacija repozitorijuma, ta metoda ne bi mogla da se im-
plementira za te specijalne slučajeve. Sa druge strane, generalizacijom se izbegava duplikacija
koda i kasnije se olaksava rad. Zbog toga, najčesće, koristi se hibridni način implementacije

57
repozitorijuma, gde svi repozitorijumi imaju isti interfejs i dodaju svoje prilagodene metode
za rad sa agregatima.

Dijagram 14: Dijagram klasa za repozitorijum proizvod

Osnovni interfejs repozitorijuma je generički, čime se omogućuje laka upotreba u ostalim


delovima aplikacije. Konkretni interfejsi repozitorijuma nasleduju bazni interfejs repozitori-
juma i dopunjuju ga svojim metodama.
Klijenti znaju i rade sa konkretnim interfejsima repozitorijma. Obično se i u imple-
mentaciji kreira generička bazna klasa koja implementira zajedničku funkcionalnost za sve
konkretne repozitorijume. Na kraju, konkretna implementaciona klasa nasleduje baznu klasu
implementacije i implementira konkretni interfejs repozitorijuma.

6.1.5 Implementacija bazne klase repozitorijuma


Glavni zadatak bazne klase repozitorijma je implementirati zajedničku funkcionalnost
za sve klase repozitorijma. Klasa implementira osnovni interfejs repozitorijma i interfejs
jedinice rada. Bazna klasa je, takode, generička i apstraktna (kao i sve ostale bazne klase).
Kako podržava mapiranje iz agregata u DTO objekat, zahteva generičke parametre za ove
dve klase.

Listing 37: Definicija bazne klase repozitorijuma


public abstract class BaseRepository<TAggregate, TDto>
: IBaseRepository<TAggregate>, IUnitOfWorkRep where TAggregate : AggregateRoot

Sledeća grupa metoda koju bazna klasa repozitorijuma sadrži jesu metode za implemen-
tiranje osnovnog interfejsa repozitorijuma.

58
Listing 38: Implementacija CRUD metoda
protected abstract TAggregate BuildAggregate(TDto dtoObject);

public TAggregate FindBy(long id)


{
if (_unitOfWork.CheckIfExists(id, typeof (TAggregate)))
return _unitOfWork.GetById<TAggregate>(id);
TDto dto = _tableGateway.GetById(id);
return BuildAggregate(dto);
}

public void Update(TAggregate item)


{
_unitOfWork.RegisterChanged(item, this);
}

public void Add(TAggregate item)


{
_unitOfWork.RegisterAdded(item, this);
}

public void Remove(TAggregate item)


{
_unitOfWork.RegisterRemoved(item, this);
}

public TAggregate this[long id]


{
get { return FindBy(id); }
set
{
if (FindBy(id) == null)
{
Add(value);
}
else
{
Update(value);
}
}
}

FindBy metoda pretražuje agregat za dati jedinstveni identifikator (id). Proverava da


objekat nije već keširan u objektu jedinice rada, a ako jeste, onda se objekat preuzima iz
memorije i vraća pozivaocu. Ukoliko objekat nije u memoriji, onda poziva se TDG objekat
kako bi se preuzeo DTO objekat iz izvora podataka, a onda poziva se apstraktna metoda
BuildAggregate čiji je zadatak da sagradi agregat od datog DTO objekta.
Metode Upate, Add i Remove jednostavno prijavljuju promene objektu jedinice rada,
bez ikakvog upisa na izvor podataka. Takode, jezik c# podržava sintaksu inteksera, kako
bi se olakšao rad sa repozitorijumima implementirana je metoda this, čija se implementa-
cija svodi na poziv prethodno opisanih metoda. Kako bi repozitorijum objekti mogli da
komuniciraju sa objektima jedinice rada potrebno je da klase repozitorijuma implementi-
raju interfejs IUnitOfWorkRep koji definiše metode za stvarno snimanje agregata na izvor
podataka. Implementacija interfejsa IUnitOfWorkRep sastoji se iz sledećih metoda:

59
Listing 39: Implementacija interfejsa IUnitOfWorkRep
protected abstract TDto CreateDto(TAggregate item);

protected virtual void PersistNewAggregate(TAggregate item)


{
TDto itemDto = CreateDto(item);
_tableGateway.InsertItem(itemDto);
}

protected virtual void PersistUpdatedAggregate(TAggregate item)


{
TDto itemDto = CreateDto(item);
_tableGateway.UpdateItem(itemDto);
}

protected virtual void PersistDeletedAggregate(TAggregate item)


{
TDto itemDto = CreateDto(item);
_tableGateway.DeleteItem(itemDto);
}

Sve metode su virtuelne i u njima je implementirano osnovno mapiranje upotrebom osnov-


nog DTO objekta. CreateDto je apstraktna metoda koju svaka klasa implementira i koja
nasleduje baznu klasu i njen zadatak je konstruisanje DTO objekta iz agregata. Sve metode
primaju agregat kao parametar, pozivaju metodu za mapiranje agregata u DTO objekat, i
na kraju, pozivaju TDG objekat za izvršavanje stvarne akcije nad izvorom podataka.

6.1.6 Implementacija repozitorijuma ProizvodRepository


Za svaki agregat postoji posebna klasa repozitorijuma. U daljem tekstu opisaćemo im-
plementaciju klase repozitorijuma implementiranu za agregat proizvoda.

Listing 40: Klasa ProizvodRepository


public class ProizvodRepository : BaseRepository<Proizvod, ProizvodGateway.ProizvodDto>,
IProizvodRepository

Klasa implementira interfejs IProizvodRepository, a ujedno nasleduje baznu klasu Ba-


seRepository sa paremetrima Proizvod - klasa agregata i ProizvodDto - objekat transfera
podataka. Sledi ostatak klase:

Listing 41: Metode


public Proizvod GetByNaziv(string naziv)
{
var resDto = _proizvodGateway.SelectByName(naziv);

if (resDto == null) return null;


return BuildAggregate(resDto);
}

protected override Proizvod BuildAggregate(ProizvodGateway.ProizvodDto dtoObject)

60
{
return _proizvodFactory.BuildAggregate(dtoObject);
}

protected override ProizvodGateway.ProizvodDto CreateDto(Proizvod item)


{
ProizvodGateway.ProizvodDto res = new ProizvodGateway.ProizvodDto();
res.ProizvodId = item.Id;
res.Naziv = item.Naziv.Naziv;
res.BarKod = item.BarKod.Kod;
res.NetoKolicina = item.NetoKolicina;
...

return res;
}

private void InsertGrupeProizvoda(Proizvod item)


{
ProizvodGrupaGateway.ProizvodGrupaDto temp = new ProizvodGrupaGateway.ProizvodGrupaDto();
temp.ProizvodId = item.Id;
foreach (var grupa in item.ProizvodGrupaIds)
{
temp.GrupaId = grupa;
_proizvodGrupaGateway.InsertItem(temp);
}
}

private void DeleteAllProizvodGrupe(long proizvodId)


{
_proizvodGrupaGateway.DeleteByProizvodId(proizvodId);
}

protected override void PersistUpdatedAggregate(Proizvod item)


{
base.PersistUpdatedAggregate(item);
DeleteAllProizvodGrupe(item.Id);
InsertGrupeProizvoda(item);
}

protected override void PersistDeletedAggregate(Proizvod entity)


{
DeleteAllProizvodGrupe(entity.Id);
base.PersistDeletedAggregate(entity);
}

protected override void PersistNewAggregate(Proizvod item)


{
base.PersistNewAggregate(item);
InsertGrupeProizvoda(item);
}

GetByNaziv metoda iz interfejsa IProizvodRepository, implementirana je tako što se prvo


poziva TDG objekat za proizvode, kako bi se selektovao proizvod po nazivu. Ukoliko je
pronaden proizvod po nazivu, onda se kreira agregat pozivom fabrike.
CreateDto jednostavno mapira agregat u DTO objekat koji TDG objekat koristi za ma-
piranje na izvor podataka. Takode, ovde su prepisane sve metode za snimanje i preuzimanje
agregata iz izvora podataka, jer je pored snimanja samog DTO objekta proizvoda potrebno
snimiti i grupe u koje je proizvod učlanjen.
InsertGrupeProizvoda metoda prima agregat, prolazi kroz sve grupe u koje je proizvod

61
učlanjen i dodaje ih na izvor podataka. DeleteAllProizvodGrupe jednostavno poziva metodu
na TDG objektu koji briše proizvode za dati objekat.
PersistUpdatedItem poziva baznu klasu koja vrši ažuriranje samog proizvoda, a zatim
briše sve grupe u kojima je proizvod bio učlanjen i učlanjuje opet proizvod u nove grupe.
Na sličnom principu rade i metode PersistDeletedItem i PersistNewItem.

6.2 Jedinica Rada (eng. Unit Of Work – UOW)


Kada se radi sa podacima u memoriji koje je potrebno snimiti na izvor podataka, veoma
je bitno imati evidenciju o izmenama nad objektima.
Jedan od najjednostavnijih načina sinhronizacije objektnog modela sa izvorom podataka
je istovremena primena promena na objekat i izvor podataka. Ovo može voditi prema
velikom broju poziva prema bazi, što na kraju utiče na performanse aplikacije. Dalje, zahteva
otvorenu transakciju tokom čitave obrade zahteva, što je nepraktično. Situacija je, čak, i
gora kada je potrebno voditi evidenciju o objektima koji su učitani (kako bi se izbegla
nekonzistentna čitanja).
Jedinica rada vodi evidenciju o svim promenama tokom biznis transakcije koje mogu
imati uticaj na izvor podataka. Kada se obrada korisničkog zahteva završi zadatak jedinice
rada je koordinisanje akcijom snimanja promena na izvor podataka.
Prilikom izmene objekata, koje je potrebno snimiti na izvor podataka, klijent kreira
instancu jedinice rada kako bi vodio evidenciju o promenjenim objektima. Svaki put kada
se kreira, promeni ili izbriše neki domen objekat potrebno je obavestiti objekat jedinice rada
o promeni.
Ključna stvar kod jedinice rada je ta da kada dode vreme za komitovanje, jedinica rada
odlučuje šta je potrebno uraditi. Otvara transakciju, proverava da objekti u bazi nisu iz-
menjeni od strane druge transakcije i snima podatke u bazu. Aplikacioni programeri nikada
eksplicitno ne pozivaju metode za ažuriranje podataka nad bazom podataka. Na ovaj način
oni nemaju potrebu da vode evidenciju o promenama ili da brinu o tome kako referencijalni
integritet u bazi podataka utiče na redosled promena u aplikacionom sloju.
Naravno, kako bi sve ovo funkcionisalo, objekti jedinice rada moraju biti svesni objekata
o kojima vode evidenciju. Postoji nekoliko načina na koje je moguće izvestiti objekat jedinice
rada o promenama:

• Sa registracijom klijenta korisnik objekta mora da zapamti da je potrebno registro-


vati promenjeni objekat instanci jedinici rada. Svi objekti koji nisu registrovani neće
biti snimljeni na izvor podataka prilikom komita. Ovo omogućuje da zaboravnost pro-
uzrukuje probleme, ali, takode, daje fleksibilnost dopuštajuci promene u memoriji bez
njihovog snimanja na izvor podataka. Ipak, ovo svojstvo pre može da izazove konfuziju
u odnosu na benefite koje pruža. U tu svrhu bolje je napraviti eksplicitnu kopiju.

• Sa registracijom objekta, odgovornost registrovanja promena smešta se na same


objekte. Obično se u ovom slučaju registracione metode smeštaju u samim objektima.
Kada se objekat učita iz izvora podataka, on se registruje kao “čist”, a kada se promeni

62
stanje objekta, objekat se registruje kao “zaprljan”. Kako bi ovo radilo, objekat jedinice
rada mora da se prosledi svakom pojedinačnom objektu u domenu ili mora da se nalazi
na nekom poznatom mestu u projektu kome svi objekti domena mogu da pristupe.
Prosledivanje objekta jedinice rada je zamorno ali obično nije problem imati instancu
objekta u nekoj vrsti sesionog objekta.
• Pored ova dva pristupa postoji i treći pristup koji je uveden kasnije. Ovaj pristup
prepušta odgovornost registrovanja promena objektima repozitorijuma. Objekat jedi-
nice rada izvršava sve neophodne pozive prema izvoru podataka u ime repozitorijuma.
Dobit kod ovog pristupa je da registracije o promenama objekata nisu vidljive klijentu
i dešavaju se u pozadini.

6.2.1 Implementacija jedinice rada


Takode, dosta se diskutuje i o načinu implementiranja paterna jedinica rada. Na internetu
je moguće naći različite implementacije ovog paterna. Problem je pronaći odgovarajuću
implementaciju tj. onu koja se najbolje uklapa u projekat.
Kako od načina implementiranja jedinice rada uveliko zavisi izgled klijentskog koda, de-
finišu se prvo zahtevi klijentskog koda. Svaka biznis transakcija može zahtevati različite
tipove objekata repozitorijuma. Takode, potrebno je omogućiti kreiranje objekata repozito-
rijuma nezavisno od objekata jedinice rada. Osim toga, u toku biznis transakcije svi objekti
repozitorijuma moraju vršiti upis podataka pod istom transakcijom koju je objekat jedi-
nice rada inicijalizovao. Registrovanje izmenjenih objekata prepušta se repozitorijumu, kako
bi se izbegle nenamerne greške programera i sklonio sav tehnički kôd iz klijentskog koda.
Klijentski kôd jedinice rada treba da predstavlja varijaciju sledećeg koda:

Listing 42: Primer klijentskog koda jedinice rada


using(unitOfWork)
{
// obrada klijentske biznis transakcije
unitOfWork.Commit();
}

Kako bi se zadovoljili ovi klijentski zahtevi objekat jedinice rada mora da implementira
samo jednu metodu bitnu za klijenta Commit. Kako objekti jedinice rada rade direktno
sa izvorom podataka, i verovatno postoji potreba da se oslobode resursi nakon transakcije,
uništavanje ovakvog objekta je eksplicitno. Sledeći interfejs definisan je u tu svrhu:

Listing 43: Osnovni interfejs jedinice rada


public interface IUnitOfWork : IDisposable
{
void Commit();
}

Sada kada imamo definisan interfejs, potrebno je pružiti implementaciju. Implementacija se

63
nalazi u sloju pristupa podacima i u daljem tekstu opisujemo način na koji je implementiran
interfejs. Prvo se opisuje način na koji je implementirana evidencija izmenjenih objekata.

6.2.1.1 Evidencija izmenjenih objekata


U daljem tekstu se opisuje način implementiranja odgovornosti evidentiranja izmenjenih
objekata tokom biznis transakcije. U osnovi vrši se evidencija tri vrste promena nad objek-
tima: brisanje, dodavanje i izmena objekata. Prvo se definišu objekti i promenljive koji služe
za predstavljanje svih tih promena.

Listing 44: Objekti za evidenciju izmenjenih objekata


private enum UowOperation { Add, Update, Delete }

private class PersistOperation


{
public AggregateRoot Aggregate { get; set; }
public IUnitOfWorkRep Repository { get; set; }
public UowOperation Operation { get; set; }
}

private readonly Dictionary<Tuple<long, Type>, AggregateRoot> _cachedAggregates;


private readonly LinkedList<PersistOperation> _uowPersistActions;

Kreirana je enumeracija koja sadrži tri vrednosti za svaku od operacija. Takode, kreiran je
objekat koji predstavlja akciju za snimanje i sastoji se od agregata koji je potrebno snimiti,
repozitorijuma koji treba da sprovede operaciju snimanja i operacije, koja indikuje koju
metodu repozitorijuma je potrebno pozvati. Heš mapa cachedAggregates služi za keširanje
agregata kako se ne bi više puta preuzimao isti agregat iz izvora podataka u toku transakcije.
Takode, ovim keširanjem izbegava se nekonzistentno čitanje. Lista ouwPersistActions drži
referencu na sve operacije koje je potrebno izvršiti u redosledu u kome su ubačene. Sada je
potreban kôd koji pruža interfejs klijentskim klasama za registrovanje promena:

Listing 45: Metode za registraciju izmenjenih objekata


public void RegisterAdded(AggregateRoot entity, IUnitOfWorkRep repository)
{
var addOperation = new PersistOperation { ... }
_uowPersistActions.AddLast(addOperation);
_cachedAggregates.Add(new Tuple<,>(entity.Id, entity.GetType()), entity);
}

public void RegisterChanged(AggregateRoot entity,


IUnitOfWorkRep repository)
{
var changedTuple = new Tuple<,>(entity.Id, entity.GetType());
if (_cachedAggregates.ContainsKey(changedTuple)) return;

var updateOperation = new PersistOperation { ... }


_uowPersistActions.AddLast(updateOperation);
_cachedAggregates.Add(changedTuple, entity);
}

64
public void RegisterRemoved(AggregateRoot entity,
IUnitOfWorkRep repository)
{
var delTuple = new Tuple<,>(entity.Id, entity.GetType());
if (_cachedAggregates.ContainsKey(delTuple)) return;

var deleteOperation = new PersistOperation { ... }


_uowPersistActions.AddLast(deleteOperation);
}

Kako se samo nove operacije dodaju u listu uowPersistActions, implementacija ovih


metoda veoma je jednostavna. Ovde, takode, možemo da vidimo implementaciju keširanja
objekata koji učestvuju u toku transakcije. Keširanje ovde ima dvojaku ulogu, prva je samo
keširanje podataka, a druga je sprečavanje duplog unosa iste operacije za jedan agregat u
toku transakcije. Sledi kôd metoda koje pružaju interfejs repozitorijumima za preuzimanje
keširanog agregata.

Listing 46: Metode za preuzimanje keširanih objekata


public bool CheckIfExists(long id, Type t)
{
return _cachedEntities.ContainsKey(new Tuple<long, Type>(id, t));
}

public TEntity GetById<TEntity>(long id) where TEntity : AggregateRoot


{
return (TEntity) _cachedEntities[new Tuple<long, Type>(id, typeof (TEntity))];
}

Metoda CheckIfExists proverava da li je tip agregata sa datim ID-em keširan. Dok


generička metoda GetById vraća agregat datog tipa iz keša.

6.2.1.2 Snimanje podataka


U ovom delu opisujemo način na koji se upravlja konekcijom, transakcijom i kako se
snimaju sve promene na izvor podataka. Komitovanje promena postiže se sledećim kodom:

Listing 47: Komitovanje


public IDbConnection Connection { get; private set; }
public IDbTransaction Transaction { get; private set; }

public void Commit()


{
// Process Domain Events before transaction is started
ProcessDomainEvents();

_connectionPool.LockConnection(Connection);
Transaction = Connection.BeginTransaction();
try
{
PersistData();
Transaction.Commit();
}

65
catch (Exception e)
{
Clear();
Transaction.Rollback();
throw;
}
finally
{
Transaction.Dispose();
_connectionPool.UnlockConnection(Connection);
}
}
public void Dispose()
{
if (_connectionPool.IsLocked(Connection))
_connectionPool.UnlockConnection(Connection);
}

˜UnitOfWork() { Dispose(); }

Iz priloženog koda vidimo da UnitOfWork klasa drži reference na aktivnu konekciju i


aktivnu transakciju. Metoda Commit poziva se prilikom snimanja svih promena na izvor
podataka. Pre nego što se krene sa snimanjem svih promena na izvor podataka procesiraju
se prvo svi dogadaji domena. Nakon toga se eksplicitno zahteva upotreba tj. zaključavanje
objekta konekcije, startuje se transakcija i poziva se metoda za snimanje podataka. Na
kraju transakcije, komituju se promene. Ukoliko dode do greške prilikom snimanja podataka,
poništavaju se sve promene.
Metoda Dispose poziva se prilikom uništavanja objekta i ovde se osigurava da objekat
konekcije nije zaključan.
Svakako najvažnija i najzanimljivija metoda ove klase je metoda PersistData. Njen za-
datak, zapravo, je da snimi podatke na izvor podataka. Implementacija ove metode je
jednostavna, jer jednostavno prolazi kroz kolekciju akcija za snimanje i zavisno od akcije
poziva odgovarajući metod repozitorijuma.

Listing 48: Metoda za snimanje podataka


private void PersistData()
{
foreach (var action in _uowPersistActions)
{
switch (action.Operation)
{
case UowOperation.Delete:
action.Repository.PersistDeletedItem(action.Aggregate);
break;
case UowOperation.Update:
action.Repository.PersistUpdatedItem(action.Aggregate);
break;
case UowOperation.Add:
action.Repository.PersistNewItem(action.Aggregate);
break;
default:
throw new Exception("Not supported Unit of work operation: " + action.Operation);
}
}
}

66
Iz priloženog vidimo da jedinica rada koristi repozitorijume za konačnu komunikaciju sa
izvorom podataka. Kako bi se omogućila ova funkcionalnost svaki repozitorijum mora da
implementira sledeći interfejs:

Listing 49: Interfejs IUnitOfWorkRep


public interface IUnitOfWorkRep
{
void PersistNewItem(AggregateRoot item);
void PersistUpdatedItem(AggregateRoot item);
void PersistDeletedItem(AggregateRoot item);
}

6.3 Izlaz na tabelu podataka (eng. Table Data Gateway – TDG)


TDG objekat predstavlja objektno orijentisani ekvivalent tabele(ili view objekta) u re-
lacionoj bazi podataka. Namera ovog paterna je sakriti interakciju sa tabelom. U većini
slučajeva, TDG objekat saraduje sa relacionim modelom, imajuci 1:1 vezu sa tabelom. U re-
lacionoj implementaciju, TDG rukuje SQL upitima, izlažući specifičan interfejs koji odgovara
potrebama za rad sa podacima u tabeli.
TDG je kapija, interfejs na posebnu komponentu ili podsistem koji pruža pristup svojim
podacima apstraktovanjem cele tabele u bazi podataka (fajla ili bilo koje strukture) iza
interfejsa jednog objekta. Sve standardne CRUD operacije izvršavaju se upotrebom metoda
TDG objekta. Za SQL bazu podataka, TDG metodi direktno odgovaraju SQL upitima:
INSERT, SELECT, UPDATE, DELETE. Argumenti metoda se mapiraju direktno u SQL
upite. Generalno, TDG pruža sve metode za pristup izvoru podataka, uključujući i metode
za pretragu.
TDG pruža centralnu tačku pristupa izvoru podataka. Zbog toga, gotovo je nemoguće
implementirati ovaj patern sa objektima koji sadrže interno stanje. TDG objekti ne treba
da sadrže stanje, ili da pamte podatke izmedu poziva. TDG objekti se obično implemen-
tiraju bez stanja. Uloga ovih objekata je jednostavno prebacivanje podataka u različite
reprezentacije.
TDG patern je specijalno koristan kada je kôd pristupa baziran na šablonu ili kada se
kôd automatski generiše.
TDG ima jednostavan interfejs, obično se sastoji od nekoliko metoda pretrage kako bi
preuzeo podatke iz baze podataka i CRUD metoda. Svaki metod mapira ulazne parametre
u SQL poziv i izvršava SQL kroz konekciju prema bazi podataka.
Najvarljivija stvar kod TDG paterna je kako on vraća informacije iz upita. Moguće je
vratiti jednostavne strukture podataka, kao što je to mapa. Upotreba mapa za prosledivanje
podataka kroz aplikaciju je loša praksa zato što se zaobilaze provere u vremenu kompajliranja
i ne postoji eksplicitan interfejs. Bolja alternativa je koristiti DTO (Data Transfer Object)
objekte.

67
Najveći deo vremena, TDG koristi se za mapiranje podataka na jednu tabelu u bazi
podataka. U veoma jednostavnim aplikacijama, moguće je imati jedan TDG objekat koji
upravlja svim metodima za pristup svim tabelama u bazi. Moguće je, takode, imati jedan
TDG objekat koji se mapira na jedan view objekat u bazi podataka. TDG objekati se u nekim
situacijama kreiraju za komplikovanije upite koji se ne čuvaju u bazi podataka. Očigledno je
da TDG objekti koji su bazirani na view objekte u bazi često ne mogu da ažuriraju podatke
i tako oni ne sadrže logiku vezanu za brisanje i ažuriranje podataka. Medutim, ukoliko
je moguće ažurirati podatke, onda skrivanje operacija ažuriranja iza metode TDG objekta
predstavlja veoma dobru tehniku.

6.3.1 Implementacija bazne TDG klase


Kako je zadatak svih TDG klasa mapiranje podataka izmedu različih reprezentacija,
postoje zajedničke funkcionalsnosti koje sve TDG klase implementiraju. Najbolje mesto za
smeštanje tih funkcionalnosti je bazna klasa. Bazna klasa je generička i apstraktna.

Listing 50: CRUD metode


public abstract class BaseTableGateway<T> : IReadMapper<T>
{
public abstract void PrepareSelById(long id, IDbCommand command);
public abstract void PrepareInsert(T item, IDbCommand command);
public abstract void PrepareUpdate(T item, IDbCommand command);
public abstract void PrepareDelete(T item, IDbCommand command);
public abstract T Retrieve(IDataReader reader);

Bazna klasa, takode, sadrži apstrakne metode koje obavljaju osnovne CRUD operacije
i koje moraju da implementiraju sve TDG klase. Kako je efikasnije komunicirati sa bazom
podataka putem pripremljenih upita, zadatak metoda je da pripreme komandu za datu
operaciju. Takode, ova klasa nasleduje interfejs IReadMapper koji definiše metodu Retrive.
Ona treba da mapira i kreira DTO objekat iz Reader objekta i implementira je apstraktno.
Bazna klasa, takode, nudi korisne metode koje se često upotrebljavaju prilikom preuzimanja
podataka iz baze podataka.

Listing 51: CRUD apstraktna implementacija


protected IDbCommand CreateCommand()
{
IDbCommand command = UnitOfWork.Current.CreateCommand();
return command;
}

public T GetById(long id)


{
IDbCommand command = CreateCommand();
PrepareSelById(id, command);
return FetchOneObject(command);
}

public void InsertItem(T item)


{

68
IDbCommand command = CreateCommand();
PrepareInsert(item, command);
ExecuteCommand(command);
}

public void UpdateItem(T item)


{
IDbCommand command = CreateCommand();
PrepareUpdate(item, command);
ExecuteCommand(command);
}

public void DeleteItem(T item)


{
IDbCommand command = CreateCommand();
PrepareDelete(item, command);
ExecuteCommand(command);
}

Ove metode zapravo sprovode osnovne CRUD operacije. Metoda CreateCommand po-
ziva se kada je potrebno kreirati novu komandu u okviru trenutne transakcije i ona samo
preusmerava poziv jedinici rada koja zapravo kreira komandu. Algoritam je sličan za sve
ostale metode. Prvo se kreira komanda, onda se pozove operacija koja kreira i priprema
SQL upit za izvršavanje i na kraju se izvršava komanda/upit. Izuzetak je GetById metoda,
gde se na kraju vraća pročitani DTO objekat.
Sledi još nekoliko korisnih metoda koje se nalaze u baznoj klasi:

Listing 52: Uslužne metode


protected static int BoolToInt(bool boolValue)
{
return boolValue ? 1 : 0;
}

protected static void AddParameter(IDbCommand command, string name, object value)


{
IDbDataParameter param = command.CreateParameter();
param.ParameterName = "@" + name;
param.Value = value;
command.Parameters.Add(param);
}

public static void ExecuteCommand(IDbCommand command)


{
CommandExecutor.ExecuteCommand(command);
}

public T FetchOneObject(IDbCommand command)


{
return CommandExecutor.FetchOneObject(command, this);
}

public IList<T> FetchMulObjects(IDbCommand command)


{
return CommandExecutor.FetchMulValues(command, this);
}

69
Ove metode korisne su za sve klase koje nasleduju baznu klasu, kao i za samu baznu klasu.
BoolToInt metoda za logičku vrednost vraća brojčanu vrednost jer većina baza podataka ne
podržavaju logički tip podataka. AddParameter metoda se koristi prilikom pripreme upita,
gde se parametar mapira na odredenu vrednost. Metode ExecuteCommand, FetchOneObject
i FetchMulObjects, su uslužne metode, ali kako se ta funkcionalnost razlikuje od ostatka
funkcionalnosti, njihova implementacija je izdvojena u posebnu klasu CommandExecutor.

6.3.1.1 Klasa Command Executor


CommandExecutor je statička klasa koja implementira tri statičke metode opisane ranije.

Listing 53: Metoda za preuzimanje više redova podataka iz baze podataka


public static IList<TResult> FetchMulValues<TResult>(IDbCommand command,
IReadMapper<TResult> mapper)
{
IList<TResult> result;
IDataReader reader = null;
try
{
result = new List<TResult>();

Log.Log("Fetch Multiple Values:" + command.CommandText, Category.Debug, Priority.Low);

reader = command.ExecuteReader();

while (reader.Read())
{
result.Add(mapper.Retrieve(reader));
}
if (!reader.IsClosed) reader.Close();
}
catch (Exception e)
{
Log.Log("FetchMulValues exception:" + e, Category.Exception, Priority.High);
if (reader != null && !reader.IsClosed) reader.Close();
throw;
}

return result;
}

FetchMulValues je generička metoda, koja vraća listu objekata tipa TResult. Ima dva
parametra; prvi je komanda u koju se nalazi SQL upit, a drugi je referenca na objekat koji
zna kako da iščita rezultate upita u objekat i koji implementira interfejs IReadMapper za
objekat tipa TResult. Kako se ovde zapravo komunicira sa bazom podataka postoji velika
verovatnoća za izbijanje greške u ovom kodu. Zbog toga se sve loguje kako bi se kasnije lakše
utvrdio uzrok greške. Izvršava se upit nad bazom podataka i iščitavaju se objekti. Kada
se završi sa čitanjem, zatvara se objekat. Ukoliko se desi neka greška, loguje se ta greška i
zatvara se objekat čitanja (ukoliko je to moguće).
Metoda FetchOneObject je samo specijalni slučaj prethodno opisane metode i iz tog
razloga neće biti opisana.

70
Listing 54: Izvršavanje komande
public static void ExecuteCommand(IDbCommand command)
{
try
{
Log.Log("Execute command:" + command.CommandText, Category.Debug, Priority.Low);

int cmdCnt = command.ExecuteNonQuery();

Log.Log("Affected Count:" + cmdCnt, Category.Debug, Priority.Low);


}
catch (Exception e)
{
Log.Log("Command execution failed - " + e, Category.Exception, Priority.High);
throw;
}
}

Metoda ExecuteCommand izvršava komandu u kontrolisanim uslovima. Loguje tekst


komande i izvršava tu komandu. U slučaju greške, loguje se greška.

6.3.2 Implementacija ProizvodGateway klase


Kako bi kompletirali priču o TDG klasama, potrebno je opisati implementaciju jedne kon-
kretne klase. Kako je ranije opisana klasa repoztorijuma ProizvodRepository, sada opisujemo
klasu ProizvodGateway.

Listing 55: Definicija ProizvodGateway klase


public class ProizvodGateway : BaseTableGateway<ProizvodGateway.ProizvodDto>
{

Klasa ProizvodGateway nasleduje klasu BaseTableGateway, gde se kao generički parame-


tar zadaje DTO klasa ProizvodDto.

Listing 56: Statička klasa FieldNames


public static class FieldNames
{
public static readonly string Id = "proizvod_id";
public static readonly string Naziv = "naziv";
public static readonly string NetoKolicina = "neto_kolicina";
...
}

Uvedena je praksa da svaka konkretna TDG klasa sadrži statučku klasu u sebi pod
nazivom FieldNames koja definiše polja u tabeli na koja se mapiraju podaci.

71
Listing 57: Klasa ProizvodDto
public class ProizvodDto
{
public long ProizvodId { get; set; }
public string Naziv { get; set; }
public string BarKod { get; set; }
...
}

ProizvodDto je jednostavni objekat transfera podataka koji sadrži proste tipove podataka
i služi za iščitavanje i snimanje podataka u bazi podataka.
Sledi implementacija apstraktnih metoda iz bazne TDG klase.

Listing 58: Priprema upita za selektovanje po id-u


private static readonly string SelectByIdCommandText =
string.Format(
@"select {0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9} from proizvod where {0}=@{0}",
FieldNames.Id, FieldNames.Naziv, FieldNames.NetoKolicina, FieldNames.DaniUpotrebe,
FieldNames.ProizvodjacId, FieldNames.BarKod, FieldNames.MaxMarza,
FieldNames.JedinicaMereId, FieldNames.PoreskaStopaId, FieldNames.NetoKolicinaJmId);

public override void PrepareSelById(long id, IDbCommand command)


{
command.CommandText = SelectByIdCommandText;

command.Prepare();

AddParameter(command, FieldNames.Id, id);


}

Prva metoda je PrepareSelById. Ona postavlja tekst upita za selektovanje jednog reda po
id-u, poziva metodu objekta IDbCommand, kako bi se pripremila operacija za selektovanje
podataka, i na kraju, dodaje se vrednost dinamičkog parametra pozivom metode AddPara-
meter iz bazne klase. Kako je tekst selektovanja podataka isti za svaku instancu objekta,
tekst komande je smešten u statičku konstantu pored same metode.

Listing 59: Priprema upta za insert


// Insert
private static readonly string InsertCommandText =
string.Format(
"insert into proizvod ({0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9})
VALUES (@{0}, @{1}, @{2}, @{3}, @{4}, @{5}, @{6}, @{7}, @{8}, @{9})",
FieldNames.Naziv, FieldNames.BarKod, FieldNames.NetoKolicina,
FieldNames.MaxMarza, FieldNames.JedinicaMereId, FieldNames.PoreskaStopaId,
FieldNames.ProizvodjacId, FieldNames.DaniUpotrebe, FieldNames.Id,
FieldNames.NetoKolicinaJmId);

public override void PrepareInsert(ProizvodDto item, IDbCommand command)


{
command.CommandText = InsertCommandText;

command.Prepare();

72
AddParameter(command, FieldNames.Naziv, item.Naziv);
AddParameter(command, FieldNames.BarKod, item.BarKod);
AddParameter(command, FieldNames.NetoKolicina, item.NetoKolicina);
...
}

Metoda PrepareInsert po svojoj implementaciji veoma je slična prethodnoj metodi, s tim


što je komplikovanija i razlikuje se u upitu koji izvršava. Implementacija metoda Prepare-
Update i PrepareDelete u mnogome je slična implementaciji ovog metoda, algoritam je isti,
jedino se razlikuje upit koji se izvršava.

Listing 60: Mapiranje podataka


public override ProizvodDto Retrieve(IDataReader reader)
{
var p = new ProizvodDto();
p.ProizvodId = MappHelper.GetDbInt(reader[FieldNames.Id]);
p.Naziv = MappHelper.GetDbString(reader[FieldNames.Naziv]);
p.BarKod = MappHelper.GetDbString(reader[FieldNames.BarKod]);
...
return p;
}

Na kraju, metoda Retrieve je zadužena za mapiranje podataka u objekat iz baze podataka.


Ovde se koriste konstante definisane u statičkoj klasi FieldNames se univerzalna statička
klasa za mapiranje tipova podataka MappHelper.

6.4 Fabrika
Kako je ovaj patern veoma poznat i kako se koristi veoma često, u daljem tekstu ne opisuje
se sam patern, već i način na koji je on implementiran u projektu za izgradnju agregata.

6.4.1 Implementacija bazne klasa fabrika


Repozitorijum koristi fabrike. Takode, za fabrike se implementira bazna klasa koja sadrži
zajedničku funkcionalnost.

Listing 61: Bazna klasa fabrika


public abstract class BaseFactory<TAgg, TDto> where TAgg : AggregateRoot
{
public TAgg BuildAggregate(TDto dto)
{
return CreateAggregate(dto);
}

protected abstract TAgg CreateAggregate(TDto dto);


}

73
Apstraktna klasa BaseFactory objedinjuje svu zajedničku funkcionalnost fabrika. Za
sada je implementacija klase jednostavna i sadrži samo metodu za kreiranje agregata iz datog
DTO objekta. Ona dalje poziva apstraktnu metodu CreateAggregate, koja sadrži logiku za
konstruisanje agregata. Klasa ima dva generička parametra: TAgg predstavlja tip agregata
koji je potrebno konstruisati, dok TDto predstavlja tip DTO objekta u koji se smeštaju
sirovi podaci preuzeti iz izvora podataka.

6.4.2 Implementacija fabrike ProizvodFactory


Kako bi objekat fabrike bio u mogućnosti da rekonstruiše agregat, potrebna mu je re-
ferenca na DTO objekat glavnog entiteta (koji se mora rekonstruisati). U velikom broju
slučajeva to nije dovoljno i tada klase fabrike u svom konstruktoru zahtevaju instance na
ostale TDG objekte koji su potrebni za konstrukciju agregata.

Listing 62: Klasa ProizvodFactory


public class ProizvodFactory : BaseFactory<Proizvod, ProizvodGateway.ProizvodDto>
{
private readonly ProizvodGrupaGateway _proizvodGrupaGateway;

public ProizvodFactory(ProizvodGrupaGateway proizvodGrupaGateway)


{
_proizvodGrupaGateway = proizvodGrupaGateway;
}

protected override Proizvod CreateAggregate(ProizvodGateway.ProizvodDto proizDto)


{
Proizvod selProizvod = null;

IList<ProizvodGrupaGateway.ProizvodGrupaDto> grupe =
_proizvodGrupaGateway.SelectByProizvodId(proizDto.ProizvodId);

selProizvod = Proizvod.Reconstitute(proizDto.ProizvodId,
proizDto.Naziv, proizDto.BarKod, proizDto.NetoKolicina,
proizDto.NetoKolicinaJmId, proizDto.DaniUpotrebe,
proizDto.MaxMarza, proizDto.JedinicaMereId, proizDto.PoreskaStopaId,
proizDto.ProizvodjacId, grupe.Select(x => x.GrupaId));

return selProizvod;
}
}

U prethodnom primeru vidimo da klasa ProizvodFactory nasleduje baznu klasu fabrike


BaseFactory upotrebom generičkih parametara Proizvod (tip objekta agregata) i Proizvod-
Gateway.ProizvodDto (tip DTO objekta). Agregat proizvoda sadrži i grupe proizvoda. Zbog
toga je potrebno preuzeti i sve grupe proizvoda za dati proizvod. Kako bi se preuzele sve
grupe proizvoda kao parametar se zahteva referenca na objekat ProizvodGrupaGateway.
Metoda CreateAggregate implementira funkcionalnost kreiranja agregata. Kako je kao
parametar dobijen DTO objekat proizvoda, potrebno je preuzeti sve grupe za dati proizvod.
To se postiže pozivanjem metode SelectByProizvodId objekta ProizvodGrupaGateway. Kada
se preuzmu sve grupe, poziva se statički metod agregata, koji je zadužen za rekonstrukciju

74
objekta. Na kraju, vraća referenca se na novokreirani agregat.
Generalno, implementacija fabrika je jednostavna, ali ipak, korisno je držati kôd za kon-
struisanje agregata odvojenim, kako bi se izbeglo dupliranje koda.

75
7 Sloj pristupa podacima - strana upita
Za razliku od komandne strane gde je potrebno voditi računa o transakcijama i izmenje-
nim objektima, ovde to nije potrebno. Samim tim, sloj pristupa podacima na strani upita
je jednostavniji. Takode, za razliku od komandne strane, gde je centralni objekat repozitori-
jum, ovde je centralni objekat hendler upita. Nije dozvoljena izmena podataka, već se samo
njihovo preuzimanje. Kako je ovaj sloj koncentrisan na preuzimanje i pretragu podataka
razumno je da je logika za pretragu objekata komplikovanija u odnosu na komandnu stranu.
Počinjemo opis definisanjem bazne klase hendlera upita.

7.1 Implementacija bazne klase hendlera upita


Iako postoji veliki broj različitih upita koje nije moguće staviti pod isti kalup, ipak,
postoji zajednički algoritam za sve hendlere upita, koji je potrebno izvršiti kako bi se dobili
rezultati upita.
Velika razlika u odnosu na komandnu stranu je ta što se na strani upita ne koriste objekti
jedinice rada, s obzirom na to da ne postoji logika za upis na izvor podataka. Objekti jedinice
rada su zaduženi da upravljaju konekcijom na komandnoj strani i potrebno je replicirati
sličnu logiku i na strani upita. Ta logika je smeštena u baznoj klasi hendlera. Kako je to
jedino bitno zaduženje smešteno u ovoj klasi i kako klasa nije velika, prikazuje se ceo sadržaj
klase.

Listing 63: Bazna klasa hendlera upita


public abstract class QueryHandlerBase<TQuery, TResult> : IQueryHandler<TQuery, TResult>
where TQuery : IQuery<TResult>
{
private readonly IConnectionPool _connectionPool;

protected QueryHandlerBase(IConnectionPool connectionPool)


{
_connectionPool = connectionPool;
}
public TResult Handle(TQuery query)
{
IDbConnection connection = null;
TResult result;
try
{
connection = _connectionPool.GetConnection();
_connectionPool.LockConnection(connection);
result = HandleQueryIntern(query, connection);
}
finally
{
if (connection != null) _connectionPool.UnlockConnection(connection);
}
return result;
}

protected abstract TResult HandleQueryIntern(TQuery query, IDbConnection connection);

76
Iz koda možemo da vidimo da je bazna klasa hendlera generička (gde su generički pa-
rametri tip upita koji se implementira i tip rezultata koji upit treba da vrati). Ovim ge-
neričkim parametrima implementira se i interfejs IQueryHandler. Metoda Handle generički
implementira algoritam obrade upita. U okviru metode rezerviše se konekcija prema izvoru
podataka, poziva se interna apstraktna metoda za obradu upita HandleQueryIntern i nakon
toga, oslobada se konekcija koja je upotrebljena za pristup podacima.

7.1.1 Implementacija hendlera GetFakturaByIdQueryHandler


Baznu klasu nasleduju sve ostale klase za obradu upita. Implementacija konkretnih klasa
hendlera obično je jednostavna osim u slučajevima gde je potrebno kreirati kompleksne
modele. Svaka klasa koja nasleduje baznu klasu sadrži konstruktor koji zahteva parametre
iste kao i bazna klasa (referenca na objekat tipa IConnectionPool ) i dodatne parametre
koji predstavljaju reference na TDG objekte, koji služe za preuzimanje podataka iz izvora
podataka. Sledi primer iz klase GetFakturaByIdQueryHandler.

Listing 64: Zaglavlje klase hendlera upita GetFakturaByIdQueryHandler


public GetFakturaByIdQueryHandler(IConnectionPool connectionPool,
ISelByIdGateway<FakturaDto> fakturaDtoGateway,
IMultiSelByIdGateway<FakturaStavka> fakturaStavkaGateway) : base(connectionPool)
{
_fakturaDtoGateway = fakturaDtoGateway;
_fakturaStavkaGateway = fakturaStavkaGateway;
}

U konstruktoru zahtevaju se reference na TDG objekte koji preuzimaju objekte tipa


FakturaDto i FakturaStavka iz izvora podataka. Metoda koristi ove objekte kako bi se preuzeli
podaci iz izvora podataka i kreirao rezultujući objekat. U ovoj metodi vrši se mapiranje
sirovih podataka u formu, koja je pogodna za upotrebu od strane klijenta.

Listing 65: Kreiranje rezultujućeg modela za upit


protected override FakturaEdit HandleQueryIntern(GetFakturaByIdQuery query,
IDbConnection connection)
{
FakturaDto dto = _fakturaDtoGateway.GetById(connection, query.FakturaId);

FakturaEdit res = new FakturaEdit();

res.Id = dto.FakturaId;
res.BrojFakture = dto.BrojFakture;
res.DatumFakture = dto.DatumFakture;
res.DatumValute = dto.DatumValute;
res.Iznos = new LNovac(dto.Iznos, dto.ValutaId, dto.ValutaOznaka);
{
Dobavljac d = new Dobavljac();
d.Id = dto.DobavljacId;
d.Naziv = dto.DobavljacNaziv;
res.Dobavljac = d;
}
{

77
Korisnik k = new Korisnik();
k.Id = dto.KorisnikId;
k.Ime = dto.KorisnikIme;
k.Prezime = dto.KorisnikPrezime;
res.Korisnik = k;
}
res.FakturaRabat = dto.FakturaRabat;
res.IsKalkulisana = dto.IsKalkulisana;

{
res.FakturaStavkaList = _fakturaStavkaGateway.GetById(connection, res.Id);
}

return res;
}

U prethodnoj metodi vidimo da se prvo preuzima referenca na privremeni objekat Fak-


turaDto koji se preuzima iz izvora podataka preko jedinstvenog identifikatora. Nakon toga,
kreira se objekat tipa FakturaEdit, čiji se podaci popunjuju iz privremenog DTO objekta.
Stavke fakture preuzimaju se uz pomoć TDG objekta koji vraća sve stavke fakture za dati
id fakture. Na kraju metoda vraća se novokreirani objekat FakturaEdit kao rezultat obrade.

7.1.2 Implementacija bazne TDG klase


Implementacija TDG objekata na strani upita slična je implementaciji na komandnoj
strani uz nekoliko razlika. Za razliku od komandne strane gde se jedan TDG objekat obično
mapira na tabelu, ovde se TDG objekti obično mapiraju na view objekat iz baze podataka.
Mapiranjem na view, smanjuje se broj poziva prema bazi podataka, čineći kreiranje objekta
modela efikasnijim.
Takode, dok je upisni sloj koncentrisan na upis i ažuriranje podataka i gde su zahtevi
za pretraživanje podataka obično jednostavni, strana upita zahteva kompleksniju logiku za
obradu različitih tipova upita nad izvorom podataka. TDG objekti ne sadrže metode za
izmenu podataka, ali zato sadrže različite metode za kreiranje upita nad izvorom podataka.
Najčešći slučajevi kreiranja upita nad izvorom podataka su apstraktovani, a kreirane su i
generičke metode za njihovu obradu.

Listing 66: Metode za preuzimanje podataka


public TResult GetById(IDbConnection connection, long id, ISelByIdQueryBuilder queryBuilder)
{
IDbCommand command = connection.CreateCommand();
queryBuilder.PrepareSelById(id, command);
return FetchOneObject(command, this);
}

public IList<TResult> GetMulById(IDbConnection connection, long id,


IMulSelByIdQueryBuilder queryBuilder)
{
IDbCommand command = connection.CreateCommand();
queryBuilder.PrepareMulSelById(id, command);
return FetchMulObjects(command, this);
}

78
public IList<TResult> GetAll(IDbConnection connection, ISelAllQueryBuilder queryBuilder)
{
IDbCommand command = connection.CreateCommand();
queryBuilder.PrepareSelAll(command);
return FetchMulObjects(command, this);
}

Ove metode nude funkcionalnost, koja je često potrebna u klasama. One nasleduju baznu
klasu. Zahteva se referenca na objekte koje implementiraju QueryBuilder interfejse. Oni
definišu metodu za kreiranje upita prema izvoru podataka.

7.1.3 Implementacija FakturaDtoGateway TDG klase


Implementacija TDG klase na strani čitanja, slična je implementaciji TDG klase ko-
mandne strane. Zbog različitih načina zadavanja upita, TDG objekti strane upita imple-
mentiraju različite interfejse, kako bi ovi objekti mogli da se koriste u generičkim klasama.

Listing 67: TDG klasa FakturaDtoGateway


public class FakturaDtoGateway : BaseTableGateway<FakturaDto>,
ISelByIdGateway<FakturaDto>, ISelByIdQueryBuilder
{
public override string TableName{...}

public FakturaDto GetById(IDbConnection connection, long id)


{
return base.GetById(connection, id, this);
}

public void PrepareSelById(long id, IDbCommand command) {...}

public override FakturaDto Retrieve(IDataReader reader) {...}

public static class FieldNames {...}


}

Kako vidimo iz priloženog koda, svaka TDG klasa nasleduje baznu klasu BaseTableGa-
teway i dodatne interfejse. U ovom slučaju, nasleduje se interfejs ISelByIdGateway, koji
naznačava da ova klasa omogućava preuzimanje jednog objekta FakturaDto iz izvora po-
dataka. Takode, implementiran je interfejs ISelByIdQueryBuilder koji omogućava lakšu
implementaciju metode GetById (ovaj interfejs zahteva implementaciju metode Retrieve).

79
8 Prezentacioni sloj – Model-View-ViewModel (MVVM)
patern
Kao što smo na početku rada napomenuli, prezentacioni sloj je implementiran upotre-
bom MVVM paterna. U daljem tekstu opisuje se sam patern, a zatim i način na koji je
implementiran u projektu.

8.1 Uvod
MVVM patern: pomaže čistom razdvajanju biznis logike od prezentacione logike kori-
sničkog interfejsa, olakšava dizajniranje aplikacije, čini aplikaciju jednostavnijom za testi-
ranje, održavanje i razvoj, promoviše ponovnu upotrebu koda i omogućava programerima i
dizajnerima lakšu saradnju tokom razvoja aplikacije.
Korisnički interfejs aplikacije i ležuća prezentaciona i biznis logika razdvojeni su u tri
različita sloja: view sadrži korisnički interfejs i logiku vezanu za način, na koji se podaci
predstavljaju korisniku, view model sadrži prezentacionu logiku i trenutno stanje korisnickog
interfejsa, i model sadrži aplikacionu biznis logiku i podatke.

8.2 Istorija
2005-te godine, John Gossman, jedan od arhitekata WPF i Silverlight platformi u Maj-
krosoftu predstavio je MVVM patern na svom blogu. MVVM je sličan Presentation Model
(PM) paternu u tome što oba paterna karakteriše apstrakcija prezentacije i ponašanja. Mar-
tin Fowler je uveo PM kao način za kreiranje apstrakcije prezentacije koja je nezavisna
od platforme, dok je Gossman uveo MVVM kao standardizovani način upotrebe karakteri-
stika WPF patforme, koje pojednostavljuju kreiranje korisničkog interfejsa. U tom smislu,
MVVM smatra se specijalizacijom opštijeg PM paterna dizajniranog po meri WPF i Sil-
verlight platforme. Ovaj patern se sada koristi i u drugim tehnologijama. U novije vreme
ovaj patern opisuje se kao MVB (Model-View-Binder) patern kada se implementira izvan
Microsoft zajednice.

8.3 Odgovornosti klasa i njihove karakteristike


MVVM patern je varijanta PM paterna, optimizovan da iskoristi mogućnosti WPF i Sil-
verlight platformi. To su: mehanizmi vezivanja podataka Data binding, definisanje grafičkih
šablona za podatke (Templates), komande (Commands) i ponašanja koja mogu da se pri-
dodaju na vizuelnu komponentu (Behaviours). View objekti komuniciraju sa view model
objektima kroz mehanizme vezivanja podataka, upotrebom komandi i kroz obaveštenja o
promeni podataka (Change notification events). View model objekti zaduženi su za preuzi-
manje podataka, nadgledanje i koordinisanje izmena podataka nad objektima modela. View
model objekti su, takode, zaduženi za konvertovanje i validaciju podataka sa korisničkog
interfejsa.

80
Dijagram 15 prikazuje MVVM klase i njihovu medusobnu saradnju:

Dijagram 15: Saradnja klasa u okviru MVVM paterna

Ključ za efektivnu upotrebu MVVM paterna leži u razumevanju načina na koji ove klase
medusobno saraduju u različitim scenarijima i rasporedivanju koda u odgovarajućim kla-
sama. U sledećem tekstu opisane su odgovornosti i karakteristike klasa MVVM paterna.

8.3.1 View
Odgovornost view objekata je definisanje strukture i izgleda svega što korisnik vidi
na ekranu. Idealno, kôd view objekta trebalo bi da sadrži samo konstruktor. U nekim
slučajevima, može da sadrži kôd koji sadrži logiku vezanu za način na koji se prikazuju po-
daci i vizuelno ponašanje koje je teško ili neefikasno izraziti na XAML jeziku (kompleksne
animacije ili direktan rad sa vizuelnim elementima koji su deo prikaza). Nije preporučljivo
stavljati bilo kakav kôd koji sadrži logiku (za koju je potrebno odraditi testiranje).
Podaci view objekata, vezuju se na podatke view model objekata na osnovu propertija
konteksta podataka (DataContext) koji je definisan za svaku view klasu. Za kontekst po-
dataka view objekta postavlja se odgovarajući view model objekat. View model objekti
pružaju propertije i komande koji se povezuju sa komponentama view objekata. Takode,
view model objekti obaveštavaju view objekte o promenama stanja kroz dogadaje promene
podataka (Data change events). Tipično, veza izmedu view i view modela objekata je jedan
prema jedan, tj. jedan view objekat drži referencu na samo jedan view model objekat.
Obično, view klase nasleduju neku WPF kontrolu, ali postoje i slučajevi gde se korisnički
interfejs generiše preko kontrola, koje predstavljaju šablone podataka. Šablone podataka
možemo posmatrati kao prezentacije bez prezentacione logike. Oni su dizajnirani da se
povežu direktno na specifičan view model. Šabloni podataka direktno se povezuju sa odgo-
varajućim tipom podataka view model klase. WPF automatski kreira view objekte za sve
objekte zadatog tipa view model klasa kadgod se view model objekat prikaže na korisnički
interfejs. Šablon podataka može biti definisan direktno u kodu kontrole koja ga koristi,
takode, može biti definisan i u rečniku resursa (resurce dictionary).

81
8.3.2 View model
View model objekti sadrže prezentacionu logiku i podatke koje je potrebno prikazati.
Ne sadrže direktnu referencu prema view objektima i nemaju znanje o tome kako je imple-
mentiran korisnički interfejs. View model objekti implementiraju propertije i komande na
koje se komponente view objekata vezuju i obaveštavaju view objekte o promenama stanja
generisanjem dogadaja. Definisanjem propertija i komandi precizira se funkcionalnost, ali
način prikaza funkcionalnosti odreduje se u view objektima.
Odgovornost view model objekata je koordinisanje izmedu view i model objekata. Tipično,
postoji veza jedan prema više izmedu objekata view model i model klasa. View model objekti
mogu direktno da izlože objekte modela na prikaz. U tom slučaju klase modela moraju biti
dizajnirane tako da podrže vezivanje podataka.
View model objekti konvertuju i manipulišu podacima modela tako da oni mogu biti
lako upotrebljeni u view objektima. Ovi objekti, takode, mogu definisati dodatne propertije
kako bi se dodatno olakšala implementacija view klasa. Ovi propertiji ne moraju biti deo
modela. Na primer, moguće je kombinovati vrednosti dva polja kako bi se olakšao prikaz na
korisničkom interfejsu, ili se može računati broj preostalih karaktera za unos u polju. Ovde
se, takode, implementira logika validacije podataka.
View model objekti, takode, mogu definisati logička stanja prikaza kako bi se omogućila
vizuelna promena korisničkog interfejsa. View objekti definišu raspored i stil komponenti
koje odražavaju trenutno stanje view model objekta. Na primer, view model objekat može
definisati status koji indicira da su podaci prosledeni na obradu i u tom slučaju view objekat
može prikazati animaciju kako bi dao vizuelnu indikaciju korisniku.
Tipično, view model objekti definišu komande ili akcije koje se prikazuju korisniku.
Uobičajen primer je komanda za snimanje podataka koja omogućava korisniku da prosledi
podatke izvoru podataka na snimanje. View objekat može da odluči da komandu prikaže
preko dugmeta, ili na neki drugi način. Komande pružaju način za obradu korisničkih akcija
i razdvajanje vizuelne prezentacije od prezentacione logike.

8.3.3 Model
Model sadrži biznis logiku i podatke. Biznis logika je definisana kao aplikaciona logika
koja je zadužena za preuzimanje i upravljanje aplikacionim podacima kako bi se osigurala
biznis pravila koja obezbedujuju konzistentnost i validnost podataka.
Tipično, model predstavlja klijentski biznis model aplikacije. Sadrži podatke, biznis i
validacionu logiku. Model može, takode, uključiti i kôd za pristup podacima i keširanje.
Često se model i servisni sloj definišu kao strategija pristupa podacima.
Model, takode, može podržavati validaciju podataka i izveštavanje o greškama implemen-
tiranjem odgovarajućih interfejsa. Ukoliko klase modela ne implementiraju odgovarajuće
interfejse, onda se klase modela obmotavaju view model klasom, koja onda pruža svu neop-
hodnu funkcionalnost view klasi.

82
8.4 Saradnja izmedu klasa
MVVM patern pruža čisto razdvajanje izmedu korisničkog interfejsa aplikacije, prezen-
tacione logike, biznis logike i podataka razdvajanjem svakog elementa u posebnu klasu.
Saradnja izmedu view i view model klasa je najbitnija za razmatranje, ali i saradnja
izmedu model i view model klasa je, takode, važna. Dalje opisujemo različite šablone ovih
interakcija i opisujemo kako implementirati MVVM patern u aplikaciji.

8.4.1 Povezivanje podataka (eng. Data binding)


Mogućnost vezivanja podataka igra veoma važnu ulogu u MVVM paternu. WPF pruža
bogate mogućnosti vezivanja podataka. View model klase treba da budu dizajnirane tako
da mogu da podrže povezivanje podataka kako bi se iskoristile prednosti MVVM paterna.
Obično, ovo znači da ove klase moraju da implementiraju odgovarajuće interfejse.
WPF podržava nekoliko modela vezivanja podataka. Sa jednosmernim povezivanjem,
korisničke kontrole mogu biti vezane za view model objekat tako da samo prikazuju vrednosti
podataka. Ukoliko se vrednost u view model objektu promeni, ta promena se reflektuje
na podatke koji se prikazuju na komponentu. Dvosmerno povezivanje, isto, automatski
ažurira komponentu na ekranu prilikom promene podatka u view model objektu, ali i ažurira
vrednosti podataka u view model objektu kada se njihova vrednost promeni na komponenti.
Kako bi osigurali da je korisnički interfejs uvek ažuran (kada se podatak promeni u
view model objektu), potrebno je implementirati interfejs koji ima funkciju obaveštavanja o
promeni podataka. Ako view model klasa definiše propertije, koji se vezuju na view kompo-
nente, onda view model klasa treba da implementira INotifyPropertyChanged interfejs. Ako
view model klasa predstavlja kolekciju, onda treba da implementira INotifyCollectionChan-
ged interfejs ili da nasledi klasu ObservableCollection koja implementira ovaj interfejs. Oba
interfejsa definišu dogadaj koji je potrebno izazvati prilikom promene podataka. Podaci koji
se nalaze na kontrolama automatski se ažuriraju kada se dogadaj promene podataka izazove.
U velikom broju slučajeva, view model klase definišu propertije koji predstavljaju objekte.
WPF podržava povezivanje podataka za ugnježdene propertije preko atributa Path. Zato je
čest slučaj da view model objekat vrati referencu na drugi view model ili model objekat. Sve
view model i model klase dostupne view klasama treba da implementiraju gore navedene
interfejse (u zavisnosti od situacije) kako bi povezivanje podataka uspešno radilo. U daljem
tekstu opisujemo kako implementirati INotifyPropertyChanged interfejs, dok je izostavljen
opis implementacije intefejsa INotifyCollectionChanged kako nigde nije implementiran u pro-
jektu, jer klasa ObservableCollection nudi svu potrebnu funkcionalnost.

8.4.2 Implementiranje interfejsa INotifyPropertyChanged


Implementiranjem ovog interfejsa u view model ili model klasi omogućuje se izazivanje
dogadaja promene nad podacima koje kontrole osluškuju i ažuriraju svoje vrednosti (ukoliko
je dogadaj izazvan). Implementacija ovog interfejsa je jednostavna i prikazana je u sledećem
primeru:

83
Listing 68: Sadržaj klase BasePropertyChanged
public abstract class BasePropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

protected void OnPropertyChanged(string propertyName)


{
VerifyPropertyName(propertyName);
var handler = PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}

[Conditional("Debug")]
public void VerifyPropertyName(string propertyName)
{
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
{
string msg = "Invalid property name: " + propertyName;
Debug.Fail(msg);
}
}

protected virtual bool SetProperty<T>(ref T member, T val, string propertyName)


{
if (Equals(member, val)) return false;
member = val;
OnPropertyChanged(propertyName);
return true;
}
}

Interfejs definiše properti PropertyChanged tipa event (dogadaj) koji je potrebno imple-
mentirati. Dogadaj se izaziva u metodu OnPropertyChanged gde prva linija proverava da
li dati properti stvarno postoji u klasi i izvršava se samo u debug modu. Ukoliko postoje
pretplaćeni delegati na dogadaj, on se izaziva.
Implementiranje ovog interfejsa u svakoj view model klasi posao je koji se ponavlja i može
biti sklon greškama. Zbog toga dobro je definisati baznu klasu koju nasleduju sve ostale view
model klase i koja implementira ovaj interfejs, kao što je i prikazano u prethodnom primeru.
Klasa koja nasleduje baznu klasu može podići dogadaj promene vrednosti propertija u
seteru pozivanjem metoda SetProperty. Ovaj metod proverava da li se zapravo postavlja
nova vrednost trenutnom polju. Ukoliko to jeste slučaj, vrednost polja se ažurira i izaziva
se dogadaj PropertyChanged.

8.4.3 Komande
Pored toga što view model klase pružaju pristup podacima view klasama za prikaz ili
izmenu, view model klase najverovatnije moraju imati definisanu jednu ili više akcija ili
operacija koje mogu biti inicirane od strane korisnika. U WPF okruženju akcije ili operacije,
koje korisnik može da izvrši kroz korisnički interfejs, tipično su definisane kao komande.

84
Komande pružaju zgodan način predstavljanja akcija ili operacija koje mogu biti lako vezane
za kontrole na prezentaciji. One sadrže stvarni kôd koji implementira akciju ili operaciju i
pomaže održavanju razdvajanja implementacije od vizuelne prezentacije.
Komande mogu biti vizuelno predstavljene i pozvane na različite načine od strane ko-
risnika. U najvećem broju slučajeva, one se pozivaju kao rezultat klika mišem, ali one ,
takode, mogu biti pozvane i kao rezultat neke prečice na tastaturi ili nečega drugog (npr:
završetak animacije). Kontrole na korisničkom interfejsu povezane su sa komandama u view
model objektima, tako da korisnik može da ih pozove izazivanjem bilo kog ulaznog dogadaja
koji kontrola definiše. Interakcija izmedu prezentacionih kontrola i komande može biti dvo-
smerna. Komanda može biti pozvana iz korisničkog interfejsa, a korisnički interfejs može
biti automatski ažuriran kao posledica izvršavanja komande.
View model klase mogu implementirati komande u obliku komandnog metoda ili komand-
nog objekata (objekat koji implementira ICommand interfejs). U svakom slučaju, saradnja
kontrole sa komandom može biti definisana deklerativno bez kompleksnog koda za obradu
dogadaja u view klasama. Na primer, odredene kontrole same po sebi podržavaju komande
i pružaju Command properti koji može biti povezan sa ICommand objektom u view model
klasi.
Komandni objekat je objekat koji implementira interfejs ICommand. Ovaj interfejs de-
finiše Execute metod, u kome se izvršava sama operacija. Takode, sadrži i metodu CanExe-
cute koja indikuje da li je moguće izvršiti komandu. Obe metode imaju jedan argument kao
parametar. Enkapsulacija implementacione logike za operaciju u komandnom objektu znači
da kôd može lakše da se održava i testira.
Implementirati ICommand interfejs je lako, medutim, već postoje implementacije ovog
interfejsa koje je moguće koristiti u aplikaicji. Na primer, moguće je koristiti DelegateCom-
mand klasu iz Prism biblioteke.

8.4.3.1 Klasa DelegateCommand


DelegateCommand klasa sadrži dva delegata koji referenciraju metode implementirane
u view model klasi. Nasleduje DelegateCommandBase klasu, koja implementira ICommand
interfejs pozivanjem delegata Execute i CanExecute. Delegati se zadaju u konstruktoru
prilikom kreiranja instance komandnog objekta, kao u sledećem primeru:

Listing 69: Kreiranje instance komande tipa DelegateCommand


var editStavkaCommand = new DelegateCommand(OnEditStavkaCommand, CanEditStavka);

Kada se pozove metod Execute u objektu DelegateCommand, objekat jednostavno prosleduje


poziv ka metodi u view model klasi preko delegata koji je zadat u konstruktoru. Slično, kada
se CanExecute metod pozove, poziv se prosleduje odgovarajućoj metodi u view model klasi.
Delegat CanExecute je opcionalan parametar u konstruktoru i ukoliko nije zadat onda je
komanda uvek dostupna za izvršavanje.
DelegateCommand klasa je generička klasa, argument tipa odreduje tip parametra ko-

85
mande u Execute i CanExecute metodama. Postoji i negenerička verzija DelegateCommand
klase u Prism biblioteci, koja može se koristiti kada nije potreban parametar, kao što je to
prikazano u prethodnom primeru.
View model klasa može nažnačiti promenu statusa dostupnosti komande pozivom metode
RaiseCanExecuteChanged nad objektom DelegateCommand. Ovo podiže dogadaj CanExe-
cuteChanged i sve kontrole na prikazu, koje su povezane sa ovom komandom, automatski
ažuriraju svoje stanje kako bi odrazile dostupnost povezane komande.

8.4.3.2 Povezivanje komandnih objekata sa vizuelnim komponentama


Postoji više načina na koje view kontrola može da se poveže sa komandnim objektom.
Odredene kontrole, posebno kontrole koje nasleduju klasu ButtonBase (Button, RadioBut-
ton, Hyperlink ), ili klase koje nasleduju klasu MenyItem, mogu jednostavno da se vežu za
komandni objekat kroz Command properti.

Listing 70: Povezivanje view kontrole na komandu


<Button Command="{Binding EditCommand}" Content="Izmeni Stavku" ... />

Komandni parametar je opcioni i može se proslediti upotrebom CommandParameter


propertija. Kontrola automatski poziva ciljanu komandu u trenutku kada korisnik izvrši
akciju nad njom. Komandni parametar, ukoliko je zadat, prosleduje se metodi Execute.

8.5 Validacija podataka i izveštavanje o greškama


View model klase i klase biznis modela, često, zahtevaju validaciju podataka i signali-
ziranje krajnjem korisniku o validacionim greškama, kako bi korisnik mogao da ih ispravi.
WPF pruža podršku za upravljanje validacionim greškama koje se dešavaju kada se menjaju
vrednosti projertija koji su vezani na view kontrole. Za propertije koji su vezani za kon-
trolu, view model ili model klasa može signalazirati validacionu grešku preko metode setera
vrednosti propertija odbijanjem da postavi lošu vrednost i izazivanjem izuzetka. Ukoliko je
ValidatesOnExceptions properti za povezivanje podataka postavljen na True, WPF obraduje
izuzetak i prikazuje vizuelni indikator korisniku da postoji greška u podacima.
Medutim, bacanje izuzetaka u seter propertija treba izbegavati gde god je to moguće.
Alternativni pristup je da view ili view model klase implementiraju interfejse IDataErrorInfo
ili INotifyDataErrorInfo. Ovi interfejsi dopuštaju view model klasama da izvrše validaciju
podataka za jednu ili više vrednosti propertija i vrate poruku greške view klasi tako da
korisnik može biti obavešten o grešci.

8.5.1 Implementiranje interfejsa IDataErrorInfo


IDataErrorInfo interfejs pruža osnovnu podršku za validaciju podataka i izveštavanje o
greškama. Definiše dva propertija čija vrednost može samo da se pročita. Jedan je indeksni

86
properti, koji ima naziv propertija koji se validira kao argument, a drugi properti je Error
koji dozvoljava view model objektu da pruži poruku greške za ceo objekat.
Indeksni properti dozvoljava view model/model klasama da vrate poruku o grešci za
odredeni properti. Prazan string ili null vrednost označava da je properti validan.
Indeksnom propertiju pristupa se kada se povezani properti prvi put prikaže, i kadgod
se nakon toga promeni. Zato što se indeksni properti poziva za sve vezane propertije koji
se menjaju, treba biti pažljiv prilikom implementiranja validacije kako bi se osiguralo da
validacija bude brza i efikasna.
Kada povezujemo kontrole u prikazu na propertije koje je potrebno validirati kroz ovaj
interfejs potrebno je postaviti vrednost propertija ValidatesOnDataErrors na True prilikom
povezivanja kontrole sa podacima. Ovo osigurava da WPF zahteva informaciju o grešci za
vezani properti, kao u sledećem primeru:

Listing 71: Povezivanje view kontrole sa view model propertijem


<DatePicker Name="DPic2"
SelectedDate="{Binding Path=VmFaktura.DatumValute,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged,
ValidatesOnDataErrors=True}"
Style="{StaticResource DateStyle}" />

U projektu, ovaj interfejs je implementiran u apstraktnoj klasi BaseEditEnityViewModel,


kako je potrebno odraditi još nekoliko akcija prilikom validacije.

Listing 72: Apstraktna implementacija metode za validaciju u klasi BaseEditEntityVie-


wModel
public string this[string columnName]
{
get
{
var res = ValidateField(columnName);
var eventArgs = new ValidationArgs(columnName, res);
OnValidatedObject(eventArgs);
return res;
}
}

Kao što vidimo iz implementacije, indekser metoda kao ulazni parametar prima naziv
kolone. Kako je BaseEditEnityViewModel apstraktna klasa, ona nije svesna svih polja koje
je potrebno validirati, zato poziva se apstraktna metoda ValidateField koju implementiraju
sve klase koje nasleduju ovu baznu klasu. Kada se validacija polja u metodi ValidateField
završi, poziva se metoda OnValidatedObject koja izaziva dogadaj ValidatedObject, kako bi se
obavestili svi zainteresovani objekti o promeni statusa validnosti polja.

87
Listing 73: Validacija polja u klasi VMFaktura
protected override string ValidateField(string fieldName)
{
if (string.Equals(fieldName, "BrojFakture") && !string.IsNullOrWhiteSpace(BrojFakture))
{
return BrojFakture.GetValidationError(BrojFakture);
}
if (string.Equals(fieldName, "FakturaRabat") && !string.IsNullOrWhiteSpace(FakturaRabat))
{
if (StrUtil.ToDouble(FakturaRabat) == null) return "Neispravan broj unet za rabat";
}
return null;
}

Sada je prikazan primer validacije polja iz klase VMFaktura. Potrebno je proveriti naziv
polja koje je poslato na validaciju (ovde treba biti pažljiv kako se ne bi pogrešio naziv polja).
Ukoliko se naziv poklapa sa trenutnim poljem, onda se vrši validacija. Grešku je moguće
direktno vratiti iz koda ili pozvati metodu neke druge klase koja vraća opis greške. Kao što
je ranije napomenuto prazan string ili null vrednost označavaju da greška nije pronadena.

8.6 Konstrukcija i povezivanje objekata


MVVM patern pomaže kod čistog razdvajanja prezentacije od prezentacione i biznis
logike. Smeštanje koda u prave klase je prvi važan korak kod efektivne upotrebe MVVM
paterna. Sledeći korak je razmotriti kako se view, view model i model klase implementiraju
i medusobno povezuju u vremenu izvršenja.
Tipično, veza izmedu view i view model klasa je jedan prema jedan. View i view model
klase povezuju se preko propertija DataContext koji se nalazi u view klasi; ovo omogućava
vizuelnim elementima da se vežu na propertije, komande i metode view model objekta.
Potrebno je odlučiti kako se instanciraju view i view model objekti i kako se medusobno
povezuju preko DataContext propertija u toku izvršenja.
Takode, potrebno je obratiti pažnju da prilikom konstruisanja i povezivanja view i view
model objekti ostanu medusobno slabo zavisni. Kao što je ranije napomenuto, view model
klase ne treba da zavise od view klasa. Slično tome, view klase ne moraju da zavise od
konkretnih implementacija view model klasa.
Postoji više načina na koji view i view model objekti mogu biti konstruisani i povezani u
vremenu izvršenja aplikacije. Najpogodniji pristup za aplikaciju najviše zavisi od toga da li
se prvo kreira view objekat ili view model objekat i da li se kreiranje objekata vrši programski
ili deklerativno.

8.6.1 Povezivanje objekata deklarativno


Možda je najjednostavniji pristup da view deklerativno instancira svoj odgovarajući view
model objekat kroz XAML kôd. Kada se prikaz konstruiše, instancira se odgovarajući view

88
model objekat. Ovaj pristup zahteva da view model klasa sadrži prazan konstruktor bez
parametara.
Deklarativna konstrukcija ima prednost da je jednostavna i da radi dobro u alatima za
dizajniranje korisničkog interfejsa. Mana ovog pristupa je da view klase imaju znanje o kon-
kretnom tipu view model klase i view model klasa mora da sadrži prazan konstruktor jer nije
moguće proslediti parametre view model objektima iz XAML koda. Ovaj način instanciranja
objekata upotrebljen je u projektu samo prilikom dizajniranja korisničkog interfejsa.

Listing 74: Postavljanje vrednosti DataContext propertija


<UserControl
...
d:DataContext="{d:DesignInstance fakturaEdit:VMFakturaNavEdt}"
...
>

8.6.2 Upotreba Šablona


View se može definisati kao šablon podataka kome je dodeljen tip view model klase.
Šabloni podataka mogu biti definisani kao resursi ili mogu biti definisani unutar kontrole
koja prikazuje view model objekat. WPF automatski instancira šablon podataka i podešava
njegov kontekst podataka na view model instancu. Ova tehnika je primer situacije gde se
view model prvo instancira.
Šabloni podataka su fleksibilni i ne tako zahtevni za resurse. Dizajner može da ih koristi
za lako vizuelno predstavljanje view model objekata, bez implementacije kompleksnog koda.

Listing 75: Implicitni šablon podataka za prikaz informacija o korisniku


<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock FontStretch="Expanded"
FontWeight="Bold"
Text="Ime: " />
<TextBlock Text="{Binding Path=Ime}" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock FontStretch="Expanded"
FontWeight="Bold"
Text="Prezime:" />
<TextBlock Text="{Binding Path=Prezime}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>

U prethodnom primeru vidimo implicitno definisan šablon podataka za kontrolu Com-


boBox. Ovde je definisan šablon za objekat Korisnik gde se svaki element u listi prikazuje
tako što se prvo prikazuje ime korisnika, a ispod imena se prikazuje prezime (sa labelama).

89
8.6.3 Povezivanje objekata programski
Drugi pristup je programski. Postoji više načina na koji view model objekat može da se
poveže sa view objektom.

• View model klasa može da ima kao ulazni parametar referencu na view objekat. Kako
nije dozvoljeno da view model objekat ima znanje o konkretnoj implementaciji view
objekta, prosleduje se interfejs koji view objekat implementira. View model objekat u
svom konstruktoru menja vrednost Data context propertija view objekta tako tako što
mu postavi vrednost da pokazuje na sebe.

• View klasa zahteva ili instancira view model objekat u svom konstruktoru i postavlja
vrednost DataContext propertija na taj objekat. Ovaj metod koristi se u projektu i
biće opisan detaljnije.

• Automatsko povezivanje upotrebom neke bibloteke. Postoje biblioteke koje automat-


ski instanciraju view objekte upotrebom različitih pravila/konfiguracije. Na primer,
postoje biblioteke koje instanciraju objekte bazirano na konvenciji imenovanja. Nazivi
svih view model objekata završavaju se sa “ViewModel”, dok se nazivi svih view obje-
kata završavaju sa “View”. Biblioteka automatski instancira view objekat i postavlja
vrednost DataContext properija view objekta da pokazuje na novokreirani view model
objekat.

8.6.3.1 Povezivanje objekata programski u konstruktoru view klase


Programska konstrukcija i dodeljivanje view model objekta u kodu view klase ima pred-
nost u jednostavnosti, a i dobro radi u alatima za dizajniranje korisničkog interfejsa. Nedo-
statak ovog pristupa je da view klasa mora da ima znanje o tipu view model klase i to što
zahteva pisanje programskog koda u view klasi. Sledi primer iz projekta:

Listing 76: Sadržaj klase WFakturaEdt


public partial class WFakturaEdt : UserControl
{
public WFakturaEdt(VMFakturaNavEdt viewModel)
{
InitializeComponent();
DataContext = viewModel;
}
}

Prethodni primer ilustruje implementaciju povezivanja view objekta WFakturaEdt i view


model objekta VMFakturaNavEdt. Kao što se vidi u primeru, u konstruktoru klase WFaktu-
raEdt zahteva se referenca na objekat VMFakturaNavEdt, inicijalizuje svoje komponente i,
zatim, postavlja vrednost svog DataContext propertija na vrednost reference objekta VM-
FakturaNavEdt. Interesantno je, takode, da je ovo jedini programski kôd u klasi WFakturaEdt
dok je ostatak klase ispisan u XAML kodu.

90
8.7 Implementacija MVVM klasa
View model objekti puni su prezentacione logike, isto tako, view objekti sadrže dosta
koda, koji se bavi interakcijom sa korisnikom, dok objekti modela uglavnom sadrže podatke
i pružaju podršku view objektima za lakši rad sa podacima.
View objekti su obični objekti koji nasleduju neku WPF kontrolu i implementiraju ko-
risničke kontrole pretežno u XAML kodu. Kako implementacija interakcije sa korisnikom
nije primarni fokus ovog rada i kako view klase ne sadrže nikakvu specijalnu logiku koja je
značajna za rad (jer sadrže uglavnom tehnički kôd), opis implementacije ovih klasa ćemo
izostaviti. Takode, veliki broj koncepata koji se implementiraju u view model klasama već je
opisan tako da se u ovom poglavlju izostavljaju detalji klasa koji su opisani u okviru drugih
poglavlja ili je njihova implementacija veoma jednostavna. Dijagram 16 prikazuje dijagram
baznih model i view model klasa, koje se nalaze u prezentacionom sloju.

Dijagram 16: Dijagram klasa baznih MVVM klasa

Na osnovu dijagrama možemo da vidimo strukturu nasledivanja model i view model


klasa. Struktura za model klase mnogo je jednostavnija, što zaključujemo na osnovu toga što
sadrži samo jednu klasu (BaseEditEntityModel ) koja nasleduje krovnu baznu klasu promene
propertija. View model stuktura sadrži nekoliko nivoa gde svaki nivo gde svaki nivo “ispod”
dodaje svoju logiku. U daljem tekstu opisije se svaka bazna klasa, pojedinačno, kao i primer
implementacije baznih klasa (kroz opis konkretne klase koja nasleduje neku od ovih baznih
klasa).

8.7.1 Klasa BasePropertyChanged


Ovo je veoma jednostavna klasa koja sadrži samo implementaciju interfejsa INotifyPro-
pertyChanged. Kako se notifikacije promena vrednosti koriste i u view model i u model
klasama, izdvojena je posebna klasa koja implementira logiku interfejsa. Sadržaj klase je
već prikazan i opisan u okviru odeljka koji se bavi implementiranjem interfejsa INotifyPro-
pertyChanged.

91
8.7.2 Klasa BaseViewModel
Sadržaj ove klase prazan je, jer trenutno ne dodaje nikakvu dodatnu funkcionalnost u
odnosu na klasu BasePropertyChanged koju nasleduje, ali sa druge strane, dosta view model
klasa nasleduje ovu klasu. To su klase za koje bazne klase “ispod” ne zadovoljavaju njihove
potrebe, ali bazna klasa za te objekte nije kreirana zbog previše specifične logike, koja se
upotrebljava samo u tim klasama.

8.7.3 Klasa BaseNavigableViewModel


Karakteristike ove klase su sledeće: sadrži baznu logiku (koja je potrebna da bi view
model mogao da učestvuje u navigaciji), kao i reference na menadžer regiona, implemen-
tira interfejs IConfirmNavigationRequest i pruža osnovnu implementaciju metoda interfejsa,
sadrži “Nazad” komandu za navigaciju nazad, (string koji predstavlja naslov view objekta)
i čuva parametre koji su mu prosledeni tokom navigacije u propertiju CallerParameters.

Listing 77: Sadržaj klase BaseNavigableViewModel


public abstract class BaseNavigableViewModel : BaseViewModel, IConfirmNavigationRequest
{
private IRegionNavigationService _navigationService;

protected BaseNavigableViewModel(string title, IRegionManager regionManager,


IServInjectCommand commandInjector) {...}

public DelegateCommand Back {...}


public UriQuery CallerParameters {...}
public IRegionManager Manager {...}
public string Title {...}
public IServInjectCommand CommandInjector {...}

public virtual void OnNavigatedTo(NavigationContext navigationContext) {...}


public virtual bool IsNavigationTarget(NavigationContext navigationContext) {...}
public virtual void OnNavigatedFrom(NavigationContext navigationContext) {...}
public virtual void ConfirmNavigationRequest(NavigationContext navigationContext,
Action<bool> continuationCallback) {...}

protected virtual bool CanGoBack()


{
if (_navigationService != null)
return _navigationService.Journal.CanGoBack;
return false;
}

private void GoBack()


{
if (_navigationService.Journal.CanGoBack)
_navigationService.Journal.GoBack();
}
}

Prethodni listing prikazuje sadržaj klase bez zadiranja u detalje svake pojedinačne metode
(navigacione metode kasnije se opisuju u okviru posebnog poglavlja koje se bavi navigaci-
jom). Detaljno su jedino prikazane metode koje implementiraju komandu Back, tako da iz

92
njih možemo da vidimo način implementiranja navigacije u nazad kroz istoriju. Implemen-
tacija ovih metoda postignuta je upotrebom navigacionog servisa koji je opisan u posebnom
poglavlju. Za sada, dovljno je reći da se ovaj servis koristi za navigaciju u aplikaciji.

8.7.4 Klasa BaseNavSearchViewModel


Kako je potrebno izlistati i pretražiti elemente, pre nego što se odabere konkretan objekat
sa kojim je potrebno raditi, i kako je ovo uobičajen slučaj u aplikaciji, implementirana je
bazna klasa koja podržava sve ove funkcionalnosti.
Klasa sadrži implementaciju najčešćih komandi, koje su potrebne u ovom scenariju, kao
što je to komanda za brisanje, izmenu, kopiranje u klipboard i filtriranje objekata. Sve ove
komande implementirane su uz upotrebu virtuelnih metoda, tako da je moguće prepraviti
kôd svake komande, u svakoj klasi naslednici.
Za implementaciju nekih od ovih komandi potreban je interakcioni servis. Tako kon-
struktor ove klase sadrži ulazni parametar, koji predstavlja referencu na ovaj servis.
Klasa sadrži sve rezultate upita, (npr. ukupan broj rezultata dobijen iz upita, referencu
na trenutno selektovane elemente itd.) i sve te objekte smešta u objekat tipa Presentati-
onCollection<T>. Kako bi mogli da se pribave rezultati upita, ova klasa sadrži referencu
na klasu CommandQueryService. Ona, pak, sadrži metode za izvršavanje komandi i upita
iz aplikacionog sloja. Naravno, kako sadrži sve ove reference, klasa implementira algoritam
učitavanja podataka u toku navigacije.

Listing 78: Sadržaj klase BaseNavSearchViewModel


public abstract class BaseNavSearchViewModel<TEntity, TFilterQuery> : BaseNavigableViewModel
where TFilterQuery : BaseFilterQuery<TEntity>
where TEntity : IdentityCarier
{
protected BaseNavSearchViewModel(IRegionManager regionManager,
IServInteraction interactServ, string formNavName,
string title, TFilterQuery filter, IServInjectCommand commandInjector,
CommandQueryService service)
: base(title, regionManager, commandInjector) {...}

public ICommand EditCommand {...}


public ICommand DeleteCommand {...}
public ICommand AddCommand {...}
public ICommand CurrSellAsCSVToClipBoard {...}
public abstract string EditFormPath { get; }
public PresentationCollection<TEntity> ItemsCollection {...}
public CommandQueryService Service {...}
public ICommand FilterCommand {...}
public uint TotalCount {...}
public uint FilteredCount {...}
public TFilterQuery Filter {...}
public IServInteraction InteractService {...}

private async void RefreshData() {...}


}

Prethodni listing prikazuje generalni sadržaj klase bez ulaska u implementacione detalje

93
metoda. Sledi opis implementacije komandi iz ove klase:

Listing 79: Sadržaj metoda OnEditCommand i OnDeleteCommand


protected virtual void OnEditCommand()
{
if (_itemsCollection.CurrentItem != null)
{
UriQuery query = new UriQuery();
query.Add(NavigationConstants.Key,
_itemsCollection.CurrentItem.Id.ToString());
Manager.RequestNavigate(ShellRegions.MainRegion,
new Uri(EditFormPath + query, UriKind.Relative));
}
else
{
InteractService.ShowNotificationInfo("Izmena objekta", "Nema selektovane stavke");
}
}

protected virtual void OnDeleteCommand()


{
if (_itemsCollection.CurrentItem != null)
{
var conf = new ConfirmationMessage();

conf.Title = "Potvrda za brisanje";


conf.Message = "Da li ste sigurni da zelite da obrisete objekat sa Kljucem:" +
_itemsCollection.CurrentItem.Id;

InteractService.ShowConfirmation(conf, async message =>


{
if (!message.IsConfirmed) return;
var res = await DeleteItem(_itemsCollection.CurrentItem);
if (res) RefreshData();
});
}
else
{
InteractService.ShowNotificationInfo("Brisanje Stavke", "Nema selektovane stavke");
}
}

Prilikom izvršavanja OnEditCommand metode prvo se proverava da li postoji selektovan


element koji je potrebno izmeniti. Ukoliko nije selektovan nijedan element, onda se kori-
sniku prikazuje poruka o grešci. U slučaju da je korisnik selektovao stavku, onda se kreira
navigacioni upit (promenljiva query) u koji se dodaje ID trenutno selektovane stavke i vrši
se navigacija na view za izmenu podataka.
Implementacija komande AddCommand je prosta, jer samo poziva menadžer regiona
za navigaciju na view za izmenu podataka. To je i razlog što nije prikazana. Komanda
FilterCommand jednostavno poziva metodu RefreshData, koja je opisana u daljem tekstu.
Komanda CurrSellAsCsvToClipBoard uzima podatke iz trenutno selektovanih objekata i
kreira CSV koji onda kopira u clipboard.
Prilikom brisanja elementa, proverava se, opet da li postoji selektovan element. Ukoliko
se dobije potvrda da on postoji, kreira se zahtev za interakciju za korisnikom, kako bi on, još

94
jednom, potvrdio svoju nameru. Kada se to dogodi, poziva se apstraktna metoda za brisanje
elementa i osvežava se prikaz.

Listing 80: Metode za učitavanje podataka na korisnički interfejs


protected async Task<BaseFilterResult<TEntity>> GetFilterResult(TFilterQuery filter)
{
return await Service.ProcessQueryAsync<TFilterQuery, BaseFilterResult<TEntity>>(filter);
}

private async void RefreshData()


{
BaseFilterResult<TEntity> result = null;
result = await GetFilterResult(_filter);
if (result == null) return;
ItemsCollection.Items = new ObservableCollection<TEntity>(result.Results);
TotalCount = result.TotalCount;
FilteredCount = (uint) result.Results.Count;
}

Takode, bitno je opisati način na koji se učitavaju podaci. U tu svrhu koriste se prethodno
prikazane metode. Prvo je prikazan sadržaj metode GetFilterResult koja jedino služi za lakši
poziv servisa koji procesira upite. Metoda RefreshData upravlja učitavanjem i prikazivanjem
podataka na korisnički interfejs. Najpre, poziva se servis za preuzimanje podataka kome se
prosleduje upit/filter. On sadrži uslove za filtriranje. Nakon uspešnog preuzimanja podataka
popunjava se lokalna kolekcija iz rezultata i lokalni propertiji koji prikazuju ukupan broj
elemenata i broj vraćenih elemenata.

8.7.5 Klasa BaseNavEditViewModel


Jedna od osnovnih funkcionalnosti aplikacije je mogućnost izmene podataka odredenih
objekata. Kako dosta view model klasa implementira ovu funkcionalnost, kreirana je bazna
klasa koja implementira zajedničku funkcionalnost.
Klasa sadrži komande koje se često koriste prilikom izmene objekta. To su komande
za snimanje i otkazivanje promena. Sadrži referencu na interakcioni servis za interakciju
sa korisnikom i sadrži referencu na CommandQueryService servis za izvršavanje komandi i
upita iz aplikacionog sloja.
Klasa apstraktno implementira interfejs IDataErrorInfo, gde se koristi objekat tipa Er-
rorTracker koji beleži sve poruke o validacionim greškama.
Definisano je polje koje označava trenutno stanje objekta, u smislu da li je objekat izme-
njen. Njegova vrednost se automatski ažurira nakon što se objekat izmeni ili kada se objekat
učita ili snimi upotrebom servisa. Sledi listing i opis najbitnijih detalja iz klase:

Listing 81: Najbitniji detalji klase BaseNavEditViewModel


public abstract class BaseNavEditViewModel<TDataModel> : BaseNavigableViewModel, IDataErrorInfo
where TDataModel : IdentityCarier
{
private readonly BaseEditEnityModel<TDataModel> _editInstance;
...

95
protected BaseNavEditViewModel(IRegionManager regionManager, IServInteraction interactService,
string title, IServInjectCommand commandInject, CommandQueryService cqSrvice,
BaseEditEnityModel<TDataModel> editInstance)
: base(title, regionManager, commandInject)
{
...
_editInstance = editInstance;
// track validation for edit object
editInstance.ValidatedObject +=
(sender, args) => { ErrorTracker.UpdateErrorStatus(args.ErrorText, args.ColumnName); };
editInstance.PropertyChanged += (sender, args) => { SaveChanges.RaiseCanExecuteChanged(); };
VMEditState = EditState.Unchanged;
}
}

protected async void ReloadEditObject(string id)


{
if (string.IsNullOrEmpty(key))
{
_editInstance.PopulateForNewEntity();
OnNewEntityFormPopulated(EditInstance);
OnPropertyChanged(String.Empty);
}
else
{
var entityId = long.Parse(key);
TDataModel data = null;

data = await LoadEntityData(entityId);

if (data != null)
{
_editInstance.PopulateForEditEntity(data);
}
_errorTracker.Clear();
_saveChanges.RaiseCanExecuteChanged();
OnEditEntityFormPopulated(EditInstance);
OnPropertyChanged(string.Empty);
}
VMEditState = EditState.Unchanged;
}
protected override bool SetProperty<T>(ref T member, T val, string propertyName)
{
if (!Equals(member, val))
VMEditState = EditState.Modified;

return base.SetProperty(ref member, val, propertyName);


}

Iz prethodnog koda, vidi se da view model objekti izmene podataka sadrže referencu
na objekte modela u prezentacionom sloju, koji, pak, obmotavaju model objekte, dobijenih
kao rezultat upita iz aplikacionog sloja. Prilikom konstrukcije klase, dodaje se metoda koja
prati dogadaj promene podataka, kako bi se kontrolisala mogućnost izvršavanja komande za
snimanje podataka i metoda koja osluškuje validacione greške iz modela, kako bi se korisniku
prikazale validacine poruke.
Metoda za učitavanje objekta, čije detalje je potrebno izmeniti ReloadEditObject koristi
se svuda u okviru klase. Metoda ima parametar koji predstavlja ID objekta, koji je potrebno
učitati i koji je zadat kao string, jer se u navigaciji prosleduju samo stringovi. Parametar ID

96
nije obavezan i, ukoliko nije zadat, u formi se popunjuju podrazumevane vrednosti za krei-
ranje novog objekta. To se postiže pozivom metode PopulateForNewEntity objekta modela
i generisanjem dogadaja OnNewEntityFormPopulated. U slučaju da je zadata ID vrednost,
poziva se apstraktna metoda za učitavanje podataka sa trenutnim ID-em kao parametrom,
popunjava se forma za izmenu i izaziva se dogadaj OnEditEntityFormPopulated. Na kraju,
pozivaju se metode za resetovanje svog stanja (kako bi se resetovala forma).
U ovoj klasi je nadogradena implementacija bazne metode SetProperty, gde je dodata
logika praćenja trenutnog stanja izmene objekta.

8.7.6 Klasa BaseEditEntityModel


Ovo je bazna klasa za sve model klase, koje omogućuju izmenu podataka. Podaci, koji
se preuzmu kao rezultat upita, su obični DTO objekti i namenjeni su samo za čitanje.
Čak, i kada bi se omogućila izmena tih podataka, te klase ne bi implementirale interfejse iz
prezentacionog sloja (koji omogućavaju efikasnu upotrebu MVVM paterna i prednosti WPF
tehnologije).
Može se reći da je ova klasa veoma slična prethodno opisanoj klasi BaseNavEditViewMo-
del i da nema svrhe dodavati još jednu klasu, koja omogućuje izmenu podataka. Medutim,
klasa BaseNavEditViewModel se više bavi koordinacijom procesa prilikom izmene podataka,
dok se ova klasa bavi samim podacima.
Takode, u ovoj klasi, apstraktno se implementira interfejs IDataErrorInfo, sadrži refe-
rencu na ID objekta, nad kojim se vrše izmene i, čuva trenutno stanje izmene.
Klasa implementira mapiranje podataka iz model objekta (dobijenog iz aplikacionog sloja
kao rezultat upita) i sadrži apstraktno implementirane metode PopulateForNewEntity (gde
se inicijalizuje stanje za kreiranje nove instance objekta) i PopulateForEditEntity (sa para-
metrom koji predstavlja objekat za izmenu). Prikazuju se samo najbitniji delovi klase:

Listing 82: Najbitniji delovi klase BaseEditEntityModel


public enum EditState
{
New,
Unchanged,
Modified
}

public abstract class BaseEditEnityModel<T> : BasePropertyChanged,


IDataErrorInfo where T : IdentityCarier
{

private string _entityId;

public void PopulateForNewEntity()


{
PopulateNewEntityForm();
EntityId = null;
CurrEditState = EditState.New;
}

protected abstract void PopulateNewEntityForm();

97
public void PopulateForEditEntity(T entity)
{
PopulateEditEntityForm(entity);
EntityId = entity.Id.ToString();
CurrEditState = EditState.Unchanged;
}

protected abstract void PopulateEditEntityForm(T entity);

public string this[string columnName]


{
get
{
var res = ValidateField(columnName);
var eventArgs = new ValidationArgs(columnName, res);
OnValidatedObject(eventArgs);
return res;
}
}

Postoji referenca na trenutni ID objekta, čiji detalji se menjaju i smeštaju u properti


EntityId. Implementacija metoda PopulateForNewEntity i PopulateForEditEntity je veoma
jednostavna, budući da one samo pozivaju svoje apstrakne implementacije, postavljaju vred-
nost propertija EntityId i podešavaju trenutno stanje izmene. Validaciona procedura, takode,
poziva svoju apstraktnu verziju i nakon toga izaziva dogadaj validacije podataka.

8.7.7 Klasa VMProizvodSel


Generalno, implementacija svih klasa u projektu, koje nasleduju klasu BaseNavSearc-
hViewModel veoma je jednostavna. U konstruktoru klase zadaje se naziv forme i kreira se
instanca objekta upita, koji se koristi za filtriranje podataka, pored ostalih parametara, koji
se prosleduju baznoj klasi. Zadaje se putanja do forme za izmenu i pruža se implementacija
metode za brisanje jednog elementa.

Listing 83: Sadržaj klase VMProizvodSel


public class VMProizvodSel : BaseNavSearchViewModel<ProizvodFilterResultItem, ProizvodFilterQuery>
{
public VMProizvodSel(IRegionManager regionManager, IServInteraction confServ,
IServInjectCommand cmdInj, CommandQueryService service)
: base(
regionManager, confServ, "Prikaz i Selekcija Proizvoda",
new ProizvodFilterQuery(),
cmdInj, service
)
{
}

public override string EditFormPath


{
get { return ProizvodViews.WProizvodEdt; }
}

protected override async Task<bool> DeleteItem(ProizvodFilterResultItem currentItem)


{
var command = new BrisanjeProizvodaCommand();

98
command.ProizvodId = currentItem.Id;
return await Service.ProcessCommandAsync(command);
}
}

Prethodni listing prikazuje ceo sadržaj klase VMProizvodSel. Jedina zanimljiva metoda
je DeleteItem koja kreira komandu za brisanje proizvoda i poziva servis za izvršavanje te
komande.

8.7.8 Klasa VMProizvodNavEdt


Ova klasa nasleduje apstraktnu klasu BaseNavEditViewModel i implementira svu logiku
potrebnu za izmenu i kreiranje novog proizvoda. Klasa je puna prezentacione logike koju je
teško i nepraktično opisati. Opisuju se samo najbitnije metode.

Listing 84: Najbitniji detajli klase VMProizvodNavEdt


public VMProizvodNavEdt(IRegionManager regionManager, IServInteraction interactService
, IServInjectCommand cmdInj,
CommandQueryService cqService)
: base(regionManager, interactService, "Izmena Podataka O Proizvodu", cmdInj,
cqService, new VMProizvod())
{
...
LoadLookupValues();
}

private void LoadLookupValues()


{
_allJedinicaMere.AddRange(
CqSrvice.ProcessQuery<GetAllJedinicaMereQuery, IList<JedinicaMere>>(
new GetAllJedinicaMereQuery()));
...
}
protected override async Task<ProizvodEdit>LoadEntityData(long entityId)
{
var query = new GetProizvodByIdQuery(entityId);
return await GetQueryResult(query);
}

protected override async Task<bool> ProcessCreateObject()


{
var noviProizCmd = new NoviProizvodCommand();
noviProizCmd.Naziv = VmProizvod.Naziv;
noviProizCmd.BarKod = VmProizvod.BarKod;
...
var res = await CqSrvice.ProcessCommandAsync(noviProizCmd);
if (res)
{
VmProizvod.EntityId = noviProizCmd.EntityId.ToString();
return true;
}
return false;
}

Prilikom kreiranja nove instance učitavaju se sve lookup vrednosti pozivanjem servisa za

99
procesiranje upita, (kao što je to prikazano u metodi LoadLookupValues). Implementacija
metode za učitavanje objekta za izmenu veoma je jednostavna, kako se samo kreira upit,
poziva servis za obradu upita i vraća rezultat pozivaocu. Metoda ProcessCreateObject ima
sličnu logiku, s tim što se u ovom slučaju kreira komanda koja se šalje na procesiranje. Kako
je ovo komanda za kreiranje novog objekta, nakon procesiranja komande preuzima se ID
novokreirane stavke.

8.7.9 Klasa MProizvod


Klasa MProizvod nasleduje baznu klasu BaseEditEnityModel i predstavlja omotač klase
ProizvodEdit koja se nalazi u servisnom sloju upita.

Listing 85: Najbitniji detalji klase MProizvod


public class MProizvod :
BaseEditEnityModel<AppServ.Query.ProizvodQueries.ProizvodEditing.Model.ProizvodEdit>
{

protected override string ValidateField(string fieldName)


{
if (string.Equals(fieldName, PropNames.Naziv) && !string.IsNullOrWhiteSpace(Naziv))
return new ProizvodNaziv(Naziv).GetErrorMessage(true, "Naziv Proizvoda");
...
return null;
}

protected override void PopulateNewEntityForm()


{
_barKod = null;
_grupaList.Clear();
_cena = 0;
...
}
protected override void PopulateEditEntityForm(
AppServ.Query.ProizvodQueries.ProizvodEditing.Model.ProizvodEdit entity)
{
_barKod = entity.BarKod;
GrupaList.Clear();
GrupaList.AddRange(entity.GrupaList);
...
}
public string BarKod
{
get { return _barKod; }
set { SetProperty(ref _barKod, value, PropNames.BarKod); }
}
public double UkupnaVrednost
{
get { return Cena*StanjeKolicina; }
}
...
}

U prethodnom listingu možemo da vidimo implementaciju metode za validaciju koja je


implementirana upotrebom vrednosnog objekta iz sloja domena. Takode, prikazan je deo im-
plementacije metode PopulateNewEntityForm, gde se postavljaju podrazumevane vrednosti

100
za propertije. Implementacija metode PopulateEditEntityForm sastoji se u kopiranju vred-
nosti iz ulaznog objekta. Takode, ovde je prikazan primer implementacije propertija, gde se
u seteru poziva metoda bazne klase SetProperty, kako bi se omogućila obaveštenja o promeni
podataka view objektu. Na kraju, prikazan je veštački dodat properti UkupnaVrednost koji
se ne snima i učitava iz aplikacionog sloja, ali pruža korisnu informaciju korisniku.

8.7.10 Klasa CommandQueryService


Ova klasa služi kao centralna pristupna tačka aplikacionom sloju za izvršavanje komandi
i upita. Sve ostale view model klase izvršavaju svoje komande/upite pozivom metoda ove
klase. Zadatak ove klase je da pozove dispečer za obradu upita/komande, vrati rezultat u
slučaju uspešnog izvršavanja i obradi izuzetke iz aplikacionog sloja u slučaju greške.

Listing 86: Najbitniji detalji klase CommandQueryService


public class CommandQueryService
{
private readonly IUnityContainer _container;
private readonly IServInteraction _interactServ;

public CommandQueryService(IUnityContainer container, IServInteraction interactServ)


{
_container = container;
_interactServ = interactServ;
}

public TResult ProcessQuery<TQuery, TResult>(TQuery query)


where TQuery : IQuery<TResult> {...}

public async Task<TResult> ProcessQueryAsync<TQuery, TResult>(TQuery query)


where TQuery : IQuery<TResult> {...}

public void ProcessCommand<TCommand>(TCommand command)


where TCommand : ICommand {...}

public async Task<bool> ProcessCommandAsync<TCommand>(TCommand command)


where TCommand : ICommand
{
try
{
BusyStateManager.IsBusy = true;
await TaskEx.Run(() =>
{
var commandDispatcher = _container.Resolve<ICommandDispatcher>();
commandDispatcher.Execute(command);
});
BusyStateManager.IsBusy = false;
return true;
}
catch (CommandProcessingException e)
{
BusyStateManager.IsBusy = false;
var message = new DialogMessage();
message.Title = "Greska prilikom procesiranja komande: " + command.GetType().Name;
message.Message = e.Message;
_interactServ.ShowWarningDialog(message);
return false;
}

101
catch (Exception e)
{
BusyStateManager.IsBusy = false;
var message = new DialogMessage();
message.Title = "Greska prilikom procesiranja komande: " + command.GetType().Name;
message.Message = e.Message;
message.DetailMessage = e.ToString();
_interactServ.ShowErrorDialog(message);
return false;
}
}
}

Ova klasa sadrži reference na Unity kontejner i interakcioni servis koje se zahtevaju u kon-
struktoru klase. Sadrži četiri različite metode za procesiranje upita/komandi. Jedna grupa
metoda je za procesiranje komandi, a druga grupa metoda je za procesiranje upita. Takode,
svaka grupa metoda sadrži sinhronu i asinhronu verziju. Kako je implementacija svih ovih
metoda veoma slična, prikazuje se samo implementacija metode ProcessCommandAsync.
Metoda ProcessCommandAsync služi za asinhrono procesiranje jedne komande. To je
generička metoda koja sadrži generički parametar TCommand. On označava tip komande
koji se izvršava. Naravno ta klasa mora da nasleduje osnovni interfejs za komande ICom-
mand. Komanda za izvršavanje se prosleduje ovom metodu i onda se kreće sa procesiranjem.
Prvi korak je vizuelno predstaviti korisniku da je u toku izvršavanje komande i to se postiže
postavljanjem vrednosti ISBusy na true klase BusyStateManager, koja dalje izveštava view
objekat da promeni prikaz i odgovarajućom animacijom ukaže na izvršavanje komande. Onda
se razrešava referenca na dispečer komandi i poziva se izvršavanje komande kroz dispečer.
Ukoliko je izvršavanje komande uspešno, obaveštava se view da je završeno procesiranje ko-
mande i završava se procesiranje sa povratnom informacijom o uspešnom procesiranju. Uko-
liko dode do greške prilikom procesiranja, onda se opet obaveštava view objekat o završetku
procesiranja komande ali, isto se tako korisniku prikazuje poruka o grešci. Postoje dve va-
rijante poruke koje se prikazuju: prva je očekivana i prikazuje se samo poruka iz izuzetka,
dok je druga neočekivana i prikazuju se svi detalji greške korisniku.

102
9 Podela aplikacije u module
Niko više ne piše sam celu aplikaciju. Izvan sveta integrisanih sistema, skoro svi se
oslanjaju na biblioteke i radne okvire koje je napisao neko drugi. Njihovom upotrebom,
moguće je koncentrisati se na stvarnu logiku aplikacije dok se infrastruktura, biblioteke i
radni okviri ugraduju u aplikaciju i njih obično je razvio neko drugi. Radeći na taj način
smanjuje se potrebno vreme za razvoj aplikacije.
Uspeh razvoja softvera otvorenog koda poslednjih decenija čini koncept ponovne upotrebe
biblioteka mnogo ubedljivijim. Za veliki broj problema postoje rešenja i ona su dostupna za
upotrebu bez ikakve nadoknade. Pisanje modernih aplikacija je, umnogome, proces sklapanja
koliko je proces kreiranja. Odabir dostupnih delova i njihovo spajanje predstavlja veliki deo
modernog razvoja aplikacije. Umesto pisanja svega od početka, oni kojima je potreban
HTTP server koriste npr. Apache ili Tomcat server, a drugi kojima je potreban sistem
za rad sa bazom podataka, mogu da odaberu MySQL, PostgreSQL ili neki treći softver za
upravljanje bazom podataka. U aplikaciji programer spaja različite delove i dodaje logiku
aplikacije. Rezultat je potpuno funkcionalna aplikacija razvijena u neverovatno kratkom
roku.
Razmotrimo kako Linux distribucije rade. Fedora, Mandriva, SUSE i Debian sadrže veliki
broj istih aplikacija koje su napisane od istih ljudi. Distributor ih jednostavno upakuje i spoji
kako bi se instalirale zajedno. Izdavači distribucije, često, pišu samo centralni upravljački
softver, instalacioni softver i pružaju garanciju kvaliteta kako bi osigurali da sve odabrane
komponente rade zajedno.
Još jedna stvar koju je potrebno shvatiti je da niko više kompletno ne kontroliše orga-
nizaciju celog proizvoda. Ne samo izvornog koda, već i programera, s obzirom na to da su
programeri rasprostranjeni po celom svetu i rade po svom rasporedu. Ova situacija uopšte
nije retka i opasna kao što zvuči. Svako ko je pokušao da organizuje projekat sa timom većim
od pedeset ljudi zna da je ideja imati potpunu kontrolu procesa, u najboljem slučaju, utešna
iluzija.
Mogućnost upotrebe eksternih biblioteka i njihovo uklapanje u aplikaciju daje mogućnost
kreiranja kompleksnog softvera sa manjim utroškom vremena i manje posla. Naravno, po-
trebno je upravljati tim bibliotekama i osigurati njihovu kompatibilnost. To nije jednostavan
zadatak, ali ne postoji drugi praktičan i isplatljiv način za izgradnju sistema današnje kom-
pleksnosti.

9.1 Modularne aplikacije


Modularna aplikacija je aplikacija koja je podeljena u skup funkcionalnih jedinica, koje
nazivamo modulima. Modul sadrži deo funkcionalnosti aplikacije i, tipično, predstavlja skup
povezanih zadataka. Modul može uključiti kolekciju povezanih komponenti, uključujuci ko-
risnički interfejs i biznis logiku ili delove aplikacione infrastrukture, kao što su servisi apli-
kacionog nivoa za logovanje ili za autentikaciju korisnika. Moduli su medusobno nezavisni,
ali mogu da saraduju na slabo povezani način. Modularne aplikacije mogu olakšati razvoj,

103
testiranje, puštanje u produkciju i nadogradivanje aplikacije.
Na primer, razmotrimo bankovnu aplikaciju. Korisnik može pristupiti različitim funkcio-
nalnostima, kao što je transfer novca, plaćanje računa, ažuriranje ličnih informacija i sve to
iz jednog istog korisničkog interfejsa. Medutim, iza scene, svaka od tih funkcija se nalazi u
različitom modulu. Moduli komuniciraju medusobno i sa različitim sistemima kao što su to
baza podataka ili web servisi. Aplikacioni servisi integrišu različite komponente sa svakim
od modula i upravljaju komunikacijom sa korisnikom. Korisnik vidi integrisani prikaz koji
izgleda kao jedna aplikacija.
Modularni pristup pomaže pri identifikaciji velikih funkcionalnih oblasti aplikacije i dopušta
razvoj i testiranje funkcionalnosti, nezavisno. Ovo može učiniti razvoj i testiranje jedno-
stavnijim, a može učiniti aplikaciju fleksibilnijom i lakšom za nadogradivanje u budućnosti.
Benefit modularnog pristupa je da može učiniti celokupnu arhitekturu aplikacije fleksibilni-
jom i jednostavnijom za održavanje, zbog toga što omogućava podelu aplikacije na održive
delove. Svaki deo sadrži specifičnu funkcionalnost i svaki deo je integrisan kroz čiste, ali
slabo zavisne komunikacione kanale.
Prilikom razvoja aplikacije na modularan način, aplikacija se struktuira u odvojene mo-
dule, koji mogu biti nezavisno razvijeni. Svaki modul sadrži deo funkcionalnosti aplikacije.
Jedna od prvih dizajnerskih odluka, koje je potrebno doneti jeste kako odlučiti kako podeliti
aplikaciju na module. Modul bi trebalo da sadrži skup povezanih odgovornosti. Modul može
predstavljati vertikalni deo aplikacije ili horizontalni servisni sloj. Velike aplikacije obično
imaju oba tipa modula.

Dijagram 17: Podela aplikacije u module

104
Modul bi trebalo da ima minimalni skup zavisnosti na druge module. Kada modul ima
zavisnost na drugi modul, treba da bude povezan upotrebom interfejsa definisanih u deljivoj
biblioteci ili upotrebom dogadaja.
Cilj modularnosti je podela aplikacije na takav način da ona bude fleksibilna, laka za
održavanje, stabilna čak i prilikom učitavanja i brisanja modula iz memorije ili u toku
izvršavanja aplikacije. Najbolji način za ostvarivanje ove funkcionalnosti je dizajnirati apli-
kaciju tako da moduli budu slabo zavisni.
Postoji nekoliko načina za kreiranje i pakovanje modula. Preporučljiv način je kreiranje
jednog projekta po modulu. Kako jedan modul predstavlja jedan projekat, olakšava se nje-
gova integracija i puštanje u produkciju. Medutim, ne postoji ograničenje da jedan projekat
mora da sadrži samo jedan modul. U nekim slučajevima ovo može biti poželjno kako bi se
minimizovao broj projekata u soluciji. Za velike aplikacije, nije neuobičajeno imati 10 do 50
modula. Razdvajanje svakog modula u poseban projekat dodaje dosta kompleksnosti i može
usporiti performanse okruženja za razvoj aplikacije.

9.2 Razvoj modularne aplikacije upotrebom Prism biblioteke


Prism biblioteka daje podršku za razvoj modularnih aplikacija i upravljanje modulima
u toku samog izvršenja aplikacije. Upotrebom funkcionalnosti Prism biblioteke smanjuje
se vreme razvoja modularnih aplikacija, budući da nije potrebno implementirati i testirati
sopstvene biblioteke koje bi obavljale tu funkcionalnost. Prism sadrži sledeće funkcionalnosti
za razvoj modularnih aplikacija:

• Katalog modula za registraciju imenovanih modula.

• Podrška upotrebe meta podataka za definisanje modula, inicijalizaciju modula i konfi-


gurisanje njihovih zavisnosti.

• Integracija sa kontejnerima za ubrizgavanje zavisnosti, kako bi se podržala slaba zavi-


snost izmedu modula.

• Prilikom učitavanja modula Prism biblioteka podržava:

– Upravljanje zavisnostima, uključujući detekciju duplikata i ciklusa kako bi se osi-


guralo da su moduli samo jednom učitani i inicijalizovani u odgovarajućem redo-
sledu.
– Dovlačenje modula na zahtev u pozadini kako bi se minimizovalo vreme pokretanja
aplikacije. Ostatak modula može biti učitan i inicijalizovan u pozadini ili kada
moduli budu potrebni na zahtev.

9.2.1 Osnovni gradivni elemenat modularnih aplikacija - IModule interfejs


Modul predstavlja logučku kolekciju funkcionalnosti i resursa, koji su upakovani u jednu
logičku celinu na način koji dozvoljava nezavisan razvoj, testiranje i integraciju modula u

105
aplikaciji. Svaki modul sadrži centralnu klasu, koja je odgovorna za inicijalizaciju modula i
integraciju svojih funkcionalnosti u aplikaciju. Ta klasa mora da implementira IModule inter-
fejs. Postojanje klase koja implementira interfejs IModule dovoljno je da se celina identifikuje
kao modul. Ovaj interfejs sadrži samo jedan metod Initialize u okviru koga se implementira
logika za inicijalizaciju i integraciju funkcionalnosti modula u aplikaciju. Zavisno od svrhe
modula, modul može registrovati view objekte u kompozitni korisnički interfejs, dodati ser-
vise u aplikaciju ili nadograditi funkcionalnost aplikacije. Sledeći kôd prikazuje minimalnu
implementaciju modula.

Listing 87: Minimalni kôd za deklarisanje modula


public class MyModule : IModule
{
public void Initialize()
{
// Inicializacioni kod
}
}

9.2.2 Životni ciklus modula


Proces učitavanja modula u Prism aplikaciji uključuje sledeće korake:

1. Registracija/otkrivanje modula. Moduli koje aplikacija učitava u toku izvršavanja se


definišu u katalog modula. Katalog sadrži informacije o modulima koje je potrebno
učitati, njihovu lokaciju i redosled učitavanje modula.

2. Učitavanje modula. Fajlovi koji sadrže module učitavaju se u memoriju. Ova faza
može zahtevati dovlačenje modula sa neke udaljene lokacije.

3. Inicijalizacija modula. Moduli se onda inicijalizuju, što znači kreiranje instance klase
koja nasleduje IModule interfejs i poziv Initialize metoda kroz IModule interfejs.

9.2.3 Katalog modula


Katalog modula sadrži informacije o modulima koji se mogu koristiti u aplikaciji. Katalog
je jednostavno kolekcija ModuleInfo klasa. Svaki modul je opisan kroz ModuleInfo klasu koja
čuva podatke kao što su naziv, tip i lokacija modula. Postoji nekoliko tipičnih pristupa za
popunjavanje kataloga modula:

• Registrovanjem modula kroz kôd.

• Registrovanjem modula kroz XAML fajl.

• Registrovanjem modula upotrebom konfiguracionog fajla.

• Automatskim otkrivanjem modula u direktorijumu na disku.

106
9.2.4 Integracija modula u aplikaciju
Prism pruža sledeće klase za inicijalizaciju aplikacije: UnityBootstrapper i MefBootstrap-
per. Ove klase se mogu upotrebiti za kreiranje i konfiguraciju menadžera modula kako bi se
pronašli i učitali moduli. Moguće je promeniti metod konfiguracije izmenom samo nekoliko
linija koda.
Kao što smo ranije napomenuli, za integraciju modula sa ostatkom aplikacije, koristi
se metoda Initialize. Način implementacije ove metode se razlikuje u implementacijama i
zavisi od strukture aplikacije i sadržaja modula. Sledeće su uobičajene akcije koje je potrebno
uraditi prilikom integracije modula u aplkaciji:
• Dodavanje view klasa u navigacionu strukturu aplikacije. Ovo je uobičajeno prilikom
izgradnje kompozitnih korisničkih interfejsa.

• Pretplaćivanje na dogadaje i servise na nivou aplikacije.

• Registracija deljenih servisa.

9.2.5 Komunikacija izmedu modula


Iako moduli trebaju da imaju slabu medusobnu zavisnost, uobičajeno je za module da
komuniciraju izmedu sebe. Postoje nekoliko paterna komuniciranja na slabo zavisan način,
pri čemu, svaki ima svoje prednosti i mane. Tipično, koristi se kombinacija ovih paterna za
kreiranje rešenja.
• Slabo zavisni dogadaji. Modul može emitovati informaciju da je odredeni dogadaj iza-
zvan. Ostali moduli, mogu se pretplatiti na te dogadaje kako bi mogli da reaguju kada
se dogadaj izazove. Slabo zavisni dogadaji su lagani način podešavanja komunikacije
izmedu dva modula zato što se veoma lako implementiraju. Medutim, dizajn koji se
dosta oslanja na dogadaje, može postati težak za održavanje, posebno ukoliko postoji
potreba za rukovanjem velikim brojem dogadaja kako bi se ispunio jedan zadatak. U
tom slučaju, možda je bolje razmotriti deljeni servis.

• Deljeni servisi. To je klasa kojoj se može pristupiti kroz dobro poznati interfejs.
Tipično, deljeni servisi se nalaze u deljenom projektu i pružaju sistemske servise, kao
što je autentikacija, logovanje i konfiguracija.

• Deljeni resursi. Ukoliko nije poželjno za module da komuniciraju medusobno, moguće


je, takode, omogućiti komunikaciju indirektno kroz deljive resurse kao što je baza po-
dataka ili web servis.

9.3 Ubrizgavanje zavisnosti (eng. Dependency Injection – DI)


Kontejneri, kao što su to Unity i MEF, dopuštaju jednostavnu upotrebu paterna inverzije
kontrole IoC i ubrizgavanje zavisnosti DI (Dependency injection), koji pomažu prilikom
sastavljanja komponenti na slabo zavisan način. Kontejner omogućuje komponentama da

107
dobiju reference na druge komponente od kojih zavise bez hardkodiranja tih referenci. Zato
kontejneri promovišu bolju ponovnu upotrebu koda i unapreduju fleksibilnost aplikacije.
Aplikacije bazirane na Prism biblioteci su modularne aplikacije, i, potencijalno, sastoje
se od velikog broja slabo zavisnih klasa i servisa. Njima je potrebno da medusobno saraduju
kako bi pružili sadržaj i primili obaveštenja zasnovana na akcijama. Zato što su objekti slabo
povezani, potreban je način za medusobnu interakciju i komunikaciju kako bi se isporučila
neophodna biznis funkcionalnost.
Kako bi ove komponente bile zajedno povezane, aplikacije bazirane na Prism biblioteci se
oslanjaju na kontejner ubrizgavanja zavisnosti (dependency injection container ). Kontejneri
smanjuju zavisnost izmedu objekata pružajući mogućnost instanciranja klasa i upravljanjem
njihovim životnim vekom. Tokom kreiranja objekta, kontejner ubrizgava sve zavisnosti koje
objekat zahteva kao parametre. Ukoliko objekti zavisnosti još nisu kreirani, kontejner ih kre-
ira koristeći isti postupak. U nekim slučajevima, kontejner se i sam razrešava kao zavisnost.
Postoje nekoliko prednosti koje se dobijaju upotrebom kontejnera:

• Sklanja potrebu komponenti da sama inicijalizuje svoje zavisnosti i upravlja njihovim


životnim vekom.

• Dopušta promenu implementacije zavisnosti bez promene klijenske klase.

• Omogućuje testiranje tako što omogućava da zavisnosti budu lažirane.

• Čini aplikaciju lakšom za održavanje omogućavajući da se nove komponente lako do-


daju u sistem.

9.3.1 Upotreba kontejnera - osnovni scenariji


Kontejneri se koriste za dve primarne svrhe: registraciju i razrešavanje zavisnosti.

9.3.1.1 Registracija
Pre nego što je moguće ubrizgati zavisnost u objekat, potrebno je registrovati tipove
zavisnosti sa kontejnerom. Registracija tipa, obično, uključuje prosledivanje interfejsa i
konkretnog tipa koji implementira taj interfejs kontejneru. Postoje primarno dva načina
registrovanja tipa i objekata: kroz kôd ili kroz konfiguraciju. Specifičnosti registracije zavise
od tipa kontejnera.
Postoje dva načina registracije tipova i njihovih implentacija u kontejner kroz kôd:

• Moguće je registrovati tip ili mapiranje u kontejner. U odgovarajuće vreme kontejner


vraća referencu na instancu tipa koji je zadat.

• Moguće je registrovati postojeću instancu objekta u kontejner kao Singleton. Kontejner


vraća referencu na postojeći objekat.

108
9.3.1.2 Razrešavanje
Nakon što se tip registruje, moguće je da se on razreši ili ubrizga kao zavisnost. Kada se
tip razrešava, kontejner kreira novu instancu tipa i ujedno se ubrizgavaju sve zavisnosti te
instance.
Generalno, kada se tip razrešava jedna od tri akcije se izvršava:
• Ukoliko tip nije registrovan, baca se izuzetak.
• Ukoliko je tip regitrovan kao singleton, kontejner vraća singleton instancu. Ukoliko je
ovo prvi put da se poziva kontejner za razrešavanje, on po potrebi kreira novu instancu.
• Ukoliko tip nije registrovan kao singleton, kontejner vraća novu instancu.

9.4 Paterni za ubrizgavanje zavisnosti


Veliki broj ljudi meša princip inverzije zavisnosti – DIP, inverziju kontrole – IoC i ubri-
zgavanje zavisnosti – DI. U sledećem tekstu dajemo osnovna objašnjenja ovih pojmova.
DIP predstavlja softverski dizajn princip, IoC predstavlja dizajn patern, a DI predstavlja
implementaciju jedog tipa IoC. Pogledajmo sada koja je razlika izmedu principa i paterna:
• Dizajn princip nam pruža direktive. Princip samo kaže šta je dobro i šta nije dobro.
Ne kaže nam ništa o tome kako da rešimo problem. Samo pruža direktive koje pomažu
dobrom dizajnu sofvera i kako izbeći loš dizajn.
• Dizajn patern je generalno rešenje za čest problem u okviru datog konteksta u dizajnu
softvera.

9.4.1 Princip inverzije zavisnosti (eng. Dependency inversion principle – DIP)


Princip glasi: Umesto da moduli nižeg sloja definišu interfejs na koji moduli višeg sloja
mogu da zavise, moduli višeg sloja treba da definišu interfejs koji moduli nižeg sloja imple-
mentiraju. Formalna definicija principa glasi:
1. Moduli višeg sloja ne treba da zavise od modula nižeg sloja. Oba bi trebalo
da zavise od apstrakcija.
2. Apstrakcije ne bi trebalo da zavise od detalja. Detalji treba da zavise od
apstrakcija.
Na osnovu dijagrama 18 vidimo da u prvom slučaju moduli višeg sloja zavise od interfejsa
nižeg sloja. Kada se kreira nova klasa nižeg sloja, potrebno je izmeniti klasu višeg sloja kako
bi se podržala nova funkcionalnost, što povećava kompleksnost održavanja.
U drugom slučaju vidimo sistem nakon inverzije zavisnosti. Ovde klase višeg sloja definišu
interfejs. Takode, klase višeg sloja ne zavise direktno od klasa nižeg sloja. Klase nižeg sloja
implementiraju interfejs definisan u klasi višeg sloja, tako da klasa višeg sloja ne treba da
se promeni kada se pojavi nova implementacija.
Sledeće su karakteristike sistema koji ne održava DIP:

109
Dijagram 18: Zavisnosti klasa pre i posle upotrebe DIP principa

• Sistem je rigidan. Teško je promeniti deo sistema bez uticaja na veliki broj drugih
delova sistema.

• Sistem je krhak. Kada napravimo promenu, moguće je da prestanu da rade funkcio-


nalno nepovezani delovi sistema.

• Sistem je nepomičan. Teško ga je ponovo upotrebiti u drugoj aplikaciji, zato što je


teško isčupati kôd iz trenutne aplikacije.

Kako je DIP princip, on ne nudi rešenje problema. Ukoliko želimo da znamo kako rešiti
problem, moramo da razmotrimo IoC.

9.4.2 Inverzija kontrole (eng. Inversion of Control – IoC)


IoC definiše način na koji možemo da održavamo DIP. IoC je moguće praktično primeniti
prilikom razvoja softvera. Postoji veliki broj definicija za IoC. Ovde se iznosi jednostavna
definicija kako bismo lakše razumeli patern.
IoC je u osnovi patern za primenu DIP. Pojednostavljeno, IoC predstavlja inverziju kon-
trole nečega, zamenom kontrolora. Posebna klasa ili drugi modul u sistemu odgovorni su za
kreiranje objekta od spolja. IoC znači da menjamo kontrolu u odnosu na normalni način
rada.
DIP kaze da moduli višeg sloja ne bi trebalo da zavise od modula nižeg sloja i da bi
oba trebalo da zavise od apstrakcija. IoC je način za pružanje te apstrakcije, tj. način za
promenu kontrole. Ukoliko želimo da modul višeg sloja bude nezavisan od modula nižeg sloja
potrebno je invertovati kontrolu tako da modul nižeg sloja ne kontroliše interfejs i kreiranje
objekata. Konačno IoC pruža način za inverziju kontrole.
IoC je moguće implementirati na sledeće načine:

• Inverzijom interfejsa. Umesto da pružalac usluga definiše interfejse, koji se, onda,
koriste od strane klijenta, klijent definiše interfejse koje zatim pružalac usluga imple-
mentira.

110
• Inverzijom toka: Inverzija toka kontrole je fundamentalna ideja IoC koja je slična
holivudskoj izreci “Don’t call us, we will call you”. Tokom obavljanja biznis zadatka
ne upravlja pružalac usluga, već se ta kontrola daje klijentu.

• Inverzijom kreiranja: Ovaj vid inverzije se najčešće koristi od strane programera.

9.4.3 Ubrizgavanje zavisnosti (eng. Dependency Injection – DI)


DI je tip IoC gde pomeramo kreiranje i sastavljanje zavisnosti izvan klase, koja zavisi od
njih. U normalnom objektu zavisnosti su kreirane unutar zavisne klase. Upotrebom DI to
se postiže izvan zavisne klase.
Sledeći su tipovi ubrizgavanje zavisnosti.

• Ubrizgavanje u konstruktor. Prosleduju se zavisni objekti u konstruktor objekta zavi-


sne klase. Onaj koji ubrizgava zavisnosti, kreira ih i prosleduje kroz konstruktor.

• Ubrizgavanje kroz seter metoda. Ovo je još jedan tip DI tehnike gde prosledujemo
zavisnosti kroz seter umesto kroz konstruktor.

• Ubrizgavanje upotrebom interfejsa. Ovaj metod najmanje se koristi u praksi i komplek-


san je u odnosu na prethodna dva. Zavisna klasa implementira interfejs. Interfejs ima
metod za postavljanje zavisnosti. Onaj koji ubrizgava zavisnosti, koristi interfejs za
postavljanje zavisnosti. Tako možemo da kažemo da zavisna klasa ima implementaciju
interfejsa i ima metod za postavljanje zavisnosti.

9.4.4 Uklapanje pojmova

Dijagram 19: Uklapanje pojmova DIP, IoC i DI

111
Dijagram 19 prikazuje kako se sve uklapa zajedno. DI nije jedini način implementacije
inverzije kreiranjem, već postoji više takvih načina. Na vrhu, nalazi se DIP koji predstavlja
način dizajniranja softvera koji ne kaže nista o tome kako napraviti nezavisan modul. IoC
pruža način za primenu DIP i ne pruža specifičnu implementaciju. On pruža metode za
inverziju kontrole.

9.5 Implementiranje modula u aplikaciji


Potrebno je definisati module u aplikaciji, i dati primer praktične primene svih gore nave-
denih pojmova. Za definisanje modula potrebno je iskustvo u razvoju softverskih aplikacija
i dobro poznavanje problema koji se rešava. Ukoliko se definišu moduli koji definišu previše
funkcionalnosti, postoji problem da se isčupaju odredene funkcionalnosti iz modula. Ukoliko
se definišu moduli sa malo funkcionalnosti, onda, verovatno, modul zahteva dosta konfigu-
racionih parametara. Projekat je podeljen, na projekte/module na vertikalni i horizontalni
način.

9.5.1 Horizontalni slojevi aplikacije


Aplikacija je horizontalno podeljena u projekte po ugledu na standardne DDD slojeve
aplikacije. Uglavnom je cilj bio imati jedan projekat po sloju kako bi se aplikacija ne bi previše
usitnjavala. Medutim, postoje situacije u kojima je bilo potrebno napraviti kompromise
kreiranjem više projekata za jedan sloj.

Dijagram 20: Horizontalna podela aplikacije na projekte

112
Dijagram 20 prikazuje vertikalni isečak aplikacije, tj. horizontalne slojeve aplikacije. Na
slici možemo da vidimo da aplikacija sadrži dve strane, komandnu stranu i stranu upita.
Takode, možemo da primetimo da je arhitektura strane upita dosta jednostavnija. Sledi
kratak opis svih slojeva.

9.5.1.1 Sloj pristupa podacima


Sloj pristupa podacima sadrže i komandna strana i strana upita. Komandna strana se
sastoji iz repozitorijuma, dok se strana upita sastoji iz implementacija hendlera upita. Iz
dijagrama možemo, takode, da vidimo da su u oba slučaja izvrnute zavisnosti kako viši slojevi
ne bi zavisili od konkretne implementacije sloja pristupa podacima.

9.5.1.2 Sloj domena


Sloj domena je podeljen u dva projekta: osnovni i projekat koji sadrži implementaciju
vrednosnih objekata. Razlog za razdvajanje na dva projekta je prezentacioni sloj. Problem
je bio validacija u prezentacionom sloju. Naime, bilo je potrebno validirati različita polja,
koja već sadrže validacionu logiku u vrednosnim objektima. Obično, validaciona logika se
kopira za web aplikacije, medutim kako je ovo desktop aplikacija, postojala su dva izbora:
duplirati logiku ili referencirati vrednosne objekte. Kako je rešeno da se referenciraju vred-
nosni objekti da bi se izbegla duplikacija koda, bilo ih je potrebno izbaciti u poseban projekat
kako prezentacioni sloj ne treba da ima znanje o implementaciji domena.

9.5.1.3 Aplikacioni sloj


Aplikacioni sloj komandne strane sastoji se iz dva projekta. Prvi je *Command projekat
koji sadrži samo komande koje su dostupne klijentu, bez ikakve implementacije, dok projekat
*CommandImpl sadrži implementaciju komandi i upravlja koordinacijom zadataka potrebnih
da se izvrši komanda.
Strana upita sadrži samo jedan projekat koji sadrži i definiciju upita i model koji se vraća
nakon izvršenja upita.

9.5.1.4 Prezentacioni sloj


Prezentacioni sloj je sloj koji se nalazi na “vrhu” jer je on zadužen za upravljanje interak-
cijom sa korisnikom. Sastoji se uglavnom od MVVM objekata i dodatnog infrastrukturnog
koda koji uglavnom predstavlja implementaciju kontrola koje služe za prikaz različitih po-
dataka. Kao što je rečeno, upravlja interakcijom sa korisnikom, navigacijom u aplikaciji i
kreira komande i upite za aplikacioni sloj.
U slučaju desktop aplikacija, aplikacioni sloj može da se prelije u prezentacioni sloj,
jedan primer toga je upravljanje privilegijama gde se različitim korisnicima prikazuju različite
forme.

113
9.5.2 Definisanje horizontalnih modula aplikacije
Aplikacija je horizontalno podeljena na projekte. Potreban je način za inicijalizaciju i
spajanje projekata kako bi svi projekti činili celinu. U isto vreme, potrebna je mogućnost
uključivanja i zamena odgovarajućih delova bez izmene izvornog koda (u web aplikacijama
je često zahtev učitati module u toku izvšavanja aplikacije, bez ponovnog pokretanja). Sada
je potrebno definisati module.
Moduli trebaju da budu nezavisni jedan od drugog, u smislu, da mogu nezavisno da se
kompajliraju i puštaju u produkciju, jer je to jedan od najbitnijih benefita modula. Kako je
praktično nemoguće da projekti potpuno budu nezavisni jedan od drugog moduli se povezuju
preko deljivih projekata. Deljivi projekti ne treba da budu oni projekti koji sadrže logiku
modula već samo projekti koji izlažu interfejs. Na osnovu tog razmišljanja, aplikacija je
horizontalno podeljena u sledeće module:

Dijagram 21: Horizontalni sloj podeljen u module

• PresentationModule: sadrži prezentacionu logiku i koristi komande i upite. Ko-


mande i upiti definisani su u projektima AppServ.Command i AppServ.Query. Medutim
prezentacioni modul nije svestan njihove implementacije, budući da ovi projekti samo
definišu izgled upita/komandi.
• CommandModule: sadrži svu biznis logiku za procesiranje komandi. Implementira
komande definisane u projektu Command upotrebom agregata, entiteta, vrednosnih
objekata i repozitorijuma definisanih u projektu DomainModel. Medutim nema nika-
kvo znanje o tome kako su repozitorijumi implementirani, s obzirom na to da referencira
samo interfejse koji su definisani u projektu DomainModel.
• QueryModule: sadrži logiku za procesiranje upita. Implementira upite i kreira
objekte modela koji su definisani u projektu AppServ.Query

114
• RepositoryModule: implementira interfejse repozitorijuma. Referencira projekat
DomainModel i implementira interfejse repozitorijuma.

9.5.3 Vertikalni moduli aplikacije


U odnosu na horizontalni pogled aplikacije, gde donekle postoje standardni slojevi, ver-
tikalni slojevi se više definišu u odnosu na potrebe biznisa. Veoma je bitno odrediti koju i
koliko logike sadrži vertikalni modul. Ukoliko sadrži previše logike, teže je isčupati odredenu
logiku kao nezavisnu celinu. Sa druge strane, ukoliko su moduli previše mali, potrebno je
konfigurisati sve te male module i dobri mehanizmi komunikacije izmedu njih. Definisani su
sledeći moduli:

Dijagram 22: Vertikalni moduli aplikacije

• ProizvodModule. Sadrži funcionalnosti za rad sa proizvodima kao što su CRUD


operacije za proizvod, promena cene, izmena trenutnog stanja u magacinu, učlanjivanje
i iščlanjivanje proizvoda iz različitih grupa proizvoda.

• KalkulacijaModule. Sadrži funkcionalnosti za izradu faktura i kalkulacija, rad sa


dobavljačima, kalkulisanje faktura i procesiranje kalkulacija.

• POSModule. Sadrži funkcionalnost kucanja računa.

• ReportingModule. Sadrži različite izveštaje. Za sada su to izveštaji koji grafički


prikazuju najveće dobavljače, najprodavanije proizvode i dnevni pazar za odredeni
vremenski period.

• Kernel. Kernel, zapravo, i nije modul, ali ovde se konceptualno posmatra kao modul
jer se referencira u svim ostalim modulima. Kernel čine projekti koji sadrže objekte
koji su zajednički za sve ostale module. U kernelu je definisano većina baznih intefejsa
i klasa koji se koriste u ostalim objektima. Kernel sadrži iste projekte/slojeve kao i
ostali moduli jer su zajedničke funkcionalnosti grupisane na nuvou projekta.

115
• Infrastrukturni Moduli. Pored prikazanih modula na slici 22, projekat sadrži do-
datne module koji nisu prikazani na njoj. Ovi moduli obično implementiraju infra-
strukturne servise koji su definisani u kernelu ili u ostalim modulima. Na primer,
modul može implementirati servis za slanje SMS-a koji se nalazi u kernelu i koji se ko-
risti u svim ostalim modulima. Modul MySql.Database je definisan kako bi priključio
MySql implementaciju repozitorijuma ostatku aplikacije.

Kao što možemo da vidimo na osnovu dijagrama 22 svi moduli referenciraju Kernel.
Takode, moduli POS i Kalkulacija zavise od modula Proizvod kako je potrebno smanjiti
količinu proizvoda na stanju prilikom kucanja računa i potrebno je promeniti cenu proizvoda
prilikom kalkulisanja fakture. Neophodno je zapaziti da Prodavnica modul, glavni modul
aplikacije, referencira samo Kernel.
Kako moduli ne smeju direktno da se referenciraju medusobno, potrebno je ukloniti vezu
izmedu POS i Proizvod modula, kao i Kalkulacija i Proizvod modula. To je rešeno tako što
su dodati novi deljeni projekti koji se koriste u oba modula. Kako POS i Kalkulacija moduli
zahtevaju usluge od modula Proizvod, kreiraju se deljeni projekti (koji definišu servise),
koji se koriste u klijentskim modulima Kalkulacija i POS, a implementiraju se u modulu
Proizvod. Nakon dodavanja novih projekata, dijagram zavisnosti modula izgleda kao na slici
23.

Dijagram 23: Vertikalni moduli aplikacije nakon što su uvedeni deljeni projekti

9.6 Inicijalizacija aplikacije i povezivanje modula


Inicijalizacija aplikacije kreće iz modula Prodavnica. Kao što smo videli na osnovu di-
jagrama, ovaj modul je nezavisan od ostalih modula (osim Kernela), a zadužen je i za
konfiguraciju cele aplikacije i učitavanje ostalih modula. Izvršavanje Prism aplikacije kreće
iz Bootstrapper klase, koja se obično nasleduje i služi za konfiguraciju aplikacije prilikom
učitavanja u memoriju (više o inicijalizaciji Unity aplikacija možete naći u dokumentaciji
za Prism biblioteku [14]). Jedan od koraka prilikom inicijalizacije jeste učitavanje kataloga
modula. Kako se u aplikaciji katalog modula učitava iz konfiguracionog fajla jedini kôd koji
se nalazi u metodi za konfiguraciju modula je sledeći:

116
Listing 88: Metoda za konfigurisanje kataloga modula
protected override IModuleCatalog CreateModuleCatalog()
{
return new ConfigurationModuleCatalog();
}

Sva ostala konfiguracija nalazi se u XML konfiguracionom fajlu App.conf. U tom fajlu je
izmedu ostalog definisan string za konekciju prema bazi podataka, način logovanja informa-
cija o izvršenju aplikacije i definisani su moduli koje je potrebno učitati prilikom izvršenja
aplikacije. Kada se moduli učitavaju na ovaj način, onda ih je lako dodati ili izbrisati iz
aplikacije bez promene izvornog koda. XML za inicijalizaciju modula je, uglavnom, isti za
sve module i predstavlja varijaciju sledećeg primera:

Listing 89: Učitavanje modula u aplikaciju


<module assemblyFile="Kernel.AppServ.QueryDispatcher.dll"
moduleType="Kernel.AppServ.QueryDispatcher.Module.MainModule,
Kernel.AppServ.QueryDispatcher, Version=1.0.0.0,
Culture=Neutral, PublicKeyToken=null"
moduleName="KernelQueryDispatcher" startupLoaded="true"/>

Prilikom inicijalizacije potrebno je podesiti zavisnost modula u smislu redosleda inicija-


lizacije modula. U projektu uglavnom nema zavisnosti osim jednog izuzetka. Modul Doma-
inEventsProcessor inicijalizuje objekat DomainEventsDispatcher i registruje ga u kontejner
kao Singleton. Budući da je potrebna obrada dogadaja prilikom procesiranja komandi, svi
komandni moduli moraju da sačekaju da se prvo inicijalizuje ovaj modul.

9.6.1 Inicijalizacija modula


Moduli se inicijalizuju tako što se kreira instanca klasa koja nasleduje interfejs IModule
i pozove se metoda Initialize. Svaki modul u projektu sadrži po jednu klasu koja nasleduje
interfejs IModule i, uglavnom, se nalazi u direktorijumu Module sa nazivom MainModule.
Sledi primer inicijalizacije modula DomainEventsProcessor :

Listing 90: Inicijalizacija medula DomainEventsProcessor


public MainModule(IUnityContainer container)
{
_container = container;
}

public void Initialize()


{
// init domain events dispatcher
var dispatcher = new DomainEventsDispatcher(_container);
_container.RegisterInstance(dispatcher, new ContainerControlledLifetimeManager());
_container.RegisterType<IDomainEventsDispatcher, DomainEventsDispatcher>();
_container.RegisterType<IDomainEventsRegistrator, DomainEventsDispatcher>();
}

117
Najčešće prilikom konstrukcije modula zahteva se referenca na kontejner kako bi mogao
da se iskoristi za registraciju i razrešavanje tipova. U prethodnom primeru, dispečer dogadaja
domena je registrovan kao Singleton. Referenca se dobija na njega kadgod se zatraži referenca
na interfejs IDomainEventsRegistrator, koji se koristi za registrovanje hendlera dogadaja ili
interfejs IDomainEventsDispatcher, koji se koristi kada je potrebno dispečovati dogadaje.
Kao što smo videli iz ranijih poglavlja, kada je potrebno razrešiti odredenu instancu in-
terfejsa, obično se ta zavisnost zahteva u konstruktoru, koje zatim kontejner automatski
ubrizgava. Kontejner se uglavnom koristi prilikom inicijalizacije aplikacije za registrovanje
tipova i na strateškim mestima u projektu za razrešavanje referenci, kao što je to UnityQu-
eryDispatcher, UnityCommandDispatcher i metod inicijalizacije modula. Uglavnom je loša
praksa koristiti referencu na kontejner u objektima, koji zahtevaju zavisnost (razloge za to
možemo videti u ovom izvoru [26]).

118
10 Korisnički interfejs
Korisnički interfejs može biti konstruisan praćenjem jedne od sledećih paradigmi:

• Sve kontrole forme se nalaze u jednom XAML fajlu, izgled forme je poznat u toku
dizajniranja.

• Logički bliski delovi su povezani u posebne elemente, obično korisničke kontrole. Ti


elementi se referenciraju u formama. Izgled forme je poznat u toku dizajniranja.

• Logički bliski delovi forme su povezani u posebne elemente. Nije poznato koji će
elementi biti smešteni na formu jer se oni dinamički dodaju u vremenu izvršavanja.
Aplikacije koje koriste ovu metodologiju nazivaju se kompozitne aplikacije.

Korisnički interfejs kompozitnih aplikacija je sastavljen od vizuelnih komponenti koje


su medusobno nezavisne. Komponente su obično view klase i obično se nalaze u različitim
modulima aplikacije (ne obavezno). Ukoliko je aplikacija podeljena u module, potreban je
način konstruisanja slabo zavisnog korisničkog interfejsa, tako da korisniku aplikacije i dalje
predstavlja neprestano korisničko iskustvo i pruža potpuno integrisanu aplikaciju.
Kako bi konstruisali UI, potrebna je arhitektura koja dopušta kreiranje view objekata
sastavljenih od nezavisnih vizuelnih elemenata generisanih u vremenu izvršavanja. Dodatno,
arhitektura treba da pruži strategiju za medusobnu komunikaciju elemenata bez uvodenja
dodatnih zavisnosti izmedu njih.

10.1 Koncepti rasporedivanja UI elemenata


Polazni objekat u kompozitnoj aplikaciji, poznat je kao Shell. Shell se ponaša kao glavna
strana aplikacije. Shell može da sadrži jedan ili više regiona. Regioni označavaju lokacije za
prikaz, gde je moguće ubaciti sadržaj vremenu izvršenja. Sadržaj regiona može biti učitan
automatski ili na zahtev, zavisno od potreba aplikacije.
Tipično, sadržaj regiona je view. View klase sadrže deo korisničkog interfejsa koji pred-
stavlja posebnu celinu, tj. odvojen je od ostalih delova aplikacije. View je moguće definisati
kao korisničku kontrolu, šablon podataka ili čak kao kontrolu.
Region upravlja prikazom i rasporedom svojih view objekata. Regionima može da se
pristupi po njihovim imenima. Oni podržavaju dinamičko dodavanje i brisanje view obje-
kata. Možemo misliti o regionima kao o kontejnerima u kojima se view objekti automatski
učitavaju.

10.1.1 Shell
Shell predstavlja glavni view aplikacije koji sadrži primarni prezentacioni sadržaj. U
WPF aplikaciji, Shell predstavlja instancu Window klase.
Shell igra ulogu glavne strane i pruža strukturni raspored elemenata u aplikaciji. Shell
sadrži jedan ili više imenovanih regiona, gde se ubrizgavaju view objekti iz različitih modula

119
koje je potrebno prikazati. Takode, može definisati i odredene elemente, koji se nalaze u
prvom planu, kao što je pozadina, glavni meni i lista alata.
Shell definiše celokupan izgled aplikacije. Može definisati stilove i graničnike koji su
dostupni samo za njega, a, takode, može definisati stilove, šablone i teme koji se primenjuju
na view objekte (koji se ubacuju u regione).
Tipično, Shell je deo glavnog projekta i on može (ali i ne mora) referencirati module koji
sadrže view objekte, koje je potrebno učitati u regione.

10.1.2 View
View objekti predstavljaju glavnu gradivnu jedinicu korisničkog interfejsa u kompozit-
noj aplikaciji. View objekat je moguće definisati kao korisničku kontrolu, stranicu, šablon
podataka ili kao prilagodenu kontrolu (custom control ). View objekat sadrži deo korisničkog
interfejsa koji treba da bude nezavisan od ostalih delova aplikacije što je moguće više.

10.1.3 Kompozitni view


View objekat koji podržava specifičnu funkcionalnost može postati previše komplikovan.
U tom slučaju, verovatno je potrebno podeliti taj objekat na nekoliko manjih izvedenih
view objekta, gde glavni objekat rukuje svojom konstrukcijom i upotrebom izvedenih view
objekata. Ovo može da se postigne statički u vremenu dizajna ili dinamički u vremenu
izvršavanja upotrebom regiona. View objekat, koji nije u potpunosti definisan u jednoj
klasi, obično se naziva kompozitni view (composite view). U velikom broju situacija, kompo-
zitni view objekat je odgovoran za konstruisanje izvedenih view objekata i za koordinisanje
njihovim medusobnim interakcijama.

10.1.4 Konstruisanje view objekata


U kompozitnim aplikacijama, view objekti, koji se nalaze u različitim modulima, moraju
biti prikazani na zadatim lokacijama korisničkog interfejsa u trenutku izvršavanja aplikacije.
Kako bi se ovo postiglo, potrebno je definisati lokacije gde se view objekti prikazuju i način
njihovog kreiranja. U Prism aplikacijama, ovo je moguće uraditi na dva načina:

• View Discovery - potrebno je podesiti vezu izmedu imena regiona i tipa view objekta.
Kada se region kreira, on pretražuje sve definisane veze za taj region, instancira view
objekte i smešta ih na odgovarajuće lokacije.

• View Injection - referenca na region se nalazi u kodu, view objekat programski se


ubacuje u region. Obično, ovo se radi tokom inicijalizacije modula ili kao rezultat
akcije korisnika. Kôd aplikacije treba da napravi upit po nazivu menadžeru regiona
kako bi pribavio referencu na region. Nakon uspešnog pribavljanja reference na region,
potrebno je ubaciti view objekat u region.

120
10.2 Navigacija
Kada korisnik radi sa aplikacijom, korisnički interfejs se konstanto ažurira kako bi odrazio
trenutni zadatak i podatke sa kojima korisnik radi. Proces kojim aplikacija koordiniše ove
promene naziva se navigacija.
Često, navigacija znači da odredene kontrole treba da budu uklonjene sa korisničkog
interfejsa, dok je druge kontrole potrebno prikazati. U drugim slučajevima, navigacija znači
da je potrebno ažurirati vizuelni status jedne ili više kontola, na primer: ako razmotrimo
master-detail scenario, podaci prikazani na kontroli, koja prikazuje detalje, ažuriraju se tako
što se prikazuju detalji trenutno odabrane stavke u master kontroli. Svi ovi scenariji mogu
da se razmatraju kao navigacija, zato što je potrebno ažurirati korisnički interfejs ne bi li se
odrazio trenutni korisnički zadatak i trenutno stanje aplikacije.
Navigacija unutar aplikacije može biti rezultat interakcije korisnika sa korisničkim inter-
fejsom ili iz same aplikacije kao rezultat nekog internog procesa. U jednim slučajevima na-
vigacija zahteva veoma jednostavna ažuriranja korisničkog interfejsa koja ne implementiraju
posebnu logiku aplikacije. U drugim slučajevima, aplikacija može implementirati komplek-
snu logiku kako bi se osigurao odredeni biznis scenario. Na primer, aplikacija može da ne
dozvoli korisniku da se udalji sa trenutnog ekrana, dok se ne osigura to da su podaci koje je
korisnik uneo tačni.
Navigaciju koja je postignuta preko promene statusa na postojećim kontrolama u vizu-
elnom stablu, nazivamo statusno bazirana navigacija. Navigaciju koja je postignuta dodava-
njem ili brisanjem elemenata iz vizuelnog stabla nazivamo prezentaciono bazira navigacija.

10.3 Prism navigacija


Implementiranje navigacije u WPF aplikacijama je lako zato što WPF ima direktnu
podršku za navigaciju. Medutim, implementiranje navigacije u kompozitnim aplikacijama
može biti izazovno, jer te aplikacije koriste slabo zavisne komponente. U tom slučaju, moguće
je upotrebiti Prism bilibioteku za implementiranje navigacije.

10.3.1 Statusno bazirana navigacija


Kod statusno bazirane navigacije, view objekat koji predstavlja korisnički interfejs se
ažurira kroz promene stanja u view model objektu ili kroz interakcije korisnika sa korisničkim
interfejsom. Kod ovog tipa navigacije, umesto zamene view objekta drugim view objektom,
menja se samo stanje view model objekta koje se reflektuje na korisnički interfejs. Zavisno
od toga kako se menja stanje view model objekta, ažuriranje korisničkog interfejsa korisniku
može izgledati kao navigacija.
Ovakav stil navigacije pogodan je u sledećim siguacijama:
• Iste podatke/funkcionalnost potrebno je prikazati na drugačiji način - drugaciji stilovi
ili formatiranje.
• Potrebno je promeniti stil ili raspored komponenti na korisničkom interfejsu kako bi se
odrazilo trenutno stanje view model objekta.

121
• Potrebno je inicirati ograničenu modalnu ili nemodalnu interekciju sa korisnikom.
Ovaj stil navigacije nije pogodan u situacijama u kojima korisnički interfejs mora da
predstavi drugačije podatke korisniku ili u sitacijama gde korisnik treba da odradi drugi
zadatak. U tim slučajevima, bolje je implementirati posebne view/view model objekte za
obradu tog zadatka. Slično, ovaj stil navigacije nije pogodan ako su različita stanja preterano
kompleksna jer kôd u klasama može postati prevelik i težak za održavanje. U ovom slučaju,
bolje je implementirati view baziranu navigaciju upotrebom posebnih view objekata.

10.3.2 View bazirana navigacija


Iako statusno bazirana navigacija može biti korisna u situacijama na koje smo ranije u
radu ukazali, navigacija u okviru aplikacije se najčešće postiže zamenom jednog view objekta
drugim.
Zavisno od zahteva i kompleksnosti aplikacije, ovaj proces može biti veoma kompleksan i
zahtevati pažljivu koordinaciju. Sledeći su česti izazovi koje je potrebno razmotriti prilikom
implementiranja view bazirane navigacije:
• Meta navigacije - kontejner ili kontrola na koji se dodaju view objekti može reago-
vati drugačije na navigaciju prilikom dodavanja i sklanjanja view objekata. Takode,
dodavanje i sklanjanje objekata iz kontrole može se vizuelno predstaviti na različite
načine. U najvećem broju slučajeva metu navigacije predstavlja obična kontejnerska
kontrola koja može da sadrži i predstavi samo jedan view objekat. U tom slučaju, sce-
nario dodavanja i sklanjanja komponenti je jednostavan. Medutim, postoje situacije
u kojima meta navigacije može biti kontrola koja sadrži listu objekata. Tada, može
se javiti potreba za aktiviranjem view objekta, koji je već ubačen na kontrolu, iil za
dodavanjem novog view objekta na specifičan način.
• Potrebno je, takode, definisati način identifikacije view objekata na koje je potrebno
izvršiti navigaciju. Na primer, u web aplikaciji stranica na koju je potrebno izvršiti
navigaciju često se identifikuje preko URI linka. U klijentskoj aplikaciji, view objekat
može biti identifikovan po imenu tipa, lokaciji, ili na drugi način. Dalje, u kompozitnoj
aplikaciji, koja se sastoji od slabo povezanih modula, view objekti se često definišu u
različitim modulima. Individualni view objekti moraju biti identifikovani tako da se
ne stvara direktna zavisnost izmedu modula.
• Nakon identifikovanja view objekta, proces instanciranja novog view objekta mora biti
pažljivo koordinisan. Ovo može biti naročito važno kada se koristi MVVM patern.
• MVVM patern pruža čisto razdvajanje izmedu korisničkog interfejsa i njegove prezen-
tacione i biznis logike. Medutim, navigaciono ponašanje aplikacije razapeto je i na
korisniški interfejs i prezentacionu logiku. Korisnik često inicira navigaciju iz kori-
sničkog interfejsa. Korisnički interfejs se ažurira kao rezultat navigacije, ali navigaciju
često inicijalizuju i koordinišu njome view model objekti. Mogućnost čistog razdvaja-
nja navigacionog ponašanja kroz view i view model objekte važan je aspekt, koji vredi
razmatrati.

122
• Prilikom navigacije često je potrebno proslediti parametre ili kontekst view objektu
na koji se vrši navigacija, kako bi mogao da se ispravno inicijalizuje. Na primer,
ukoliko korisnik želi da poseti stranicu za izmenu detalja odredenog klijenta, potrebno
je proslediti ID ili podatke klijenta kako bi view objekat mogao da prikaže potrebne
podatke.

• Veliki broj aplikacija ima pažljivo koordinisanu navigaciju kako bi se obezbedilo za-
dovoljenje odredenih biznis pravila. Na primer, ukoliko postoje greške prilikom unosa
podataka, korisnici mogu biti upitani pre nego što izvrše navigaciju sa trenutnog view
objekta, kako bi mogli da isprave invalidne podatke i uspešno kompletiraju operaciju
izmene podataka. Takode, oni mogu biti upitani za potvrdu snimanja promena koje su
načinili nad objektom. Ovaj proces zahteva pažljivu koordinaciju izmedu prethodnog
i sledećeg view objekta u navigaciji.

• Na kraju, najveći deo modernih aplikacija dozvoljava korisniku laku navigaciju nazad
kroz istoriju. Slično, mnoge aplikacije sadrže čarobnjake gde korisnik prolazi kroz
različite forme za unos podataka i gde korisnik može da se kreće napred/nazad kroz
njih i u tom procesu dodaje i menja podatke pre kompletiranja zadatka. Ovakvi
scenariji zahtevaju neku vrstu istorizacionog mehanizma tako da redosled navigacije
može biti biti sačuvan, ponovljen ili pre-definisan.

Svi ovi zadaci implementirani su uz pomoć mehanizma regiona Prism biblioteke. U


nastavku rada opisujemo osnovnu funkcionalnost regiona i način na koji je navigacija imple-
mentirana u aplikaciji.

10.3.3 Implementacija Regiona


Prism regioni su dizajnirani sa ciljem da podrže razvoj modularnih aplikacija dopuštajući
da korisnički interfejs aplikacije bude konstruisan na slabo povezan način. Regioni dopuštaju
view objektima da budu definisani u modulima i budu prikazani na isti korisnički interfejs
aplikacije, bez zahteva da modul ima znanje o strukturi korisničkog interfejsa aplikacije.
Oni dopuštaju da promena rasporeda komponenti u aplikaciji bude postignuta na lak i
bezbolan način omogućujući dizajnerima da odaberu najbolji dizajn i raspored komponenata
u aplikaciji, bez zahteva za promenama u samim modulima.
Prism regioni su u suštini imenovani držači prostora na kojima se prikazuju view objekti.
Bilo koja kontejner kontrola u aplikaciji može biti deklarisana kao region, to se postiže
jednostavno dodavanjem priključnog propertija RegionName, kao što je prikazano u sledećem
primeru:

Listing 91: Zadavanje regiona


<controls:TransitioningContentControl
regions:RegionManager.RegionName="MainRegion" ... />

123
Za svaku kontrolu zadatu kao region Prism kreira Region objekat koji predstavlja prostor
koji ona kontroliše i RegionAdapter objekat koji upravlja rasporedom i aktivacijom view
objekata unutar zadate kontrole. Postoje gotove implementacije klase RegionAdapter za
najkorišćenije kontrole ali moguće je i kreirati sopstvene adaptere. Pristup Region objektima
u kodu aplikacije vrši se kroz klasu RegionManager.
U velikom broju slučajeva, region kontrola je jednostavna kontrola, kao što je to Content-
Control, koja može da prikaže jedan view objekat u jedinici vremena. U drugim slučajevima,
Region kontrola može prikazati više view objekata u isto vreme, kao što je TabControl ili
ListBox kontola.
Region adapter upravlja listom view objekata dodeljenih regionu. Jedan ili više ovih view
objekata prikazuju se u region na kontrolu, u skladu sa definisanom strategijom rasporedivanja
kontrola unutar regiona. View objektima može biti dodeljeno ime koje se može koristiti za
kasnije preuzimanje istih. Ukoliko postoji više view objekata u regionu, region adapter je
zadužen za odredivanje aktivnog.
Kao što je već ranije napomenuto, view objekti se dodaju u region na dva načina. Prvi je
ubrizgavanje view objekata (View Injection), dopušta da se view objekti programski dodaju
u region. Ovaj pristup je koristan za dinamički sadržaj, gde se view objekti, koji se prikazuju
na regionu, menjaju često prateći prezentacionu logiku.
Ubrizgavanje je podržano kroz metod Add u klasi Region. Sledeći primer koda prikazuje
kako se može preuzeti referenca na Region objekat preko klase RegionManager i programski
dodati view u region.

Listing 92: Dodavanje view objekta u region


IRegionManager regionManager = ...;
IRegion mainRegion = regionManager.Regions["MainRegion"];
WProizvodi view = container.Resolve<WProizvodi>();
mainRegion.Add(view);

Drugi metod, pod nazivom view discovery, dopušta modulu da registruje view tip za
ime regiona. Kadgod se region sa zadatim imenom prikaže, instanca zadatog view tipa se
automatski kreira i prikazuje u regionu. Ovaj pristup je koristan za relativno statički sadržaj,
gde view objekat treba da bude prikazan u regionu koji se ne menja. Ovaj metod je podržan
kroz metodu RegisterViewWithRegion koja se nalazi u klasi RegionManager.

Listing 93: Registracija view objekta na region


var regionManager = ...;
regionManager.RegisterViewWithRegion(GlobalRegions.CommandRegion, typeof (WCommands));

10.3.4 Osnovna navigacija u regionu


Oba prethodna metoda dodavanja view objekata u region mogu biti posmatrani kao
ograničeni način navigacije. Regioni podržavaju generalniji vid navigacije, baziran na URI

124
identifikatore i proširiv mehanizam navigacije.
Navigacija u okviru regiona znači da view objekat treba da bude prikazan u okviru re-
giona. View objekat (koji je potrebno prikazati) identifikuje se preko URI parametra, koji
sadrži naziv tog view objekta. Moguće je programski inicijalizovati navigaciju upotrebom
RequestNavigate metoda definisanog u interfejsu INavigateAsync. Ovaj interfejs implemen-
tira Region klasa, dozvoljavajući inicijalizovanje navigacije u okviru tog regiona. Primer:

Listing 94: Navigacija na region


IRegionManager regionManager = ...;
regionManager.RequestNavigate(GlobalRegions.MainRegion,
new Uri(CoreForms.WDobavljacSel, UriKind.Relative));

U navigacioni URI zadaje se naziv view objekta, koji treba biti kreiran i prikazan u
regionu. Naziv se koristi za razrešavanje tipa view objekta, kako bi se kreirala nova instanca
upotrebom kontejnera.
Tokom navigacije, zadati view objekat instancira se upotrebom kontejnera zajedno sa
odgovarajućim view model objektom i ostalim komponentama. Nakon što se view objekat
instancira, on se dodaje na zadati region.

10.3.5 Uloga view i view model objekta u navigaciji


Često view i view model objekti žele da učestvuju u navigaciji. Prism biblioteka omogućuje
ovu funkcionalnost upotrebom INavigateAware interfejsa. Ovaj interfejs moguće je imple-
mentirati na view ili (mnogo uobičajenije) na view model objekte. Implementiranjem ovog
interfejsa omogućava se da view/view model objekti učestvuju u navigaciji.
Tokom navigacije, Prism proverava da li view klasa na koju se vrši navigacija (ili njen
DataContext za view model objekte) implementira interfejs INavigateAware. Ukoliko to jeste
slučaj pozivaju se potrebne metode tokom navigacije. Interfejs INavigationAware definiše
sledeće metode:

Listing 95: Interfejs INavigationAware


public interface INavigationAware
{
bool IsNavigationTarget(NavigationContext navigationContext);
void OnNavigatedTo(NavigationContext navigationContext);
void OnNavigatedFrom(NavigationContext navigationContext);
}

Metod IsNavigationTarget dopušta objektu da vrati status o tome da li može da obradi


trenutni zahtev za navigaciju. Ovo je korisno u situacijama gde se ista instanca view objekta
ponovno upotrebljava za različite podatke i scenarije. Na primer, view objekat može da pri-
kazuje informacije o korisnicima i može se ažurirati tako da prikazuje informacije za različite
korisnike. U projektu ova metoda uvek vraća pozitivan rezultat, kako se isti view objekat
koristi za prikazivanje različitih podataka.

125
Listing 96: Implementacija metode IsNavigationTarget
public virtual bool IsNavigationTarget(NavigationContext navigationContext)
{
return true;
}

Metod OnNavigatedTo poziva se kada je potrebno izvršiti navigaciju na trenutni objekat.


Ovaj metod dopušta objektu, na koji se vrši navigacija, da se inicijalizuje. U primeru iz
aplikacije vidimo da se u ovom slučaju brišu komande iz komandnog panela.

Listing 97: Implementacija metode OnNavigatedTo


public override void OnNavigatedTo(NavigationContext navigationContext)
{
base.OnNavigatedTo(navigationContext);

CommandInjector.AddViewToRegion(_addCommandVm);
CommandInjector.AddViewToRegion(_editCommandVm);
CommandInjector.AddViewToRegion(_deleteCommandVm);
CommandInjector.AddViewToRegion(_cvsCommandVm);

RefreshData();
}

Metoda OnNavigatedFrom poziva se kada je potrebno izvršiti navigaciju sa aktivnog


objekta na drugi. Ovaj metod omogućava trenutno aktivnom objektu da snimi svoje stanje
ili da se pripremi za deaktiviranje ili brisanje iz korisničkog interfejsa. U aplikaciji ova metoda
obično se koristi za dodavanje komandi na komandni panel i za učitavane podataka, kao što
je to prikazano u sledećem primeru iz klase BaseSelViewModel.

Listing 98: Implementacija metode OnNavigatedFrom


public override void OnNavigatedFrom(NavigationContext navigationContext)
{
base.OnNavigatedFrom(navigationContext);
CommandInjector.RemoveViewFromRegion(_addCommandVm);
CommandInjector.RemoveViewFromRegion(_editCommandVm);
CommandInjector.RemoveViewFromRegion(_deleteCommandVm);
CommandInjector.RemoveViewFromRegion(_cvsCommandVm);
}

10.3.6 Prosledivanje parametara tokom navigacije


Kako bi se implementiralo potrebno ponašanje aplikacije tokom navigacije, često je po-
trebno proslediti dodatne podatke tokom navigacije. NavigationContext objekat pruža pri-
stup navigacionom URI parametru. Parametru NavigationContext je moguće pristupiti u
svim metodama iz interfejsa IsNavigationTarget.
Prism pruža UriQuery klasu kako bi olakšao zadavanje i preuzimanje navigacionih para-
metera. Klasa se koristi za dodavanje parametara na URI u toku inicijalizacije navigacije i

126
olakšava pristup tim parametrima u drugim metodama u toku navigacije. UriQuery klasa
održava listu ključ-vrednost parova za svaki parametar.
U sledećem primeru iz projekta, možemo da vidimo kako se dodaju parametri u okviru
metode, koja vrši navigaciju na view kalkulacija, kako bi se izvršilo kalkulisanje fakture.

Listing 99: Navigacija upotrebom parametra


private void OnKalkulacijaCommand()
{
...
UriQuery query = new UriQuery();
query.Add(GlobalConstants.NavCallerControl, CoreForms.WFakturaSel);
query.Add(GlobalConstants.SelectedItem, ItemsCollection.CurrentItem.Id);
Manager.RequestNavigate(GlobalRegions.MainRegion,
new Uri(CoreForms.WKalkulacijaEdt + query, UriKind.Relative));
}

Parametri se preuzimaju iz propertija Parameters, koji se nalazi u objektu Navigation-


Context. Ovaj properti vraća instancu UriQuery klase, koja pruža indeksni properti, koji
dopušta lak pristup individualnim parametrima.
Sledeći primer demonstrira preuzimanje navigacionih parametara potencijalno prosledenih
iz VMFakturaEdit objekta u view model klasi za kreiranje/izmenu detalja kalkulacije.

Listing 100: Preuzimanje parametra u navigaciji


public override async void OnNavigatedTo(NavigationContext navigationContext)
{
string fakturaIdStr = navigationContext.Parameters[GlobalConstants.SelectedItem];

if (fakturaIdStr != null)
{
long fakturaId = long.Parse(fakturaIdStr);
var res = await LoadFaktura(fakturaId);
if (!res)
InteractService.ShowShortError("Greska prilikom ucitavanja podataka",
"Desila se greska prilikom ucitavanja"
+ " fakture za kalkulisanje: fakturaId:" + fakturaId);
base.OnNavigatedTo(navigationContext, false);
}
else
{
base.OnNavigatedTo(navigationContext, true);
}
CommandInjector.AddViewToRegion(_procesiranjeKalkulacije);
}

Iz prethodnog primera možemo da vidimo da se prvo pribavlja parametar SelectedItem


(ukoliko je on zadat), a onda se vrši inicijalizacija prikaza upotrebom ID-a fakture. Inače,
poziva se bazna klasa za obradu navigacije (u tom slučaju, se iščitava ID kalkulacije i dobavlja
se objekat kalkulacije).

127
10.4 Interakcija sa korisnikom
Često aplikacija treba da obavesti korisnika o nekom dogadaju ili pita za potvrdu pre
nego što izvrši odredene operacije. Ove interakcije često su kratka obraćanja korisniku, di-
zajnirane tako da jednostavno informišu korisnike o promeni u aplikaciji ili da se preuzme
jednostavan odgovor od korisnika. Interakcije mogu biti modalne, u slučajevima kada prika-
zujemo poruku korisniku gde očekujemo odgovor od korisnika ili nemodalne, u slučaju kada
se prikazuju informacije korisniku o promeni stanja u aplikaciji, ali se ne očekuje obavezno
reakcija korisnika na obaveštenje.
Postoji više načina na koji je moguće implementirati interakciju sa korisnikom, ali može
biti izazovno implementirati je u MVVM aplikacijama, gde je potrebno očuvati čisto odva-
janje odgovornosti izmedu view i view model objekata. Na primer, u običnoj aplikaciji koja
ne implementira MVVM patern, moguće je upotrebiti direktno poziv kontrole iz koda koji
implementira prezentacionu logiku (code behind fajl u WPF aplikacijama) kako bi se dobio
unos korisnika. U MVVM aplikaciji, ovo nije prepručljivo jer se krši pravilo razdvajanja
odgovornosti izmedu view i view model objekata.
Ukoliko interakciju posmatramo iz ugla MVVM paterna, nalazimo da je view model obje-
kat, odgovoran za iniciranje interakcije sa korisnikom, kako bi dobio odgovor potreban za
dalje procesiranje, dok je view objekat zadužen za prikaz i interakciju sa korisnikom upotre-
bom odgovarajućih kontrola. Očuvavanjem čistog razdvajanja odgovornosti izmedu prezen-
tacione logike implementirane u view model klasi i korisničkog iskustva implementiranog u
view klasi, pomaže unapredenju testabilnosti i fleksibilnosti aplikacije.
Postoje dva pristupa za implementiranje ove vrste interakcije sa korisnikom u MVVM
paternu. Jedan pristup je implementiranje servisa, koji se koristi u view model objektima
kako bi inicirao komunikaciju sa korisnikom. Drugi pristup koristi dogadaje koji se iniciraju
u view model objektima kako bi se izrazila želja za komunikaciju sa korisnikom, zajedno sa
komponentama u view objektima (koji su vezani za te dogadaje i koji upravlja vizuelnim
delom interakcije). U daljem tekstu, opisuje se samo prvi pristup, budući da je samo on
upotrebljen prilikom inicalizacije ove vrste komunikacije sa korisnikom.

10.4.1 Komunikacija sa korisnikom upotrebom interakcionog servisa


Kod ovog pristupa, view model objekat oslanja se na interakcioni servis kako bi inicirao
interakciju sa korisnikom. Ovaj pristup podržava čisto razdvajanje odgovornisti i testiranje
view model objekata skrivanjem implementacije korisničkog interfejsa u servisu. Tipično,
view model objekat ima pokazivač na instancu interfejs servisa. Nakon što view model
objekat dobije referencu na interakcioni servis, može programski da zahteva interakciju sa
korisnikom kadgod je to potrebno. Sledi primer interakcionog servisa implementiranog u
projektu:

128
Listing 101: Interfejs servisa za interakciju
public interface IServInteraction
{
void ShowNotificationInfo(string title, string message);
void ShowNotificationWarning(string title, string message);
void ShowNotificationError(string title, string message);
void ShowConfirmation(ConfirmationMessage message, Action<ConfirmationMessage> callback);
void ShowInfoDialog(DialogMessage message);
void ShowWarningDialog(DialogMessage message);
void ShowErrorDialog(DialogMessage message);
void ShowDialog(ConfirmationDialog dialog);
}

Servis za interakciju podržava više tipova interakcije.

• Moguće je korisniku samo prikazati poruku na koju on treba da reaguje, tipa kada se
desi greška prilikom validacije ulaznih podataka ili prilikom procesiranja, i u tu svrhu
služe metode Show*Dialog.

• Moguće je tražiti potvrdu od korisnika za neku akciju, kao što je često potrebna potvrda
od korisnika prilikom brisanja nekog elementa. To se postiže pozivom metode Sho-
wConfirmation koja ima dva parametra: prvi sadrži podatke o poruci koju je potrebno
prikazati korisniku, dok drugi predstavlja akciju koju je potrebno pozvati/izvršiti kada
se dobije odgovor od korisnika.

• Pruža se mogućnost prikazivanja notifikacije korisniku na koju on ne mora da reaguje.


To se postiže pozivom neke od metoda ShowNotification*.

129
• Moguće je prikazati proizvoljni dialog koji sadrži dugmiće OK/Cancel, pozivom metode
ShowDialog sa parametrom ConfirmationDialog.

Ovaj servis se koristi u svim view model objektima kroz aplikaciju. Postoji dosta načina
upotrebe ovog servisa kroz aplikaciju, a upotreba ovog servisa, uglavnom, je jednostavna.
Sledi primer implementacije komande za brisanje stavke gde se servis poziva na dva mesta.

Listing 102: Implementacija komande za brisanje


protected virtual void OnDeleteCommand()
{
if (_itemsCollection.CurrentItem != null)
{
var conf = new ConfirmationMessage();

conf.Title = "Potvrda za brisanje";


conf.Message = "Da li ste sigurni da zelite da obrisete objekat sa Kljucem:" +
_itemsCollection.CurrentItem.Id;

InteractService.ShowConfirmation(conf, async message =>


{
if (!message.IsConfirmed) return;
var res = await DeleteItem(_itemsCollection.CurrentItem);
if (res) RefreshData();
});
}
else
{
InteractService.ShowNotificationInfo("Brisanje Stavke", "Nema selektovane stavke");
}
}

Ukoliko nema selektovane stavke, onda se korisniku to i prikazuje u vidu notifikacije. Uko-
liko postoji selektovana stavka, onda se pita korisnik za potvrdu brisanja. Ukoliko korisnik
potvrdi svoju akciju, nastavlja se sa brisanjem. Na kraju se osvežavaju podaci.
Navigacioni servis implementira Shell klasa, koja se nalazi u glavnom projektu. Sadrži
ContentControl komponente u koje se zatim dinamički dodaje sadržaj pozivom servisa.

10.5 Implementacija korisničkog interfejsa u aplikaciji Prodavnica


Većina stvari o implementaciji korisničkog interfejsa već je rečena u prethodnim pogla-
vljima, uz primere iz aplikacije. Medutim, nedostaje opis načina na koji je sve povezano i
kako komponente saraduju zajedno.

10.5.1 Regioni
Kao što je ranije napisano, Shell predstavlja početni i glavni view WPF aplikacije. Nje-
gov zadatak je da drži sav sadržaj koji aplikacija prezentuje i da omogući upravljanje tim
sadržajem. U tu svrhu definisani su regioni.

130
Slika 24: Definisani regioni na Shell-u

Kao što možemo da vidimo na osnovu slike 24, definisana su dva regiona: jedan Ma-
inRegion za smeštanje glavnog sadržaja aplikacije, i drugi CommandRegion za smeštanje
komandi.

10.5.2 Navigacija
Navigacija aplikacije inicijalizuje se prilikom inicijalizacije aplikacije. U glavni region
aplikacije dodaje se instanca klase WNavigation, koja sadrži kao view model implementaciju
interfejsa IServInjectNavItem, gde svi ostali moduli (kojima je potrebno pristupiti iz glavne
navigacije) registruju svoje view objekte. Registracija se vrši u metodu Initialize klase
modula i obično izgleda ovako:

Listing 103: Registrovanje view objekta na region


IServInjectNavItem navigation = _container.Resolve<IServInjectNavItem>();
UriQuery query = new UriQuery();
var navitemSelDobavlaca = new NavItemDef
{
ItemName = "Pretraga Dobavljaca",
Command =
new DelegateCommand(
() =>
_regionManager.RequestNavigate(ShellRegions.MainRegion,
KalkulacijaViews.WDobavljacSel + query)),
BackgroundColor = Colors.DodgerBlue,
ImageSource = ImageSourcePath.Dobavljaci
};
navigation.RegisterViewForRegion(navitemSelDobavlaca);

Iz prethodnog koda možemo da vidimo, da je prvi korak preuzimanje reference na obje-


kat koji implementira interfejs IServInjectNavItem upotrebom kontejnera. Onda se kreira
instanca klase NavItemDef, gde se pored ostalih parametra nalazi i komanda, čija se akcija

131
poziva kada korisnik želi da izvrši navigaciju. Implementacija komande sastoji se od jed-
nostavnog poziva menažera regiona kome se upućuje zahtev za navigaciju na željeni view.
Nakon inicijalizacije svih view objekata glavni navigacioni prozor izgleda kao na slici 25.

Slika 25: Glavni navigacioni view aplikacije

Kako je svaki modul inicijalizovao svoje view objekte, prikazane su sve stavke na glavnom
meniju navigacije. Svaki modul registruje svoje view objekte koji se uključuju u glavnu
navigaciju, ovo je izuzetno fleksibilno, jer omogućuje dodavanje novih elemenata navigacije
iz novog modula bez ikakve modifikacije koda za glavnu navigaciju. Takode, ukoliko se
modul ne inicijalizuje, tj. zakomentariše se inicijalizacija nekog *.Presentation modula, neće
se desiti greška već samo stavke tog modula neće biti prikazane.
Nakon što se inicijalizuje navigacija, view model objekti upravljaju navigacijom u apli-
kaciji na način kao što je objašnjeno u prethodnim poglavljima.

132
11 Zaključak
Prilikom pisanja rada i izrade projekta, opisali smo više tema, i sve su se bavile načinom
razvoja odredenog dela aplikacije. Iako nisu svi koncepti u potpunosti opisani, to je bilo
dovoljno za razvoj jedne potpuno modularne aplikacije, što nam je i bio krajnji cilj. S obzirom
na to da smo u radu izložili osnovne koncepte razvoja modularnih aplikacija, smatramo da
smo ovim radom postavili dobru osnovu za dalja, tj. buduća istraživanja.
DDD filozofija izrade aplikacije je, verovatno, najbolji način za implementaciju kom-
pleksnih biznis problema, budući da ona omogućava čisto razdvajanje koncepata u različite
slojeve aplikacije i pruža način za izdvajanje biznis logike aplikacije u posebnu celinu. Čak,
i kada neki projekat ne prati DDD, upotreba paterna i koncepata, koji definišu DDD, mogu
unaprediti aplikaciju.
Razdvajanje aplikacije na komandnu stranu i stranu upita olakšalo nam je, u velikoj meri,
implementiranje, a uz to, učinio je kôd čitljivijim. Kao što smo videli iz rada, CQRS je mali
patern koji ima veliki uticaj na arhitekturu i način izrade aplikacije. U startu potrebno je više
rada prilikom izrade aplikacije, ali kako se vremenom usložavaju biznis zahtevi, održavanje
aplikacije postaje lakše (u odnosu na aplikaciju koja nije razdvojena na komande i upite).
MVVM se pokazao kao dobar patern za implementiranje korisničkog interfejsa u kombi-
naciji sa WPF tehnologijom, budući da omogućava izradu korisničkog interfejsa uz pomoć
XAML koda (koji umnogome olakšava kreiranje korisničkog interfejsa) i omogućava čisto
razdvajanje prezentacije od prezentacione logike (time omogućava da dizajneri i programeri
rade paralelno na istom projektu).
Najbitnija odlika aplikacije je to što je modularna. Uprkos početnim poteškoćama u
implementiranju modularne aplikacije, možemo da zaključimo da modularnost pruža mnogo
koristi. Prism biblioteka veoma je koristan alat, koji pomaže prilikom izrade modularnih
aplikacija. Nalazimo da je njena pogodnost u tome što sadrži implementaciju najbitnih
paterna potrebnih za izradu modularnih aplikacija.
Na kraju, nijedna arhitektura aplikacije nije savršena arhitektura. Ona zavisi od više
faktora: problema koji se rešava, zahteva koji su izloženi, ljudi koji rade na projektu itd. Nije
praktično implementirati sve slojeve za prostu aplikaciju, koja obavlja jednostavan zadatak,
budući da to zahteva dosta vremena, a dobit je mala. Sa druge strane, kod velikih aplikacija,
sa kompleksnom biznis logikom i većim brojem ljudi koji rade na razvoju aplikacije, poželjno
je imati definisane slojeve i module kako bi timovi mogli da se specijalizuju za odredeni deo
aplikacije i kako bi se kompleksna aplikacija razbila na manje delove. Sveukupno gledano,
time bismo omogućili efikasan razvoj aplikacije.

133
Literatura
[1] Eric Evans, Domain-driven Design - Tackling Complexity in the Heart of Software,
Addison Wesley, 2003.

[2] Martin Fowler, David Rice, Matthew Foemmel, Edward Hieatt, Robert Mee, Randy
Stafford, Patterns of Enterprise Application Architecture, Addison Wesley, 2002.

[3] Scott Millet, Nick Tune, Patterns, Principles, and Practices of Domain-Driven Design,
John Wiley & Sons, Inc., Indianapolis, 2015.

[4] Dino Esposito, Andrea Saltarello, Microsoft .NET: Architecting Applications for the
Enterprise, Second Edition, Microsoft Press Redmond, 2015.

[5] Vaughn Vernon, Implementing Domain-Driven Design, Addison-Wesley, 2013.

[6] Tim McCarthy, .NET Domain-Driven Design with C# Problem V Design V Solution,
Wiley Publishing, Inc., Indianapolis, 2008.

[7] Jimmy Nilsson, Applying Domain-Driven Design and Patterns: With Examples in C#
and .NET, Addison Wesley Professional, 2006.

[8] Adam Nathan, Daniel Lehenbauer, Windows Presentation Foundation UNLEASHED,


Sams Publishing, Indianapolis, 2007.

[9] Mark Seemann, Dependency Injection in .NET, Manning Publications Co., Shelter
Island NY, 2012.

[10] Abel Avram, Domain-Driven Design Quickly, C4Media Inc., USA, 2006.

[11] Greg Young, Julian Dominguez, Grigori Melnik, Fernando Simonazzi, Mani Subra-
manian, Dominic Betts, Exploring CQRS and Event Sourcing - A journey into high
scalability, availability, and maintainability with Windows Azure, Microsoft, 2012.

[12] Greg Young, CQRS Documents, https://cqrs.files.wordpress.com/2010/


11/cqrs_documents.pdf, 2010.

[13] Martin Fowler, CQRS, http://martinfowler.com/bliki/CQRS.html, 2011.

[14] Microsoft patterns & practices, Developer’s Guide to Microsoft Prism - Building MVVM
and Modular Applications with WPF and Silverlight, Microsoft Press, Redmond, 2011.

[15] Kirk Knoernschild, Java Application Arhitecture - Modularity Patterns with Examples
Using OSGi, Pearson Education, Inc, US, 2012.

[16] Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, Design Patterns - Ele-
ments of Reusable Object-Oriented Software, Addison-Wesley, US, 1995.

134
[17] Paul Rayner, Aggregates & Entities in Domain-
Driven Design, http://thepaulrayner.com/blog/
aggregates-and-entities-in-domain-driven-design/, 2015.

[18] Jeffrey Palermo, The fallacy of the always-valid entity, http://jeffreypalermo.


com/blog/the-fallacy-of-the-always-valid-entity/, 2009.

[19] Jimmy Bogart, Validation in a DDD world, https://lostechies.com/


jimmybogard/2009/02/15/validation-in-a-ddd-world/, 2009.

[20] Halil Ibrahim Kalkan, Dependency Injection and Unit Of Work using Cas-
tle Windsor and NHibernate, http://www.codeproject.com/Articles/
543810/Dependency-Injection-and-Unit-Of-Work-using-Castle#
HowToOpenConn, 2014.

[21] Leszek Koc, Generic Repository Framework (Generic Unit of


Work), http://www.codeproject.com/Articles/766609/
Generic-Repository-Framework-Generic-Unit-of-Work, 2014.

[22] Shiju Varghese, Commands, Command Handlers and Com-


mand Dispatcher, https://weblogs.asp.net/shijuvarghese/
cqrs-commands-command-handlers-and-command-dispatcher, 2011.

[23] Mateusz Stasch, CQRS - Simple architecture, https://www.


future-processing.pl/blog/cqrs-simple-architecture/, 2015.

[24] Steven van Deursen, Meanwhile... on the command side of my architecture, https:
//www.cuttingedge.it/blogs/steven/pivot/entry.php?id=91, 2011.

[25] Steven van Deursen, Meanwhile... on the query side of my architecture, https://www.
cuttingedge.it/blogs/steven/pivot/entry.php?id=92, 2011.

[26] Richard Miller, When Dependency Injection goes Wrong, http://richardmiller.


co.uk/2011/05/19/when-dependency-injection-goes-wrong/, 2011.

[27] Jimmy Bogard, A better domain events pattern, https://lostechies.com/


jimmybogard/2014/05/13/a-better-domain-events-pattern/, 2014.

[28] Udi Dahan, , Domain Events Salvation, http://udidahan.com/2009/06/14/


domain-events-salvation/, 2009.

[29] Jonas Gauffin, Repository pattern, done right, http://www.codeproject.com/


Articles/526874/Repository-pattern-done-right, 2015.

[30] Sun Microsystems, The Benefits of Modular Programming, https://netbeans.


org/project_downloads/usersguide/rcp-book-ch2.pdf, 2007.

135
Biografija
Marko Milenković je roden 6. maja 1988. godine u Leskovcu. Osnovnu školu “Siniša
Janić” u Vlasotincu završio je 2003. godine. Tehničku školu “Milentije Popović” u Crnoj
Travi (smer ekonomski tehničar) završio je 2007. godine, gde je pokazao veliko interesovanje
za informatičke nauke.
Školske 2007/2008. godine upisao je osnovne studije informatike na Departmanu za
informatiku, Prirodno-matematičkog fakulteta u Nišu, koje je završio školske 2010/2011.
godine sa prosečnom ocenom 8,32. Školske 2011/2012. godine upisao je master akademske
studije na Departmanu za informatiku, Prirodno-matematičkog fakulteta u Nišu, studijskog
programa informatike. Master akademske studije završio je školske 2015/2016. godine sa
prosečnom ocenom 9,63.

136
Прилог 5/1
ПРИРОДНO - MАТЕМАТИЧКИ ФАКУЛТЕТ
НИШ

КЉУЧНА ДОКУМЕНТАЦИЈСКА ИНФОРМАЦИЈА

Редни број, РБР:


Идентификациони број, ИБР:
Тип документације, ТД: монографска
Тип записа, ТЗ: текстуални / графички
Врста рада, ВР: мастер рад
Аутор, АУ: Марко Миленковић
Ментор, МН: Марко Петковић
Наслов рада, НР:
RAZVOJ МОДУЛАРНИХ АПЛИКАЦИЈА У .NET
ОКРУЖЕЊУ
Језик публикације, ЈП: српски
Језик извода, ЈИ: енглески
Земља публиковања, ЗП: Р. Србија
Уже географско подручје, УГП: Р. Србија
Година, ГО: 2016.
Издавач, ИЗ: ауторски репринт
Место и адреса, МА: Ниш, Вишеградска 33.
Физички опис рада, ФО: 136 стр. ; граф. прикази
(поглавља/страна/ цитата/табела/слика/графика/прилога)

Научна област, НО: Рачунарске науке


Научна дисциплина, НД: Софтверско инжињерство
Предметна одредница/Кључне речи, ПО: DDD, CQRS, MVVM, Prism, Unity, модуларне
апликације
УДК 004.728
004.738.5
Чува се, ЧУ: библиотека
Важна напомена, ВН:
Извод, ИЗ: У раду Razvoj модуларних апликација у .NET окружењу
описана је архитектура комплексних апликација кроз
израду практичног пројекта. Након увођења у проблем и
представљања захтева које апликација мора да задовољи,
представљени су основни концепти дизајна доменом
диригованих (DDD) апликација. У овом одељку описана је
архитектура DDD апликација представљањем слојева
апликација и њихове међусобне сарадње. Даље, описани су
патерни за моделирање домена, као и патерни за
управљање животним веком објеката у домену.
У раду је описан начин имплементације апликације
праћењем DDD-а и употребом CQRS патерна.
Представљене су базне класе свих основних објеката, и
пример њихове имплементације.
У наставку рада представљен је слој приступа извору
података. Описани су патерни који чине слој приступа
подацима апликације, њихова међусобна сарадња, као и
њихова имплементација. Како је апликација подељена на
два дела (командну страну и страну упита), описан је и
начин имплементације стране упита.
Након тога, описан је презентациони слој, и начин на који
је он имплементиран, у којем главну улогу игра MVVM
патерн.
Након дефинисања основних слојева, пажња је усмерена на
модуларност апликације, уз истицања предности
модуларних у односу на монолитне апликације. Том
приликом, описана је Prism библиотека и начин њене
употребе за израду модуларних апликација, кроз примере
из пројекта.
До краја овог рада описани су начини на које је апликација
подељена у модуле, могућности да се ти модули одрже
независним, затим кориснички интерфејс (уз објашњења
имплементације навигације и интеракције са корисником).
Датум прихватања теме, ДП:
06.10.2015.

Датум одбране, ДО:


Чланови комисије, КО: Председник:
Члан:
Члан, ментор:

Образац Q4.09.13 - Издање 1

Q4.16.01 - Izdawe 1
Прилог 5/2
ПРИРОДНО - МАТЕМАТИЧКИ ФАКУЛТЕТ
НИШ

KEY WORDS DOCUMENTATION

Accession number, ANO:


Identification number, INO:
Document type, DT: monograph
Type of record, TR: textual / graphic
Contents code, CC: university degree thesis (master thesis)
Author, AU: Marko Milenković
Mentor, MN: Marko Petković
Title, TI:
DEVELOPMENT OF MODULAR APPLICATIONS IN .NET
ENVIRONMENT
Language of text, LT: Serbian
Language of abstract, LA: English
Country of publication, CP: Republic of Serbia
Locality of publication, LP: Serbia
Publication year, PY: 2016
Publisher, PB: author’s reprint
Publication place, PP: Niš, Višegradska 33.
Physical description, PD: 136 p. ; graphic representations
(chapters/pages/ref./tables/pictures/graphs/appendixes)

Scientific field, SF: Computer science


Scientific discipline, SD: Software engineering
Subject/Key words, S/KW: DDD, CQRS, MVVM, Prism, Unity, modular applications
UC 004.728
004.738.5
Holding data, HD: library

Note, N:
Abstract, AB: In the Master Thesis “Development of modular applications in
.NET environment'' the architecture of complex applications
has been described by working on a practical project. After the
introduction of the problem and the presentation of the
requests that need to be met by the application, the basic
concepts of the domain driven design (DDD) application have
been presented. In this section the architecture of the DDD
applications has been described by showing the layers of the
applications and their mutual cooperation. Moreover, design
patterns for domain modelling have been described, as well as
the patterns for the governing the lifespan of the objects in the
domain.
In this thesis the way of implementing the application by
following DDD and the usage of CQRS patterns has been
depicted. Also, presented are the base classes of all basic
objects, including the example of their implementation.
In the continuation of the thesis the layer of data access has
been shown. The patterns which make up the data access
layer of the application have been described, including their
mutual cooperation and implementation. Considering the fact
that the application has been divided into two sides (the
command side and the query side), the way of implementing
the query side has also been described.
In addition, the presentation layer has been shown as well as
the way it was implemented, where the main role belongs to
the MVVM pattern.
After defining the basic layers, attention has been directed
towards the modularity of the application, with the emphasis on
the advantages of the modular as compared to monolith
applications. Here, Prism library was described, as well as the
way in which it can be used to produce modular applications by
using the examples from the project.
Furthermore, in this thesis there will be the descriptions of the
ways in which the application has been divided into modules,
the possibilities of these modules being maintained
independent, the user interface (along with the explanations of
the implementation of the navigation and interaction with the
user).

Accepted by the Scientific Board on, ASB: 06.10.2015.


Defended on, DE:
Defended Board, DB: President:
Member:
Member, Mentor:

Образац Q4.09.13 - Издање 1

Q4.16.01 - Izdawe 1

You might also like