D

DCI – O paradigma Lean pentru Domain Layer

Cand vorbim despre arhitectura software se succed rapid in minte cel putin una din urmatoarele abordari: Arhitectura Hexagonala, Arhitectura Ceapă (plangi cand ti se arata), Porturi si Adaptoare. Toate presupun o separare a Domain Layer-ului (DL) astfel incat acesta sa nu fie poluat cu termeni externi domeniului. Lucrul asta este realilzat in general urmarindu-se ca Domain Layer-ul sa nu depinda de straturile exterioare ci doar de abstractizari ale acestora.
Obtinand un Domain Layer curat avem posibilitatea implementarii cu usurinta a unei abordari precum DDD (Domain Driven Design). DDD este un set de pattern-uri care permite dezvoltarea de sisteme complexe printr-o concentrare asupra business domain-ului (si a termenilor specifici).
Pe baza acestuia se creeaza un model care evolueaza pentru a aduce valoare in procesele de business. DDD are meritul de a stimula o colaborare creativa intre expertii din domeniu si expertii tehnici care proiecteaza si implemementeaza sistemul. Inainte de a incheia scurta descriere a DDD propun sa aruncam o privire asupra unei implementari tipice.

 
Putem observa elemente specifice DDD precum AS (Serviciile de Aplicatie) si DS (Serviciile de Domeniu) care sunt de fapt clase stateless ce pot opera peste DE (entitatile de domeniu). Avem o separare clara a responsabilitatilor: logica de business e prezenta in DS iar interactiunile cu lumea exterioara (<<services>>) sunt prezente in AS. O caracteristica distinctiva a AS-urilor este ca ele contin logica de orchestrare implementand use-case-urile domeniului.
Un aspect important este acela ca logica de domeniu prezenta in DS, nu poate fi atasata in mod natural DE. Acest lucru se intampla datorita faptului ca implementand diverse use-case-uri in care sunt implicate aceleasi DE, am polua acele DE cu metode ce tin de aspecte diferite ale domeniului, fapt care le-ar afecta coeziunea si astfel mantenabilitatea / testabilitatea.
Mai mult decat atat, nu exista o abordare standardizata privind momentul in care trebuie creeat un DS si daca e bine ca acesta sa fie partajat intre AS-uri.  Situatia nu este atat de rea avand in vedere ca exista o paradigma care complementeaza si simplifica DDD.
DCI este aceasta paradigma care ofera stabilitate domeniului, necesitand mai putin efort pentru a-l mentine intr-o forma clara si curata.  Pattern-ul DCI (Date – Context – Interactiune) a fost conceput de Trygve Reenskaug. Tot el a formulat pattern-ul MVC in timpul unei vizite la Xerox PARC in 1979, lucru care ma face sa cred ca DCI merita o atentie deosebita.
Cea mai buna analogie pentru a explica DCI este aceea cu o piesa de teatru:
• D – Date = clasele din Model reprezinta actori care joaca in piesa. In DDD sunt reprezentate de catre DE.
• C – Context = scena unde are loc interpretarea piesei. Aici putem vorbi si despre pregatirea scenei si casting : ce Roluri interpreteaza actorii. Comparand cu DDD, contextul este echivalentul AS.
• I – Interactiune = scripturile sau replicile pe care le au diversele roluri. In DDD interactiunile sunt implementate in DS.
Continuand analogia, programatorul este autorul piesei. Cand implementam un use-case, actorii (DE) sunt distribuiti in roluri prin intermediul interfetelor de rol (marker) (RI), fiecare rol avand un set de replici care trebuiesc jucate aka Metode de rol (RM). Mai mult, programatorul controleaza coreografia piesei prin implementarea metodei use-case in cadrul clasei Context.

 
Sa incercam modelarea DCI intr-un limbaj mainstream precum C# ceea ce reprezinta o provocare acesta neavand suport natural pentru mixins sau traits ( un feature de limbaj specific Scala care permite atasarea de logica si stare unor clase exstente la runtime).
In DCI, pe langa mecanismul normal de apel polimorfic de metode pe instante ale claselor aplicam si un mecanism alternativ de apel direct avand in vedere ca putem asocia metode de rol statice (RM) unor entitati de domeniu (DE) in cadrul unui context prin intermediul interfetelor de rol (RI).
Astfel ne bazam mai putin pe invocarea polimorfica atunci cand executam logica de domeniu si mai mult pe invocarea directa a metodelor de rol (RM) atasate DE intr-un context dat.
Acest lucru este realizat cu ajutorul interfetelor de rol sau marker (RI) implementate de entitatile de domeniu (DE) si cu ajutorul metodelor de extensie un feature de limbaj prezent in C# care permite atasarea de metode statice unor clase existente fara modificarea definitiei acestora. Metodele de extensie reprezinta unul din elementele constructive ale LINQ in .NET.
Aceste metode de extensie constituie metodele noastre de rol (RM) si pot fi accesate pe instantele clasei de domeniu (DE) care implementeaza anumite interfete marker de rol (RI) doar daca namespace-ul sau clasa statica in care sunt declarate RM aferente acelei RI sunt importate. Acest fapt ne permite sa controlam setul de metode de rol active in cadrul unui anumit use-case sau Context.
Asadar, intr-o clasa Context, importam namespace-urile si clasele statice care contin metodele de rol aferente contextului si rolurilor jucate de entitatile participante in cadrul acestuia. Odata ce am setat contextul, putem implementa use-case-urile orchestrand entitatile de domeniu (DE) invocandu-le metodele de rol (RM).
Observam necesitatea de a conveni asupra unei structuri a contextelor, rolurilor si namespace-urilor ce contin metodele de rol pentru a putea intelege si intretine logica de business.
Sa luam exemplul unui transfer bancar pentru a intelege mai bine procesul de gandire al programatorului DCI:

  • Care este contextul? Transfer bancar.
  • Cine initiaza use-case-ul ? Titularul contului.
  • Care sunt actorii? Contul bancar.
  • Care sunt rolurile? Contul bancar va juca doua roluri: Cont sursa si Cont destinatie.
  • Care sunt scripturile? Contul sursa interpreteaza scriptul “Retragere”. Contul destinatie interpreteaza rolul “Depunere”.
  • Care este use-case-ul? Transfer fonduri.

Separarea responsabilitatilor in DCI este una granulara si corecta, grupand logica de business per use-case spre deosebire de DDD / OOP unde DE si DS deseori acumuleaza logica aferenta mai multor use-case-uri.
La nivelul arhitecturii generale a DL, DCI are rolul profund de a separa lucrurile care au frecvente si motive de schimbare diferite. Astfel DE reprezinta zona cea mai stabila urmata in ordine de RM si Context. Cele mai frecvente modificari le avem in logica de orchestrare a use-case-urilor la nivel de Context. Use-case-urile se schimba frecvent pentru ca stakeholderii modifica cerintele frecvent.
DCI este un tool util care poate fi adaugat in trusa oricarei echipe, in special pentru bonusul de a oferi o cale usoara de a incorpora concepte de programare functionala in cadrul DL. Implementarea DCI intr-un limbaj care nu ofera functionalitati de tipul traits sau mix-ins este dificila dar nu imposibila.
Inchei indreptandu-va catre un exemplu de implemetare DCI pe care l-am aplicat cu success in proiecte : https://github.com/CosminSontu/DCI-DomainLayerSample.git
Cateva resurse suplimentare:
http://www.artima.com/articles/dci_vision.html
http://folk.uio.no/trygver/2012/DCIExecutionModel-2.1.pdf

CategoriesNumarul 5

Leave a Reply

Your email address will not be published. Required fields are marked *