În țara mea, nu veți trece prin școală fără să citiți cum se plânge Faust al lui Goethe, Am studiat acum filozofia - și jurisprudența, medicina, - și chiar, din păcate! Teologie - Toate prin și prin ardor dornici! - Iată-mă acum, proastă proastă.
Din păcate, niciunul dintre eforturile și studiile sale nu a ajutat-o pe medic să perceapă ceea ce ține lumea împreună în faldurile sale intime.
Și aici suntem în IT: am studiat limbile și cadrele, bibliotecile și chiar - din păcate - IE! Totul trece prin ardor. Dar de câte ori ne-am concentrat asupra a ceea ce susține cererea împreună în pliurile sale interioare? Subiectul zilei este domeniul de afaceri.
Logica de afaceri este uneori considerată a fi unică, și este prin definiție! Dacă logica de afaceri a unei aplicații nu ar fi unică, nu va fi nevoie să scrieți o aplicație, deoarece există deja o soluție existentă (cu excepția cazului în care o aplicație există, dar nu este disponibilă). Prin urmare, mulți dezvoltatori se văd singuri ca pionieri, pentru a merge cu îndrăzneală în cazul în care nimeni nu a mers înainte. Romantismul deoparte, în timp ce logica de afaceri în sine poate fi unică într-o măsură notabilă, tehnicile de implementare a acesteia nu sunt. De aceea, au fost invitate modele inteligente de proces, cum ar fi Rational Unified Process sau Scrum, împreună cu tehnici precum iterative și cicluri de dezvoltare incrementală. Talenții arhitecților de software au elaborat și abordări pentru proiectarea software-ului; printre ei Eric Evans care a inventat termenul Domeniu de proiectare în cartea sa cu același titlu.
Dezvoltatorii merg cu îndrăzneală, unde nimeni nu a mai trecut.
Voi oferi o imagine de ansamblu asupra modului în care Designul condus de domeniu poate influența procesul de consultanță, precum și conceptele sale de bază pentru proiectarea unui model de domeniu. În cele din urmă, vom discuta cerințele de infrastructură care sunt necesare pentru a implementa un domeniu cu ușurință.
Să presupunem că sunteți un arhitect software pe o aplicație non-trivială, cu un domeniu non-trivial, ca și motorul de bază al unei mari companii logistice. Mulți oameni se alătură discuțiilor de planificare, printre care managerii de proiect, managerii de cont, marketing, consultanți și așa mai departe. Nu este nevoie de toată lumea pentru a-și face treaba (nu voi împărtăși opiniile mele asupra cărora se aplică acest lucru), dar doi oameni vor juca un rol crucial în procesul de elaborare a cerințelor: tu, arhitectul și expertul în domeniu.
A software arhitect (cel puțin în contextul afacerilor) ar trebui să aibă o înțelegere abstractă foarte bună despre modul în care funcționează procesele, modul în care sunt proiectate și optimizate.
Adevărul este că aplicațiile de afaceri vizează în principal proiectarea unor echivalente digitale eficiente și frumoase ale proceselor de afaceri. A expert domeniu ar trebui să aibă cunoștințe aprofundate despre un anumit set de procese, și anume procesele care au loc în compania logistică și care ar trebui să fie reflectate în cerere. Am constatat că consultanții de afaceri, directorul de vânzări și experții în marketing fac câteva puncte bune și valoroase de-a lungul drumului, dar atâta timp cât nu aveți pe cineva din echipa care și-a murit mâinile în anii de experiență, proiectul va eșua. De exemplu, expertul dvs. de domeniu ar trebui să cunoască lățimea rampei de încărcare din depozit și dacă există spațiu suficient pentru a instala un scaner de coduri de bare.
Deci esti tu, specializata in procese de afaceri digitale, design de software si [completați instrumentele dvs. preferate aici] și un expert în logistică cu cunoștințe despre clienții, angajații companiei și rutina zilnică. Sunt șanse, vei vorbi în scopuri încrucișate. Domain Driven Design sugerează câteva strategii care pot forma un puternic serviciu de consultanță tehnică. Iată-mi:
Suna distractiv! Să ne aruncăm în detaliu.
În fiecare industrie, fiecare grup de experți are propria terminologie. Este rafinat în fiecare companie și îmbogățit cu termenii și denumirile speciale ale companiei. Gândiți-vă la IT: când oamenii ca noi ne întâlnim pentru discuții serioase despre geek, cine altcineva ar înțelege un cuvânt? Același lucru este valabil și pentru domeniul dvs. și primul lucru pe care trebuie să-l faceți este să definiți un set de termeni. Treceți prin întregul set de procese pe care software-ul ar trebui să le reflecte și să asculte îndeaproape modul în care expertul în domeniu îl descrie. Orice termeni specifici domeniului ar trebui să fie definiți într-un mod care face dicționarele. Ar trebui să fii conștient de cuvinte care sună familiar, dar nu sunt în contextul dat. Unele companii nu au mai făcut niciodată acel loc de muncă, chiar dacă sunt valoroase pentru alte domenii.
Efectuați un glosar dedicat termenilor dvs. omniprezenți, asigurați-vă că acesta este aprobat de client și taxați pentru procesul de consultanță! Un glosar poate arăta astfel:
Un extras dintr-un glosar.Observați cum un glosar bine definit stabilește deja dependențe și asociații. Ca Ordin, care găzduiește mai multe articole. Veți avea cu siguranță cursuri pentru cei din logica dvs. de afaceri! Ta Ordin
clasa va avea probabil o metodă asemănătoare getItems ()
. Fără a lua în considerare tehnicile de programare, un glosar poate seta munca la sol pentru modelul dvs. de domeniu! În același timp, construiți o limbă care este folosită pe parcursul întregului proiect: în mails, în întâlniri și cu siguranță în cod! Codul dvs. ar trebui să reflecte domeniul; prin urmare, trebuie să fie definită în glosar. Iată o regulă de bază: ori de câte ori creați o clasă care nu este numită după o intrare în glosarul dvs., limbajul dvs. ubiquitos nu poate fi definit suficient!
Sub capacul întunericului am schimbat viziunea asupra cerințelor! În mod normal, un client descrie ce ar trebui să facă software-ul pentru a fi scris. O descriere tipică ar putea fi: "Avem nevoie de o modalitate de a adăuga note unui client și de a le tipări". Acesta este un bun punct de plecare, dar nu se concentrează pe domeniul de afaceri. Acesta introduce un fel de interfață de utilizator, funcționalitate de imprimare și chiar mai mult. Cu siguranță veți avea nevoie de acest lucru în aplicația dvs., dar nu face parte din domeniu. Domain Driven Design se concentrează pe modelarea adevăratului scop al unei aplicații: domeniul de afaceri.
Totul ar trebui să apară de acolo, odată ce domeniul este terminat. Comutatorul este următorul: nu implementați procesele și construiți un domeniu pur care să reflecte nevoile clienților din obiecte. O modalitate de vizualizare a descrierii superioare a clientului ar fi așa (primiți și setteri sunt adăugați doar atunci când este necesar pentru înțelegere):
O diagramă simplă care reflectă domeniul dorit.Acum avem un set de clase și asociații care nu fac decât să reflecte definițiile din glosarul nostru. Este acest model capabil să îndeplinească sarcinile necesare? Sigur! Veți avea nevoie de a PrinterService
și o interfață de utilizator undeva în aplicația dvs., dar trebuie doar să apucă unele date din domeniu. Nu sunt necesare acum, iar punerea sa în aplicare nu va fi decisivă pentru rezultat.
Filozofia DDD se bazează pe presupunerea că un strat de domenii atent proiectat poate efectua cu ușurință toate procesele necesare. Un model de domeniu este scalabil, deoarece nu este construit pentru a satisface o anumită sarcină, este construit pentru a reflecta un concept de afaceri. Este interschimbabil, deoarece nu este legat de niciun software specific, nici chiar de o interfață cu utilizatorul. Puteți folosi același model în scanerul de coduri de bare de pe rampa de încărcare din depozit! Așa cum vom vedea în capitolul următor, nu este chiar legat de alte componente care vă construiesc aplicația.
Într-unul din articolele mele recente, am scris despre aplicarea principiului KISS.
Într-unul din articolele mele recente, am scris despre aplicarea principiului KISS: majoritatea sistemelor funcționează cel mai bine dacă sunt păstrate simple și nu complexe. Atunci când vine vorba de implementarea unui domeniu bazat pe filosofia DDD, puteți întâlni o abordare destul de radicală în lumea modernă a cadrelor, modelelor și disciplinelor; cum ar fi, să pună în aplicare un obiect obișnuit în limbajul simplu ales de dumneavoastră. Nu există dependențe de cadru, nu există convenții de bibliotecă, nu există urme de API, nici nume fanteziste. Doar un obiect obișnuit vechi (din moment ce un concept nu este luat serios fără un nume fantezist în lumea Java, au unul acolo).
Când vrem să reflectăm modelul de domeniu, un punct crucial este definirea stării sale. În programarea orientată obiect, starea unui obiect este definită de starea proprietăților sale. De asemenea, starea modelului de domeniu este definită de starea obiectelor sale. De aceea, trebuie să avem o modalitate de a defini clar starea obiectelor. Dacă nu am putea face acest lucru, am fi eșuat în cazurile de utilizare ușoară precum "Câte comenzi există?", Deoarece răspunsul necesită întotdeauna cunoștințe despre starea tuturor Ordin
obiectele dintr-un domeniu și o modalitate de a le identifica și distinge. DDD definește două tipuri de obiecte: Entități și obiecte de valoare.
Un entitate este un concept familiar, dacă sunteți familiarizat cu bazele de date relaționale.
Tabelele dintr-o bază de date relațională au, de obicei, un identificator unic care distinge un rând de celălalt. Același lucru este valabil și pentru entități. O entitate trebuie să aibă un identificator clar care este unic în întregul sistem. Pentru o comandă, aceasta ar putea fi o proprietate de tip uint, numită Numar de ordine
. Desigur, te-ai uita în glosarul tău, unde ar trebui definit termenul corect.
O entitate rămâne aceeași atunci când se modifică anumite proprietăți. De exemplu, puteți adăuga sau elimina elemente dintr-o comandă, dar ar fi aceeași comandă. Ce se întâmplă atunci când schimbați Numar de ordine
? Ei bine, din POV din domeniul dvs., o comandă este ștearsă în timp ce un altul este creat.
A obiect de valoare este un container simplu pentru informații. Este imutabil odată ce este creat. Schimbarea unei proprietăți înseamnă că ați schimba obiectul valoare. Un obiect de valoare este definit de toate proprietățile sale; nu are nevoie de un identificator unic. Întregul obiect este unul. Un exemplu de obiect de valoare ar fi un OrderAddress
, așa cum este definită de numele, adresa și orașul destinatarului. Dacă ați schimba o proprietate, de exemplu orașul, adresa de comandă s-ar schimba complet.
Împărțirea obiectelor în obiecte și entități de valoare este importantă pentru a defini starea domeniului dvs. - deoarece aceasta este lucrarea la sol pentru identificarea componentelor. Dar este la fel de important să le definim pentru a avea un domeniu scalabil și ușor de întreținut. Entitățile reprezintă reprezentarea obiectelor din lumea reală, cum ar fi Persoane, Comenzi sau Articole. Obiectele de valoare sunt containere pentru informații precum culorile sau adresele și sunt reutilizabile și pot fi distribuite între entități sau chiar întregul dvs. sistem. Definirea acestora poate lua anumite practici, deoarece depinde de cazul de utilizare dacă aveți un obiect de valoare sau o entitate.
Când ne uităm înapoi la rezumatul glosarului nostru, putem vedea conexiunile și dependențele dintre obiectele noastre din stratul de domeniu. În DDD, aceasta se numește asociațiile și este modelul interacțiunilor care au loc.
De exemplu, elementele fac parte din comandă. Dacă am fi procesat împotriva unei baze de date relaționale, aceasta ar fi o relație unu-la-multe (sau 1: n). Dacă fiecare comandă ar avea exact o Ordonanță de Ordine, ar fi o relație unu-la-unu. Deoarece nu ne pasă de bazele de date relaționale și nu ne îngrijim doar de finisarea domeniului, relația poate fi ușor exprimată cu două metode în clasa Ordin: getItems ()
și getOrderAddress ()
. Rețineți că primul este plural (deoarece există multe elemente), iar al doilea este unic. Dacă ați avea o relație de la mulți la mulți, ați da celor două clase o metodă getter. Desigur, aveți nevoie și de setteri - le-am omorât să păstreze exemplele ușoare.
În DDD încercăm să evităm multe relații, deoarece acestea tind să adauge complexitatea domeniului. Din punct de vedere tehnic, înseamnă că două obiecte trebuie să fie păstrate în sincronizare în timpul ciclului lor de viață, iar păstrarea lucrurilor în sincronizare poate duce la încălcarea principiului DRY. De aceea procesul de rafinare a modelului ar trebui să se străduiască pentru simplitate. De multe ori, o asociație este mai puternică într-o direcție decât cealaltă și este o idee bună să redesemnăm structura la o relație unu-la-mulți. Verificați dacă asocierea este relevantă pentru logica de afaceri a aplicației dvs. Dacă se întâmplă doar în cazuri de utilizare non-core și rare, poate doriți să căutați un alt mod de a primi informațiile necesare.
Asociațiile construiesc un arbore al obiectelor și ar trebui să ajungeți la o construcție de asociere unde fiecare obiect poate fi preluat prin metode getter. Aceasta este o construcție părinte-copil care duce în final la un obiect rădăcină. Aceasta se numește agregare în DDD. Un design bun duce în final la un agregat care este capabil să reflecte întregul domeniu. În momentul de față am analizat doar o mică parte din glosarul nostru, dar se pare că un client este agregatul nostru rădăcină:
Obiectul client este parintele tuturor membrilor domeniului.Agregatele sunt o parte importantă, deoarece DDD încearcă să izoleze domeniul din aplicația din jur. Dacă ne place să avem informații despre un client, solicităm un agregat rădăcină și putem traversa prin intermediul copiilor săi accesul la informații printr-o interfață clară de getters și setters.
DDD este ca vânzările, oferă o singură față clientului, agregatul față de sistemul din jur. Prin urmare, acesta oferă acces la un set structurat de procese și informații relevante; de exemplu, ordinul.
Domain Driven Design este ca și vânzările, oferă o singură față clientului.
Aplicația din jur are acces la un agregat prin depozite, care sunt în principiu un fel de fațadă. Cu alte cuvinte: Un obiect de domeniu este un agregat dacă are un depozit. Depozitele furnizează metode de interogare a agregatelor. Exemple pot fi findClientByEmail (e-mail șir)
sau doar Găsiți toate()
. Sunt actualizări la fel de bine și adaugă noi obiecte domeniului. Astfel, ei au probabil metode cum ar fi adăugați (Client nouClient)
sau ștergeți (Client laBeDeletedClient)
.
Cu un agregat, accesați copiii numai prin intermediul părintelui. De exemplu, un agregat client vă oferă acces la toate comenzile clientului. Dar dacă aveți nevoie să accesați datele dintr-o altă perspectivă decât cea a clientului, puteți stabili un al doilea agregat. Să presupunem că doriți să aveți o listă cu toate ordinele, indiferent de clientul în care au fost plasate. Un depozit de comenzi va face treaba!
Stratul de domeniu și depozitele acestuia.Deoarece depozitul este punctul de intrare pentru aplicația înconjurătoare spre stratul de domeniu, în cazul în care alți jucători intră în zonă. Amintiți-vă că acum avem de-a face cu obiecte simple.
Te-ai intrebat cum va deveni real? DDD este un companion excelent pentru cadre, deoarece este construit pe obiecte simple, cu o schemă simplă de obiecte de valoare, entități și agregate. Cu toate acestea, deoarece simplitatea este putere în IT, suntem acum în măsură să externalizăm întreaga componentă a infrastructurii. Hai să aruncăm o privire sub capotă și cum se poate răspândi DDD în jurul unei aplicații.
S-ar putea să fi observat că accentul nostru asupra domeniului a exclus un strat de persistență, precum și de lucruri obișnuite precum vizualizări sau controlori din lista noastră de sarcini. Întreaga aplicație poate consta în chestii mult mai complexe decât obiecte simple și vreau să subliniez câțiva pași care trebuie făcuți pentru a conecta domeniul și aplicația împreună, precum și strategiile de implementare existente. Voi face câteva exemple bazate pe FLOW3, un cadru de aplicații cu accentul principal pe furnizarea infrastructurii DDD. Nu este necesar, dar nu va face rău dacă ați citit introducerea mea. Pentru a aplica domeniul de afaceri unei aplicații, sunt frecvente următorii pași:
Când aveți o privire asupra comentariilor de pe articol la Aspect Programare Oriented (AOP), veți vedea o discuție interesantă despre dacă un cadru ar trebui sau nu să-și adauge amprenta prin intermediul adnotărilor de comentarii. Abordarea din FLOW3 se bazează pe modul în care este implementat Designul condus de domeniu. Aruncați o privire la acest cod:
/ ** * Un client * * @ FLOW3 \ Scope ("prototip") * @ FLOW3 \ Entitatea * / clasa Client / ** * Numele clientului. * * @ FLOW3 \ Validați (tip = "Text") * @ FLOW3 \ Validați (tip = "StringLength", options = "minimum" = 1, "maximum" = 80) ) * @var șir * / nume protejat $; / ** * Obține numele clientului * * @return string Numele clientului * / funcția publică getName () return $ this-> Name; / ** * Setează numele acestui client * * @ param string $ Nume Numele clientului * @return void * / funcția publică setName ($ name) $ this-> name = $ name;
Aceasta este o clasă foarte simplă și nu conține multă logică de afaceri, dar acest lucru se va schimba probabil odată ce cererea va crește. FLOW3 este prezent prin unele adnotări de cod. Definește clasa ca fiind entitate și adaugă câteva reguli de validare care trebuie aplicate (acest lucru este opțional). Rețineți că există o denumire de adnotare @ORM \ Coloana (lungime = 80)
. Aceasta este o informație pentru stratul de persistență și vom reveni la aceasta într-un moment.
FLOW3 folosește adnotări aici pentru a păstra domeniul curat. Sunteți liber să utilizați clasa oriunde altundeva, deoarece este încă un obiect simplu. Puteți opta pentru a comuta la Symfony cadru, care folosește același strat de persistență (Doctrină), de unde codul aproape că va ieși din cutie. Prin împingerea configurației cadru în afara domeniului de aplicare al interpretului PHP, domeniul rămâne un obiect obișnuit PHP. Puteți reutiliza chiar și fără nici un cadru.
Dar acum că cadrul este conștient de obiect, acesta poate calcula acum cerințele pentru o tabelă de baze de date MySQL. Pentru a stoca instanțele clientului de clasă, FLOW3 (și Doctrina ca cadru de persistență) ar efectua următorii pași pentru dvs.:
Definiția proprietății pentru articolele din comanda noastră poate arăta astfel:
/** * Obiectele. * @ @ORM \ OneToMany (mappedBy = "comanda") * @ORM \ OrderBy ("price" = "ASC"<\LogisticApp\Domain\Model\Item> * / elemente $ protejate;
Observați că aceasta returnează o Colecție de Doctrină, care este un fel de înveliș pentru o matrice, cum ar fi ArrayLists în Java. În esență, aceasta înseamnă că toate elementele trebuie să fie de tipul dat, în acest caz Item. Am optat să adaug o declarație de comandă cu privire la modul în care vreau să fie organizată colecția (după prețurile articolelor).
Contrapartea din clasa elementului ar putea fi:
/** * Ordinea. * * @ORM \ ManyToOne (inversedBy = "elemente") * @var \ LogisticApp \ Domain \ Model \ Order * / ordin $ protejat;
Este doar vârful unui aisberg, dar ar trebui să vă dau o idee despre modul în care lucrurile pot fi automatizate: Doctrina oferă o strategie puternică cu privire la modul de mapare a asocierilor la mesele în care stochează obiectul. De exemplu, deoarece elementele se vor traduce într-o relație una-la-multe (o comandă poate avea mai multe elemente) în baza de date, doctrina va adăuga în tăcere o cheie străină pentru comandă la tabela de articole. Dacă decideți să adăugați un depozit pentru element (făcându-l un agregat), ați putea accesa în mod magic o findByOrder (Ordine comandă)
metodă. Acesta este motivul pentru care nu ne-a păsat de bazele de date sau de persistența în timpul creării domeniului - este ceva ce un cadru poate avea grijă de.
În cazul în care sunteți nou în cadrul persistenței, modul de mapare a obiectelor într-o bază de date relațională se numește ORM (Object-Relational Mapping-). Are unele neajunsuri de performanță, care sunt cauzate în principal de diferitele abordări pe care bazele de date relaționale și modelul de obiect le au. Există discuții lungi despre asta. Cu toate acestea, în aplicațiile CRUD moderne (nu numai pe domenii), ORM este calea de urmat - în principal pentru motive de întreținere și extensibilitate. Cu toate acestea, trebuie să știți ORM-ul dvs. și să aveți o bună înțelegere a modului în care funcționează. Nu credeți că nu mai aveți nevoie de cunoștințe despre bazele de date!
După cum probabil ați observat, obiectele dvs. pot fi complexe liniștite și pot avea o lungă linie de traversare dacă au mulți copii, care la rândul lor au mulți copii pe cont propriu.
Prin urmare, odată ce depozitul recuperează datele dintr-o bază de date, ele trebuie transformate în obiecte într-un mod inteligent. Deoarece avem acum un strat de persistență implicat, transformarea este mult mai complexă decât doar instantierea unui obiect. Trebuie să gestionăm linia de traversare prin reducerea la minimum a apelurilor relevante la baza de date. Nu toți copiii sunt întotdeauna necesari, astfel încât aceștia să poată fi recuperați la cerere.
Unele obiecte vor fi obiecte de valoare care trebuie create doar o singură dată, ceea ce poate salva o mulțime de memorie. De aceea, orice domeniu de domeniu are nevoie de o fabrica inteligenta care genereaza obiectele pentru tine. Prin urmare, în cadrul modern, nou
operatorul este considerat a fi prea scăzut pentru aplicațiile moderne. FLOW3 merge mult pentru a oferi posibilitatea de a instantiza obiecte cu nou
cuvânt cheie, dar compilarea de fundal modifică automat modificarea obiectului obișnuit în gestionarea obiectului puternic. Unele caracteristici pe care managerul / fabricantul de obiecte ar trebui să le poată avea, indiferent de cadrul pe care îl folosiți, sunt:
S-ar putea să te fi încruntat în ultima teză. De-a lungul întregului articol, am subliniat că folosesc obiecte simple în domeniu și chiar am încălcat paradigma "nu repetați-vă" și l-am menționat de mai multe ori, deoarece este atât de important pentru DDD. Și acum vă spun că aveți dependențe și servicii care trebuie să facă parte din domeniul dvs. ...
Există un adevăr trist în lumea reală: nu există un lucru ca un domeniu pur. Nu veți întâlni aproape niciodată un client care începe de la zero; deci trebuie să satisfaciți circumstanțele ca sistemele moștenite. Ele pot avea o implementare oribilă, dar compania nu poate scăpa de ea. Este posibil să trebuiască să apelați serviciile și API-urile și să preluați date de la diverse terțe părți, iar aceste sisteme vechi influențează domeniul de afaceri.
Tot ceea ce am discutat până acum este important, dar întrebarea cu privire la modul în care un cadru rezolvă dependența de serviciile non-domeniu este esențială pentru un design curat de domeniu. Acesta este motivul pentru care echipa FLOW3 a depus eforturi enorme pentru implementarea aspectului orientat; este o modalitate de a introduce servicii în domeniu fără a atinge codul, fără a încălca regula obiectelor vechi obișnuite. Există și alte abordări, cum ar fi deplasarea dependențelor dintre servicii și domeniu la controler, dar programarea orientată spre aspect este de departe cel mai elegant mod pe care îl cunosc. Aș vrea să vă aud gândurile despre acest subiect!
Un cadru bun vă poate oferi mult sprijin în afară de punctele pe care le-am menționat. De exemplu, FLOW3 manipulează în mod transparent obiectele de domeniu din vedere cu motorul său remarcabil de template numit Fluid. Scrierea șabloanelor Fluid, odată ce domeniul este terminat, este la fel de relaxantă ca o zi la plajă.
Acest articol este doar o introducere în designul de domeniu. Am prezentat câteva dintre conceptele de bază, dar trebuie să recunosc că ar putea fi greu să înțelegi singura teoria. Vreau sa va incurajez sa incercati design-ul pe Domenii in propriul proiect intr-un proiect real. Veți experimenta că conceptele de domeniu sunt foarte intuitive de utilizat.
Am fost aruncat în DDD ca un pește în apă într-un proiect foarte extins, fără prea multe cunoștințe anterioare despre concepte (extbase este un cadru de proiectare bazat pe domenii pentru extinderea clădirilor pentru CMS Typo3 și se bazează pe FLOW3). Mi-a lărgit viziunea asupra modului de gândire cu privire la proiectarea software-ului și sper că va crește și dvs..