Using a Framework will harm the maintenance of your software

Eu as recomanda Domain-Driven Design Distilled: https://www.amazon.com/Domain-Driven-Design-Distilled-Vaughn-Vernon/dp/0134434420 inainte de toate. Apoi Red book of DDD. Bule book m-a adormit.

@Cosmin_Popescu Vorbind despre frameworkuri, si stiind ca tu esti in ecosystemul java, as zice ca la inceput sa privesti proiectul tau(atentie, nu framework-ul) ca 3 layere concentrice.

Alistair Cockburn a venit prima oara cu termenul de arhitectura hexagonala in 2005, iar layerele aratau cam asa:

Directia dependentelor este de la exterior la interior.

La interior, in cercul galben, Domain adica, vei avea Entitatile, interfetele pentru repository(nu implementarea repository) si arguably domain events.

Fizic, vei face un folder Domain, sa zicem. Iar clasele java din interiorul domain nu ar trebui sa foloseasca nimic din exterior sau din alt layer al proiectului, decat poate tot din interiorul Domain.
Exemplu: Entitati ca peste tot gen User.java, Document.java, Video.java insa fara adnotari sau alte importuri de spring aici.

Mai departe, cercul rosu este cercul cu Use Cases. Unii il mai numesc Application Services. Fizic este si el un folder cu niste clase. Ce ar trebui sa fie aici? Gandeste-te la cum se livra software-ul in anii 2000. Daca cumparai Ms Word pe un CD, pe spatele CD-ului vedeai ce stie sa faca acel software. Stie sa creze un document, sa il editeze, stie sa printeze un document, sa il trimita pe mail, sa modifice fontul textului, etc. In cercul de use cases pui exact asta.
Exemplu: CreateDocumentService.java, PrintDocumentService.java.

Spring boot sau Symfony sau Laravel in cazul nostru, se regasesc in partea de Infrastructura. Partea Verde.

Mai departe avem cercul verde si cercul albastru. Adica Infrastructura. Aici gasesti controllerele, si tot ce tine de frameworks. Aici vei avea Spring Boot. Controllerele sunt ok sa aibe adnotari, dar se vor reasi in acest layer obligatoriu. E ok sa se importe din Domain sau Use-case. Aici avem si repository care se leaga la baza de date si implementeaza ce ai in interfetele din Domain. Repository aici este cel care se va folosi de un ORM(Hibernate, Doctrine in cazul Symfony, etc). Aici se regaseste si UI-ul aplicatiei. Sau CLI client daca e cazul.

DDD vine cumva ca o manusa pentru arhitectura hexagonala. As zice ca este o evolutie care include si partea de ubiquitous language. Dar nu o sa intram in detalii acum.

Sper ca ti-am raspuns cat de cat la intrebare.

Exemple de cod:

Java: GitHub - CodelyTV/java-ddd-example: ☕🎯 Hexagonal Architecture + DDD + CQRS in a Java project using SpringBoot + skeleton GitHub - CodelyTV/java-basic-skeleton: ☕🚀 Java Bootstrap: Skeleton for your new projects

PHP: GitHub - CodelyTV/php-ddd-example: 🐘🎯 Hexagonal Architecture + DDD + CQRS in PHP using Symfony 6 + skeleton GitHub - CodelyTV/php-basic-skeleton: 🐘🚀 PHP Basic Skeleton: Bootstrap your new projects using this Composer Project

Typescript: GitHub - CodelyTV/typescript-ddd-example: 🔷🎯 TypeScript DDD Example: Complete project applying Hexagonal Architecture and Domain-Driven Design patterns + skeleton GitHub - CodelyTV/typescript-basic-skeleton: 🔷🌱 TypeScript Basic Skeleton: Bootstrap your new TypeScript project with the bare minimum dependencies

6 Likes

Cum ar putea fi aplicat hexagonal architecture si DDD in gamedev?

Prespunand ca am un life simulator, ceva gen Dwarf Fortress:

→ Un logic core cu reguli care modifica database-ul.
→ Un database modificat de logic core care contine toate datele jocului (cati dwarfi, unde, ce teren, etc)
→ Un renderer care sa afiseze database-ul (2d, 3d, etc).
→ Un modul de player input care sa interactioneze cu logic core.

Fiecare modul independent de altele, unit printr-un glue programatic cu interfete.

Hmm, deci o entitate gen dwarf care sa contina datele dwarfului + behaviour nu-i ok? Ci mai curand un data repository masiv manipulat separat de o logica complexa intr-un core?

O sa citesc si cartile dar extrapolez aici din informatiile din thread.

Nu degeaba am scris asta. DDD nu are nicio treaba cu “ reguli care modifica database”, e o schimbare totala de paradigma care trebuie internalizata. La fel cum nu stim OOP doar citind despre mostenirea anima → dog si gata suntem tari la OOP.
Prima data iti modelezi domeniul, te apuci de implementat behaviour (in poza de mai sus erau numite use cases) si abia apoi ajungi la persistenta si conectivitate externa.

Si DDD poate iesi spaghetti, asta e unul din motive, arsul etapelor.

1 Like

Merci @tacheshun
Cam asta cautam :slight_smile:

In termeni de layman. Este un exercitiu bun pe care il voi face

A testat cineva DDD in viata reala ?

Am o oarecare presimtire ca daca iei orice proiect si il faci cu microservicii si API-uri serverless de la Azure/GCP/AWS, plus UI obtii practic DDD. Mai greu cu contextul si planficarea de dinainte.

Din ce am inteles DDD functioneaza doar daca pui domain experts sa lucreze cu oamenii tehnici sa ajunga la ubiquitous language si sa se faca planificare pentru ca dupa sa se identifice solutiile tehnice. Mai mult ca sigur it’s not gonna happen sau nu la un nivel destul de mare incat sa fie eficient.

Domain experts o sa ceara avioane, dezvoltatorii o sa gaseasca solutii, o sa vina bugetul si trebuie impartit pe agile…

Sa nu uitam ca ubiquitous language e diferit per microserviciu.

Da. La mai multe proiecte.

Presimtirea ta nu e buna. In primul rand DDD nu are nimic a face cu microserviciile. Prin conventie, am mai vazut folosit cate 1 microserviciu per Bounded Context. Dar este doar o conventie, ideile DDD fiind mult mai vechi decat microserviciile. Cam cu minim 10 ani mai vechi.

Apoi, faza cu Azure, GCP, si alti cloud vendori…pe cat de misto mi se pare mie ideea de serverless, pe atat de gresit este sa consideri ca poti face tactical design cu ajutorul lor. Daca tot le doresti folosite, ar trebui sa fie undeva in layerul de infrastructura.

Asta e si una din ideile principale din spatele arhitecturii hexagonale…arhitectura care sta la baza Tactical DDD, si anume tot ce ai in layerul de Domain (si as merge mai departe pana la Use Cases) poate exista complet decuplat de orice infrastructura.

Cum obtii chestia asta daca implementarea ta e 100% serverless in azure? Poti face copy/paste din azure in aws si sa mearga din prima? Ma indoiesc.

3 Likes

O sa incerc sa absorb informatia din carti si sunt de acord ca practica te ajuta sa internalizezi.

Problema in indie gamedev e ca n-am luxul de putea repeta des.
Un proiect indie este o investitie considerabila de energie si timp. Poate mai am 5-7 proiecte importante de-a lungul intregii mele vieti, daca si alea.

Lucrul ce clienti din nou nu se pune ptr. ca de multe ori n-ai posibilitatea de a-ti alege arhitectura.

Intr-o lume ideala as fi expert in toate inainte sa ma apuc de un proiect. Dar n-ai cum, oricat ai vrea. Iti alegi niste prioritati si din restul iei ce poti, cum poti.

As zice ca invatatul se face pe proiecte mici. La mentee obisnuiam sa le sugerez calculator de salarii si scraper de meteo api. Ceva de una-doua saptamani, pe care sa il refaci fara remuscari.

2 Likes

Ati avut careva experienta cu refactoring legacy code into DDD ? Mi se pare mie sau nu poti face mare lucru pana nu se face refactoring pt a ajunge la un punct in care separarea zonelor de responsabilitate e clara?

Am avut. In primul rand vrei sa ai zona aia acoperita de teste. Apoi ai vrea sa pleci cu refactorizarea de la un use case concret.

Din ce mai imi amintesc, lucram pe un proiect cu componente symfony si doctrine orm la vremea respectiva. Si ajunsesem sa avem un OrderService.php imens. Ca na…ce e rau in a avea o clasa cu 1000 de linii de cod care sa faca de toate? /s

Am citit noi ceva de single responsability, ddd + hex si rezultatul a fost cam urmatorul:

  • creare structura de foldere, Domain, Application, Infrastructure.
  • Entities mutat sub Domain.
  • Creat interfete pentru repository si puse sub Domain langa Entities. Avut grija ca entitatile sa nu foloseasca nimic din Application si Infrastructure.
  • Am mai creat niste clase de tip Value Object. Le foloseam in special pentru IDs(uuid). VO pot fi folosite foarte elegant in multe situatii. De exemplu, sa modelezi conceptul de Money. Dar intram altadata in detalii despre asta.
  • OrderService.php l-am spart in mai multe services. CreateOrderService.php, CancelOrderService.php and so on. Fiecare clasa ajunsese sa aibe maximum 60-70 de linii de cod. Si facea 1 singur lucru.
  • Dependente pentru aceste services erau in special repositories. CreateOrderService avea de exemplu ca dependenta(una dintre ele) iOrderRepository.php(interfata pe care o regaseai in Domain). Mai jos in Infrastructura, aveam implementarea concreta, cu doctrine a OrderRepository.php. In principiu, din ce mai imi aduc aminte, Clasa asta extindea din EntityRepository(use Doctrine\ORM\EntityRepository) si implementa OrderRepository interface
  • controllerele erau clasice symfony.
  • UI-ul il aveam intr-o alta structura numita apps. Tot sub infrastructura.

Cam atat. Sper ca am explicat cat de cat ok. Un POC pe care ne-am bazat munca la vremea respectiva, il poti vedea aici: GitHub - tacheshun/ad-ddd: DDD lab from eMAG DevIn

9 Likes

Ca si recomandare pentru cine mai citeste: interfata sa fie denumita OrderRepository iar implementarea care extinde doctrine DoctrineOrderRepository Sensible Interfaces

2 Likes

Vad “lab” in url-ul repoului. A fost un experiment? Ai mai face asa ceva in productie? Care sunt concluzile ?

Pai am zis mai sus ca munca efectiva s-a bazat pe acest POC. La vremea respectiva, primeam din partea companiei 1 zi pe luna pentru research :smiley:

Concluziile la vremea respectiva au fost favorabile acestui tip de arhitectura deoarece aveam destul de multa complexitate accidentala acumulata(technical debt), iar aceasta forma de organizare a codului era mult mai mentenabila pe termen lung.

Anyway, looking back, eu personal as implementa tipul asta de arhitectura cam pe la toate proiectele. Dar nu e dupa mine. Si cum cred ca inca nu mi-am pierdut mintile, o sa zic si de ce cred eu ca nu se preteaza in foarte multe cazuri:

  • ai nevoie de oameni cu o senioritate peste medie. As zice ca mult peste medie pentru a intelege ce e acolo si a executa apoi. Discutiile pot deveni interminabile. Multi chiar nu avem atata timp la dispozitie.
  • ai nevoie de suport din partea businessului. Trebuie sa inteleaga ca se va trece printr-o faza de invatate si acomodare. Nu se va putea livra la foc automat. Mare atentie aici ca noi am primit Ok-ul din partea lor insa peste 1-2 luni deja reveneau la vechile obiceiuri: “haideti sa facem repede x chestie ca sa surprindem nu stiu ce VP”, “de ce dureaza atat zona aia?” Etc.
  • sa fii pregatit sa doara la inceput. Multa lume pur si simplu nu e pregatita sa iasa din modelul promovat de site-urile de tutoriale “laracast” like. Sau “symfonycast” like. Si orice idee ce nu vine cu hype sau cu suport masiv in comunitate va intalni rezistenta maxima in adoptie. Si nu ma intelege gresit, chiar iubeam laracasts si symfonycasts cand faceam PHP, dar tutorialele alea au ca scop ca privitorul sa se familiarizeze cu un anumit subiect si apoi sa continue research-ul in particular. Nu sa puna in productie ce fac oamenii aia 1 la 1. Lucru care se intampla mult mai frecvent decat ne-am dori.
8 Likes

Cel mai adevarat punct. Si cat de ostracizat esti in general daca spui asta pe undeva.

2 Likes

Arhitectura e standard pe java, dto → controler → service / mapper → repository → entitate, nu prea vad de ce asa multe interfete bine in java un repository e doar un interfata, sau nu inteleg eu.

Inițial am fost tentat să comentez doar după titlul articolului, dar am reușit să-l citesc parțial. Deși framework-urile te ajută foarte mult în generarea codului de uzură fără să fi nevoit să reinventezi roata de fiecare dată. Te concentrezi pe esențial, ești rapid și eficient. Totuși, are dreptate, lucrând cu el ești obligat să urmezi un anume flow care, în timp, te face să fii ineficient din anumite puncte de vedere.

Find fan python (pentru că lucrez cu python de multă vreme), citind articolul mi-au venit în minte 2 chestii: în python poți să folosești membrii privați sau protected, modificatorii de acces fiind doar o convenție. În Django, atunci când faci migrările, nu primești acces la modelele custom. Totuși, dacă scrii codul suficient de decuplat, reușești.

  1. Asa multe interfete pentru ca ca codul tau sa depinde de un contract si nu de o implementare
  2. Un repository nu este o interfata. Interfata este doar un contract care defineste ce trebuie sa faca implementarea. Spre exemplu in UserInterface spui ca trebuie sa existe metoda findUser care are ca parametru un i32 denumit id. In implementare (ex: DatabaseUserRepository sau RedisUserRepository sau ApiUserRepository) vei avea implementarea efectiva pentru acel findUser unde efectiv se face queri-ul sau call de api etc. In felul asta daca initial sa zicem mergi pe useri in DB si apoi vrei sa schimbi spre altceva trebuie doar sa modifici in DI si restul ramane nemodificat inclusiv testele.
1 Like

In Java Spring JPA repositories sunt interfete, poate la asta se referea.

Merci pentru clarificare. TIL.