Capitolul 1. Concepte și șabloane care au influențat dezvoltarea aplicației
În ultimii ani, industria software a înregistrat un număr impresionant de avansuri și eșecuri. Indiferent de situație, au învățat și au dezvoltat idei care uneori au ajuns șabloane, alteori metodologii în toată regula sau sfaturi utile. În acest capitol voi trece în revistă cele mai relevante pentru mine și care au dus la dezvoltarea aplicației în forma ei curentă.
Există o mulțime de cuvinte precum: TDD1, BDD2, Agile3, Lean4, DI5, IOC6 etc. pe care toată industria producătoare de software le vehiculează și uneori, impresia mea, le cam „aruncă” clienților ca fiind soluția tuturor problemelor. Pentru firme relativ noi, conceptele sunt mai mult marketing decât un proces rafinat de-a lungul anilor. Totuși, încercarea de a le urma este un pas important în avansarea echipelor.
Domain Driven Design – Arhitectură dezvoltată pe baza domeniului
Domain Driven Design este un concept introdus de către Eric Evans în cartea sa intitulată “Domain-Driven Design: Tackling Complexity in the Heart of software”. Trebuie să recunosc că am fost foarte sceptic în ce privește această carte. Am intrat în contact cu foarte multă publicitate pe un număr foarte mare de blog-uri și mă așteptam să fie ceva ce urma să nu înțeleg sau să consider vorbărie goală. Spre marea mea surpindere cartea e scrisă cu mult bun simț și conține foarte multe idei și indicații utile.
Motivul pentru care am considerat cartea un succes este că pentru prima dată, idei pe care le aveam sau cel puțin le observam în dezvoltarea aplicațiilor personale au prins contur și, cel mai important, le-a fost dat un nume. În general, consider că suntem obișnuiți să dezvoltăm concepte și să le materializăm în conștient de-abia după ce avem un nume cu care să le identificăm.
Foarte succint o să le introduc și o să le explic în rândurile ce urmează și le puteți urmări în următoarea diagramă:
Fig. 1 Schema modului în care se realizează proiectarea în metodologia Domain Driven Design
Domain Driven Design se bazeză în mod fundamental pe modelarea domeniului, modelarea făcându-se prin aprofundarea și modificarea conceptelor care îl alcătuiesc.
1.1.1 The Ubiquitous Language – limbajul universal
Discuțiile cu clienții sunt în general marcate de cuvinte specifice domeniului intervievantului, domeniu pe care intervievatorul, programator, nu le cunoaște și nici nu are cum să le perceapă în adevăratul lor sens. Pentru a ușura comunicarea între aceștia sfatul este de a dezvolta împreună un limbaj cu un set de cuvinte și concepte comune.
Codul, ca să ii spun așa, este denumit în DDD7 „The Ubiquitous Language”, un limbaj comun participanților la comunicare. Mai departe, acest limbaj este introdus echipei de dezvoltare și semnificațiile fiecărui concept sunt explicate și dezbătute. În felul acesta echipa și clientul folosesc același vocabular, ceea ce face asimilarea nevoilor mult mai ușoară.
1.1.2 Layer architecture – arhitectură multistratificată
Fig. 2 Arhitectura unei aplicații informatice
În momentul în care dezvoltăm soft, mare parte din aplicție nu e în directă legătură cu domeniul, ci implică cod care alcătuiește o infrastructură sau care servește aplicația însăși. Domeniul este în general unul foarte restrâns relativ la alte layere, precum cel de servicii sau interfața cu utilizatorul.
Într-un program orientat obiect, codul pentru UI8, bază de date, cât și alt cod folosit pentru suport este adesea adăugat claselor care alcătuiesc logica aplicației. Asta face foarte dificilă întreținerea și stabilirea unor direcții clare de extindere a programului.
Partiționarea software-ului pe diverse straturi devine așadar obligatorie. Fiecărui strat este necesară alocarea unei responsabilități generale și este necesar ca cei implicați în dezvoltarea proiectului să țină cont de aceste respobilități și să le respecte.
O arhitectură comună propusă nu doar în această carte, ci într-un număr destul de mare de lucrări este următoarea:
User Interface (Presentation Layer) |
Responsabilitatea este prezentarea informației utilizatorului și interpretarea comenzilor acestuia.
| Application Layer |
Aceasta este un strat foarte subțire care coordonează activitatea aplicației. Nu conține logică legată de business. Nu reține starea obiectelor care se ocupă de business, dar poate să rețină starea unui anumit task care este rulat.
| Domain Layer |
Acest strat conține informații despre domeniu. Este inima software-ului dezvoltat. Stările obiectelor din logica aplicației sunt reținute aici. Persistența datelor și a stării obiectelor este delegată infrastructurii.
| Infrastructure Layer |
Acest strat deține suport pentru toate celelalte straturi. E o librărie de funcționalități precum comunicare dintre celelate straturi, implementeză persistența obiectelor și conține suport pentru interfața cu utilizatorul.
|
Este foarte importantă împărțirea aplicației în aceste straturi și stabilirea de reguli clare de comunicare între acestea. Șansa de a se ajunge la un cod cu coeziuni puternice este foarte posibilă și practic greu de evitat în arhitecturi care nu urmează un set de reguli precise.
1.1.3 Entități
Pentru mine conceptul părea unul banal la prima vedere, însă, o dată ce am trecut de suprafața noțiunii, am descoperit un stil mult mai clar de a separa responsabilitatea pe care aceste obiecte și clasele care operează cu ele o au referitor la ele.
Aceasta este o categorie de obiecte ce au o identitate care rămâne constantă pe tot parcursul aplicației. Nu atributele acestor obiecte sunt importante, ci continuitatea și identitate pe care acestea o păstrează în cadrul sistemului și dincolo de limitele acestuia.
Limbajele de programare orientate obiect păstrează instanțele acestora în memorie și le asociază o adresă de memorie. Această referință este constantă și unică pentru fiecare instanță la orice moment din timp, dar nu garantează că va fi la fel pentru o perioadă nedefinită. Obiectele sunt mutate în și din memorie tot timpul, sau sunt transmise prin rețea altor calculatoare care le vor aloca alte locații. Este evident că această referință nu reprezintă identitatea despre care discutam inițial.
Unicitatea se obține dintr-un atribut sau set de atribute pe care aplicația le stabilește o dată și apoi garantează că nici un alt obiect, sau instanță a acestuia nu o va modifica. Discut evident despre câmpuri numerice, șir de caractere, sau GUID9 care vor fi persistate și, ori de câte ori instanța obiectului este salvată ulterior sau reîncărcată în memorie, rămâne nemodificată.
1.1.3 Obiecte Valori
Am discutat despre Entități și importanța responsabilității acestora în domeniul unei aplicații, dar apare evident întrebarea dacă toate obiectele din domeniu trebuie să dețină o identitate?
Răspunsul este evident nu, deși tentația ar fi mare, întrucât entitățile pot fi urmărite și gestionate exact, dar nu este aceeași situație pentru toate obiectele care compun logica aplicației. Decizia de a da identitate unui obiect este una care trebuie analizată atent. Este foarte posibil să nu fim interesați de identitate unui anumit obiect, ci doar de atributele acestuia.
Un obiect care este utilizat pentru a descrie anumite aspecte dintr-un domeniu și care nu deține o identitate este numit Obiect Valoare.
Neavând o identiate aceste obiecte pot fi create și distruse fără prea mari probleme. Este recomandabil ca aceste obiecte să fie imutabile, să le fie stabilite atributele printr-un constructor și să nu aibe nimeni posibilitatea de a schimba atributele setate. Fiind imutabile, neavând identitate, ele pot fi distribuite prin diverse subsisteme fără a avea grija că starea obiectului va fi alterată.
1.1.4 Servicii
Descoperim uneori anumite funcționalități care ar fi comune mai multor obiecte sau apar aspecte pe care nu le putem asocia ușor unui anumit obiect. Când se dezvoltă limbajul universal observăm că substantivele sunt deosebit de ușor de atașat obiectelor domeniului, iar verbele atașate substantivului ar putea reprezenta acțiuni pe care le poate îndeplini. Dar sunt acțiuni care, deși aparțin obiectului, l-ar „corupe” dacă ar fi adăugate. Prin „corupt” mă refer la acele funcții care îi oferă responsabilități adiționale direcției pentru care a fost creat inițial.
Când asemenea funcționalități sunt descoperite cea mai bună soluție ar fi să le declarăm ca fiind Servicii. Aceste obiecte nu au o stare internă, ci au scopul de a îmbogăți funcționalitatea domeniului. Un serviciu poate grupa funcționalitate specifică atât entităților, cât și obiectelor valoare. Incorporarea funcționalității într-o entitate sau obiect valoare ar duce la obturarea motivului pentru care acel obiect există și a fost conceput ca făcând parte din domeniu în primul rând.
Serviciile sunt de fapt interfețe care oferă operații. Sunt specifice mai mult framework-urilor tehnice, dar pot fi folosite și în stratul modelului. Un serviciu este în general un punct de contact pentru mai multe obiecte.
Există trei caracteristici pe care un obiect trebuie să le îndeplinească:
-
Operația efectuată de un Serviciu referă un concept din domeniu care nu poate fi atașat natural unei Entități sau Obiect Valoare.
-
Operația efectuată implică și alte obiecte din domeniu
-
Operația nu are o stare
Când un proces sau transfomare din domeniu nu este responsabilitatea naturală a unei Entități sau Obiect Valoare, atunci acel proces, respectiv acea transformare ar trebui extrasă într-o interfață declarată ca și Serviciu.
1.1.5 Module
Pentru aplicații complexe se atinge un punct când este dificil să se mai vorbească despre aplicație ca un întreg de sine stătător, și înțelegerea realțiilor dintre elementele domeniului devine dificilă. Din acestă perspectivă este indicată gruparea domeniului în module. Modulele sunt folosite ca și metodă de a grupa concepte comune și activitățile acestora pentru a reduce complexitatea.
Conceptul este comun multor aplicații și este ușor de stabilit scopul unei aplicații privind modulele din care este compusă și apoi fiecare modul în parte și a comunicării care trebuie să existe între acestea. O dată ce interacțiunile dintre modul sunt stabilite este necesară stabilirea detaliilor de organizare internă a modulului.
Alt motiv pentru întrebuințarea modulele e legată de calitatea codului produs. Este un fapt acceptat că între diversele părți ale aplicației este necesar un nivel ridicat de coeziune și o cuplare cât mai liberă. Există mai multe tipuri de coeziune, dintre cele mai frecvente amintesc coeziune în comunicare și coeziune funcțională. Coeziunea în comunicare este remarcată atunci când diverse module efectuează operații asupra acelorași date, și ca urmare a acestei relații strânse între module e logic să fie grupate împreună. Coeziunea funcțională este obținută când toate modulele funcționează ca un întreg pentru realizarea unui anumit scop bine definit. Aceasta este considerată ca fiind cea mai bună formă de coeziune.
1.1.6 Agregări
Fiecare din șabloanele discutate anterior dispută aspecte foarte specifice pe care modelarea unui domeniu o implică. Obiectele dintr-un domeniu trec printr-o serie de stări precum crearea, alocarea unei zone de memorie și folosirea lor în timpul computațiilor până în momentul în care sunt distruse. În unele cazuri aceste obiecte sunt distruse sau păstrate într-o formă sau alta de persistenţă precum bazele de date sau arhive de unde ulterior vor fi reconstruite.
Managementul ciclului de viață al unui obiect din domeniu este în sine o provocare și dacă nu e realizat corect va avea un impact negativ asupra perfomanței. În cele ce urmează voi prezenta pe scurt trei șabloane discutate în carte care pot fi aplicate acestui set de probleme.
Aggregate este un șablon din domeniu care definește apartența și granițele unui obiect.
Factory și Repository sunt celelate șabloane care au de-a face cu crearea și persistarea obiectului.
Un model poate fi format dintr-un număr mare de obiecte și, indiferent cât de mult am vrea, se ajunge în punctul în care se dezvoltă un număr foarte mare de relații între acestea. Există mai multe tipuri de asocieri. În general o asociere 1 la 1 este reprezentată printr-o referință de la un obicet la celălalt. O relație de 1 la mai mulți este una mai complexă pentru că implică un număr mare de obiecte care au legături, asocierea aceasta se face între un obiect și o colecție de alte obiecte, deși nu e întotdeauna posibil. Există și relații de tipul mai mulți la mai mulți care în general sunt bidirecționale. Numărul de asocieri este indicat să fie cât mic și în acest sens trebuie eliminate toate relațiile care nu sunt necesare sau utile domeniului.
Oricât de mult am elimina relații care nu sunt necesare, un număr relativ mare de relații tot e posibil să rămână între obiectele din domeniu.
Șablonul propus este Aggregate. O agregare este de fapt un grup de obiecte care conțin asocieri și care e privită ca o unitate în ce privește modificarea datelor. Un Agregat este privit ca o graniță ce separă obiectele interioare de cele din afara sistemului. Fiecare agregare este reprezentată obligatoriu printr-un obiect cu rol de rădăcină. Rădăcina este o Entitate și e unicul obiect accesibil din exterior. Rădăcina poate ține referințe la oricare din obiectele agregate și obiectele pot reține referințe unul la celălalt, dar un obiect din afara poate avea referință doar la obiectul rădăcină. Dacă există și alte entități în cadrul agregării, identitatea acestora este locală, adică are sens doar în cadrul grupului.
Faptul că obiectele din afara graniței demarcate pot avea referințe doar la obiectul rădăcină aduce cu sine un control mai bun asupra efectelor pe care anumite operații le au asupra stării agregării.
E posibil ca rădăcina să pasese referințe transiente în afara graniței altor obiecte cu condiția ca aceste referințe să fie șterse la sfărșitul operației. O modalitate simplă de a realiza acest efect e prin intermediul Obiectelor Valoare.
Daca obiectele unei agregări sunt persistate în baza de date doar obiectul rădăcină ar trebui încărcat printrun query. Restul se vor obține prin traversarea realațiilor.
Obiectele din interiorul unei agregări pot să conțină referințe la alte agregate.
1.1.7 Factories
Entitățile și Agregările sunt adesea mari și complexe – prea complexe pentru a fi create printr-un constructor. De fapt, construirea unei agregate prin ea însăși e contradictoriu cu ce se întâmplă adesea într-un domeniu. E ca și cum i-ai cere unei imprimante să se construiască singură.
Așadar este utilă introducerea unui alt șablon care să fie responsabil de încapsularea cunoștințelor necesare construcției acestor obiecte.
Acest proces de construcție ar fi indicat să fie atomic. În caz contrar, este posibil ca obiectele să fie doar parțial inițiate și să lase obiectele întro stare nedefintă.
Un obiect de tip Factory are în general cunoștințe foarte „intime” despre obiectul creat. Legătura dintre Factory și obiectul creat este foarte strânsă și ar putea foarte ușor să devină o slăbiciune a domeniului, dar în același timp s-ar putea să fie un factor care să dea o calitate superioare sistemului ca și întreg.
1.1.8 Repositories
Ciclul de viață al obiectului adeseori începe cu creare și se termină cu ștergerea sau persistarea acestuia. Un constructor sau un obiect Factory este responsabil de construcția obiectului. Motivul pentru care construim obiecte este, evident, de a le folosi. Într-un limbaj orientat pe obiecte relațiile dintre acestea sunt obținute prin asocieri de referințe.
Scopul unui șablon precum Repository este de a încapsula codul responsabil pentru obţinerea unei anumite referinţe. În felul acesta eliberăm obiectele din domeniu de toată infrastructura necesară obţinerii referinţelor la alte obiecte. Referinţele vor fi obţine foarte simplu din Repository și în modul acesta modelul își recapătă simplitate și claritatea.
În spate, Repository-ul poate folosi și un șablon gen Strategy, ca să îi permită într-un mod transparent să treacă eventual de la o bază de date la alta spre exemplu.
Dostları ilə paylaş: |