Professional Documents
Culture Documents
Master rad
Student: Mentor:
Marko Milenković Prof. dr Marko Petković
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
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
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
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 faktura (unos faktura, pregled unetih faktura i izmena faktura, pre nego
što budu kalkulisane);
• 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...).
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.
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.
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.
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.
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.
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.
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.
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:
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.
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.
18
1. Održavanje integriteta kroz životni vek.
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.
• 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.
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:
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:
• Konzistentnost
• Skladištenje podataka
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.
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.
• 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:
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.
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.
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.
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.
33
Listing 3: Metode za poredenje vrednosnih objekata
public abstract class BaseValueObject<T> where T : BaseValueObject<T>
{
if (ReferenceEquals(valueObject, null))
{
return false;
}
if (!HasValue && !valueObject.HasValue) return true;
if (!HasValue || !valueObject.HasValue) return false;
return EqualsCore(valueObject);
}
return a.Equals(b);
}
34
Listing 4: Konstruktor
protected BaseValueObject(bool hasValue)
{
_hasValue = hasValue;
}
Listing 5: Validacija
public bool Validate(bool checkForNull = true)
{
return GetErrorMessage(checkForNull, null) == null;
}
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.
35
Listing 6: Bazna klasa agregata
public abstract class AggregateRoot : EntityBase
{
}
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 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");
DatumFakture = datum;
DatumValute = datumValute;
DobavljacId = dobavljacId;
KorisnikId = korisnikId;
IsKalkulisana = isKalkulisana;
FakturaRabat = new Procenat(rabatProcenat);
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:
Kako za sada postoji samo jedan scenario za izmenu vrednosti fakture, dodata je posebna
metoda koja to omogućava.
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.
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.
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.
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.
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:
41
Takode, obezbeduje se interfejs za rad dispečeru dogadaja kroz kolekciju dogadaja Events i
metode ClearEvents, koja prazni listu dogadaja nakon njihove obrade.
42
handlers.Add(typeof (THandlerType));
_eventHandlers.Add(key, handlers);
}
}
43
Listing 21: Integracija sa jedinicom rada
public void Commit()
{
// Process Domain Events before transaction is started
ProcessDomainEvents();
// Persist data...
}
while (processDomainEvents)
{
var aggregatesWithEvents =
_uowPersistActions.Where(action => action.Aggregate.Events.Count > 0).ToList();
loopCnt++;
if (loopCnt == MaxDomainEventsDepth)
throw new Exception("Maximum number of cycles " +
"for domain events reached: LoopCount:" + loopCnt);
}
}
44
5 Implementacija aplikacionog sloja
5.1 Komandna strana
Infrastruktura komande sastoji se iz tri osnovne komponente:
• Hendleri predstavljaju objekte koji sadrže biznis logiku potrebnu za obradu komandi.
5.1.1 Komande
Najpre, potrebno je kreirati Command interfejs:
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:
Svaki hendler mora da sadrži metod Handle, koji prihvata komandu kao parametar. Sledi
primer hendlera komande za brisanje fakture:
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:
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);
}
}
}
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.
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.
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.
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.
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.
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.
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.
if (handler == null)
{
throw new QueryHandlerNotFoundException(typeof (TQuery));
}
return handler.Handle(query);
}
}
52
Listing 36: Primer upita
public class GetFakturaByIdQuery : IQuery<FakturaEdit>
{
public long FakturaId { get; set; }
}
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.
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.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
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.
57
repozitorijuma, gde svi repozitorijumi imaju isti interfejs i dodaju svoje prilagodene metode
za rad sa agregatima.
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);
59
Listing 39: Implementacija interfejsa IUnitOfWorkRep
protected abstract TDto CreateDto(TAggregate item);
60
{
return _proizvodFactory.BuildAggregate(dtoObject);
}
return res;
}
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.
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.
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:
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.
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:
64
public void RegisterRemoved(AggregateRoot entity,
IUnitOfWorkRep repository)
{
var delTuple = new Tuple<,>(entity.Id, entity.GetType());
if (_cachedAggregates.ContainsKey(delTuple)) return;
_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(); }
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:
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.
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.
68
IDbCommand command = CreateCommand();
PrepareInsert(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:
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.
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);
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.
command.Prepare();
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.
command.Prepare();
72
AddParameter(command, FieldNames.Naziv, item.Naziv);
AddParameter(command, FieldNames.BarKod, item.BarKod);
AddParameter(command, FieldNames.NetoKolicina, item.NetoKolicina);
...
}
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.
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.
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;
}
}
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.
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.
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;
}
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.
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.
80
Dijagram 15 prikazuje MVVM klase i njihovu medusobnu saradnju:
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.
83
Listing 68: Sadržaj klase BasePropertyChanged
public abstract class BasePropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[Conditional("Debug")]
public void VerifyPropertyName(string propertyName)
{
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
{
string msg = "Invalid property name: " + propertyName;
Debug.Fail(msg);
}
}
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.
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.
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:
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.
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.
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.
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.
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.
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.
Prethodni listing prikazuje generalni sadržaj klase bez ulaska u implementacione detalje
93
metoda. Sledi opis implementacije komandi iz ove klase:
94
jednom, potvrdio svoju nameru. Kada se to dogodi, poziva se apstraktna metoda za brisanje
elementa i osvežava se prikaz.
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.
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;
}
}
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;
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.
97
public void PopulateForEditEntity(T entity)
{
PopulateEditEntityForm(entity);
EntityId = entity.Id.ToString();
CurrEditState = EditState.Unchanged;
}
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.
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.
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.
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.
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.
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.
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.
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.
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.
• 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.
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:
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:
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.
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.
Kako je DIP princip, on ne nudi rešenje problema. Ukoliko želimo da znamo kako rešiti
problem, moramo da razmotrimo IoC.
• 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.
• Ubrizgavanje kroz seter metoda. Ovo je još jedan tip DI tehnike gde prosledujemo
zavisnosti kroz seter umesto kroz konstruktor.
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.
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.
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:
114
• RepositoryModule: implementira interfejse repozitorijuma. Referencira projekat
DomainModel i implementira interfejse repozitorijuma.
• 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
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:
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 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.
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.
• 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.
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.
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.
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.
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.
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.
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:
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.
125
Listing 96: Implementacija metode IsNavigationTarget
public virtual bool IsNavigationTarget(NavigationContext navigationContext)
{
return true;
}
CommandInjector.AddViewToRegion(_addCommandVm);
CommandInjector.AddViewToRegion(_editCommandVm);
CommandInjector.AddViewToRegion(_deleteCommandVm);
CommandInjector.AddViewToRegion(_cvsCommandVm);
RefreshData();
}
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.
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);
}
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.
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);
}
• 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.
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.
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.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:
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.
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.
[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.
[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.
[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.
[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.
[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.
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АТЕМАТИЧКИ ФАКУЛТЕТ
НИШ
Q4.16.01 - Izdawe 1
Прилог 5/2
ПРИРОДНО - МАТЕМАТИЧКИ ФАКУЛТЕТ
НИШ
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).
Q4.16.01 - Izdawe 1