Domain Driven Design in PHP

A php az a nyelv, amin szeretünk viccelődni. Amellett, hogy én sem vagyok a vicc ellen, szeretek programozni benne, inkább csak egy eszközként gondolok rá. Persze minden eszköznek, így programozási nyelvnek is megvannak a saját tulajdonságai és limitációi, és csak ezek ellen dolgozva tudunk értékelhető eredményeket elérni.

Viszont nem ezekről a konkrét limitációkról szeretnék mesélni, hanem azt szeretném megosztani veletek, hogy nemrég feldolgoztam Carlos Buenosvinos - Domain Driven Design in PHP című könyvét, ami nekem talán az egyik legjobb élmény volt tanulás szempontjából ever.

Sok kísérletezésre adott lehetőséget, architektúrák tanulmányozásával, hogy mikor-mit, aztán Hexagonal Architecture-el párosítva mindenféle keretrendszerhez, relációs adatbázisokhoz, Redis-hez illesztve megvalósítani ValueObject collectionöket, kísérletezni Eventually Consistent Aggregate-ekkel, ha a business úgy dönt, hogy mégiscsak kell egy Invariant a 2 Aggregate közé, de mondjuk teljesítmény okokból nem lehet összevonni őket, stb. stb.

Sok érdekesség jött még szembe, ami előhozott olyan limitációkat, amikről eddig nem tudtam. Készítettem egy jegyzetet, félig-meddig címszavak szintjén, megosztottam az oldalamon, hogy ti is bele tudjatok nézni a témába, hogy érdekel-e a könyv.

Hozzászólások

Szerkesztve: 2020. 12. 29., k – 17:14

Készítettem egy jegyzetet

Szep munka, koszi!

Amikor ilyen összefoglalót olvasok, akkor el szoktam szégyellni magam, hogy milyen keveset is tudok az egész témáról.

Nem olyan nagy dolog, hogy leírtam, mivel szummában nem állok rosszul. ;) 

A DDD az aminek a létezéséről sem tudtam, de mindig problémát okozott nekem. :) Ugyanis az üzleti modellt is én tervezem nem csak a szoftvert. Így számos alkalommal az OOP tervezésnél kerültem ellenmondásba. Ha ránézek a rendszeremre ez több esetben implementálva is előfordul. Anno amikor ezt  a rendszert elkezdtem fejleszteni (2003.aug), a jogosultság kiosztásnál sikerült feltalálnom a DAVE*-t  is, csak nálam kicsit más a feloldása: DNLM (Delete,New,List,Modify) .  A rendszerben a feladatköröket szerveztem modulokba és minden user kaphat bármely modulhoz DNLM hozzáférést, ha nincs semmije, akkor talán még a modul létezéséről sem tud. Ez a fajta szervezés első problémája, ha valamely modulnak több szintű (pl.: admin, user) elérését kellene megoldani. E probléma feloldása a modulnak egy admin modul elkészítése, így már ez sem okoz problémát.

Ezt a fajta jogosultság szervezést csak azért írtam le, mert lehet, hogy meglepő, de kihat minden másra is, hiszen alapból ehhez kell elsődlegesen alkalmazkodni és ez befolyásolja minden modul elkészítését is. Ami kihat az osztályokra és annak kódszervezésére is.

*DAVE (delete, add, view, edit)

A hidegburkoló fürdőszobája, köszi! Sajnos nem közlik, hogy mi a baj, és hogy mikor láttak bajt, site ownership validáció után sem. Most nem nagyon van időm barkóbázni, ha pár nap múlva is hisztizik, átpakolom a domaint githubra, úgyis csak md fájlok vannak a ghost-ban. De tényleg köszi, hogy szóltál! :)

Szép összefoglaló!

Amit nem értek.
Az Entity-nél illetve különösen a ValueObject-nél mit jelent a fully testable? Azok jó esetben csak adatszerkezetek, nincs rajtuk mit tesztelni.

Köszi, jó érzés, hogy valaki tényleg el is olvassa :)

Az Anemic vs Rich Domain Model-nél "írok" idézek róla, hogy az Anemic Model szerint valóban csak adatszerkezetek, amit könnyű implementálni, de nehéz konzisztensen tartani...  viszont a Rich Model szerint nem csak adatszerkezetek, hanem teljes mértékben rendelkeznek magukról, megfelelnek az egységbezárás elvének, az üzleti logikát, pl. a validációt is tartalmazzák (a validációról bővebben a Validation-nél).

Képzeld el azt a helyzetet, ha a User osztályba bevezetünk egy updatedAt tulajdonságot is.

Rich Model esetén elég végignézni az osztály metódusait, és ott bevezetni a változásokat. Ez gyakorlatilag az SRP definíciója (egy oka van a változásra az osztálynak, jelen esetben a User osztálynak, hogy magába foglalja a User működését).

Anemic Model esetén tudnom kellene, hogy milyen service-ek kezelik a Usert az egész alkalmazásban, ráadásul még az összes párhuzamos csapat munkájával is tisztában kellene lennem, hogy épp nem írnak-e egy új service-t, ami kezeli a Usert. Ez előbb-utóbb inkonzisztens state-ekhez fog vezetni. A PHP interpreteres nyelv, de képzeld el a DDD-t compiler-es környezetben. Anemic Model szerint nem lenne elég a User osztályt újrafordítani, hanem minden service osztályt is újra kellene fordítani.

A fully testable alatt azt értem, hogy minden osztályt összes publikus metódusának a "viselkedése" (behaviour) unit-testelve van (az implementáció nyilván nincs). Akár Anemic, akár Rich.

Anemic Model-ben ezt könnyű implementálni, mert van egy adatot tároló objektumunk, és valamilyen módon tematizált service-eink (ahogy olvastam, Martin Fowler és Robert C. Martin szerint is kb azt kellene jelentse, hogy 1 Service Class - 1 metódus). És ezeket relatíve könnyű Unit-tesztelni, csak ugye nehéz konzisztensen tartani. El tudok képzelni olyan esetet is, amikor újra kell írni a unit test-eket is a service-ekhez, nem szerencsés.

Rich Model szerint a User-hez tartozó üzleti logikát is a User Class-be implementáljuk. Az Entity-k, Value Object-ek sokszor nem konstruktorral jönnek létre, hanem valamilyen Factory módszerrel, serializálással, stb. Ezért a klasszikus Anemic Model Dependency Injection konstruktorba nem működik, a Singleton antipattern, meg bele sem kellene égetni a Domain-be egy csomó infrastruktúra osztályt. Szerintem a legjobb megoldás, ha pl a changeNotificationEmail() metódusba ISP szerint injektálunk egy service-t, ami a konkrét validációt megvalósítja, de az üzleti logika, (notificationEmail validálása) az a User-ben marad. Az pedig, hogy pl van-e MX rekordja egy email-nek, az pedig az injektált service-ben marad.

Azt nem írtam a jegyzetemben, hogy Rich Model esetén én pl setName() szerű metódusok helyett changeName() szerű metódus neveket szoktam használni, hogy egyértelmű legyen a különbség. Az Aggregate-eknél is ilyesmi "védekezés", hogy az egész Aggregate-nek 1 Repository-ja van, nehogy valaki véletlenül az egyik részobjektumot kezelje közvetlenül.

Néhány éve csináltam egy CQRS/ES frameworköt, esetleg nézz rá, ha érdekel. Már rég nem foglalkozok vele, nem tudom, hogy pl. működik-e a legújabb PHP verziókkal.

Most épp Javában írok egy Reactor alapút. Hogy őszinte legyek, PHP-ben sok mindent meg lehet csinálni, csak nem biztos, hogy érdemes.