Miért jó domain objecteket használni?
Hoztam egy példát domain objectek használatára egy cikkből. A cikk most nem igazán releváns a kérdés szempontjából, de azért belinkelem.
A kérdés annyi, hogy miért jobb domain objecteket használni a business logic layer-ben, mint egy az egyben átküldeni az adatot a data access layer-ből.
1.) - domain objectes példa2.) - csak adat küldős példaAmit nem értek, hogy miért éri meg az első (domain object-es) megoldást használni, és miért nem divat a második?
Felsoroltam néhány érvet, de szeretném, ha írnátok hozzá ti is, illetve megvitatnánk a meglátásaimat.
■ A kérdés annyi, hogy miért jobb domain objecteket használni a business logic layer-ben, mint egy az egyben átküldeni az adatot a data access layer-ből.
1.) - domain objectes példa
class MoneyTransfer
{
private $accountDao;
public function transferMoney(MoneyTransferRequest $transfer)
{
$source = $this->accountDao->find($transfer->sourceId);
$destination = $this->accountDao->find($transfer->destinationId);
$money = new Money($transfer->amount);
$source->withdraw($money);
$destination->deposit($money);
return new MoneyTransferResponse();
}
}
MoneyTransfer : Interactor
MoneyTransferRequest : DTO
MoneyTransferResponse : DTO
Money : Entity
Account : Entity
class MoneyTransfer
{
private $accountDao;
public function transferMoney(MoneyTransferRequest $transfer)
{
$source = $this->accountDao->find($transfer->sourceId);
$destination = $this->accountDao->find($transfer->destinationId);
$source->balance = $source->balance - $transfer->amount;
$destination->balance = $destination->balance + $transfer->amount;
$this->accountDao->updateBalance($source);
$this->accountDao->updateBalance($destination);
return new MoneyTransferResponse();
}
}
MoneyTransfer : Interactor
MoneyTransferRequest : DTO
MoneyTransferResponse : DTO
Money : -
Account : DTO
Felsoroltam néhány érvet, de szeretném, ha írnátok hozzá ti is, illetve megvitatnánk a meglátásaimat.
# | 1. domain object | 2. adat küldős |
---|---|---|
A | A domain objectek esetében sima adatlekérdezésnél oda-vissza konverzió van a business logic réteg határán a DTO-k és Entity-k között. | Ha csak adatot mozgatunk a rétegek határán, logikát nem viszünk át egyik rétegből a másikba, akkor a DTO-k újrafelhasználhatóak. Tehát ha pl a business logic kap egy DTO-t mentésre, akkor azt egyből továbbíthatja a data access-nek, illetve ha kiolvasás történik, akkor a kiolvasott adatokat egyből továbbíthatja a representation-nek. A business logic-nak tehát ilyen egyszerű műveletek esetén egyáltalán nem kell az adathoz nyúlnia. |
B | A domain objectek és az adatmentés módja között szoros a csatolás. Általában ezt annotációkkal próbálják lazítani, de attól még elég nehézkes egy az egyben lecserélni az adatmentés módját mondjuk adatbázisról fájlrendszerre vagy vissza. | Ha csak simán DTO-k küldözgetése megy a rétegek között, akkor sokkal egyszerűbb lecserélni az adatmentés megvalósítását. |
C | A domain objectek esetében nem kell külön figyelni az adatok mentésére, ezt az aktuális ORM megcsinálja helyettünk. | A DTO-k küldözgetésénél figyelni kell arra, hogy mentsük is a módosításokat a perzisztens tárolóba, de ez logikusan következik abból is, hogy csak az adatot használtuk fel. Ami aggasztó lehet, hogy nincs biztosíték arra, hogy a kiolvasás és a mentés között nem nyúl valaki az adatokhoz a perzisztens tárolóban. Úgy sejtem, hogy a PHP ORM-ek a domain object-es megoldás esetében valamilyen lock-al ezt megoldják, de ebben nem vagyok teljesen biztos. Pl egy nodejs esetében ilyen probléma nincsen, mert oda lehet adni teljesen ugyanazt az entity-t minden egyes rá vonatkozó lekérésnek. Szóval a konkurrencia kezelés a DTO-s megoldásnál úgy néz ki nehézkesebb, de erről várok véleményeket, tapasztalatokat. |
Nekem az eddigiek alapján
A legnagyobb problémám, hogy a domain objectek használata nem biztosít elég laza csatolást az adatbázishoz tartozó kód és a business logic között. Én sokkal inkább valami olyasmit képzelnék el, hogy az adatbázis beregisztrál eseménykezelőket az entity-khez, amit a business logic hoz létre, aztán úgy figyeli a változásokat, nem pedig ő maga példányosítja az entity-ket. Átnéztem pár architecture style-t, ami körül most nagy a felhajtás, clean, hexagonal, stb... és kb az jött át, hogy az a lényeg, hogy az részletkérdés, hogy az alkalmazás honnan kapja az adatokat, és hogy hogyan tárolja le. Az számít, hogy mi a belső logikája. Én arra gondoltam, hogy akkor a delivery method és a storage modulokat egy az egyben leválasztom egy-egy interface csomaggal, és így foglalkozhatok az alkalmazással magával. Ennek eléggé ellentmond az, hogy a domain objectek a storage modulban kell, hogy szülessenek, és nem pedig az alkalmazás hozza létre őket...
Olvasgatok, meg agyalok tovább...
Azért kell domain objecteket
A laza csatolás az adatbázissal problémádat nem értem. Kell egy repository interfész, ami biztosítja a perzisztens tárolóból való hozzáférést és perzisztálást. Ezt pedig az infrastruktúra rétegben implementálod. Mindig használj explicit mentést repositoryn keresztül, ne hagyatkozz az éppen használt ORM szolgáltatásaira!
A domain objected meg inkább legyen nagy, mintsem elszórva legyen a business logic megannyi stateless application serviceben.
A konvertálással kapcsolatban pedig már leírtam, hogy CQRS esetén nincs rá szükség.
Még annyi, hogy az updateBalance megvalósításod nem tartja be az invariánst, mert ha csak az egyik objektumon hívod és elfelejted hívni a másikon, máris mehetsz pereskedni a bíróságra. Inkább passzold át a sourcenak a destination objektumot is (valamint nevezd át a metódust valami beszédesebbre).
Még annyi, hogy az
Azt én is látom, hogy 2x van a kódban, de az olyan eshetőségeket, hogy nem mentem, egy rendesen tesztelt kód eleve kizárja. A 2 paraméteres metódusokkal ugyanúgy probléma van, pl transferMoney(source, dest) vagy transferMoney(dest, source). Az ilyen jellegű problémákra egyedül az jelentene megoldást, ha config object-ben adom át, vagy csinálok neki fluent interface-t, pl createMoneyTransferFrom(source).to(destination).
Hogyan lehet megakadályozni, hogy invalid állapotba kerüljön? A PHP-s ORM-ek, pl Doctrine esetén ez biztosítva van?
Azért jobb ezt a domain
Ilyen szabály például az, hogy nem mehet negatívba az egyenleg. Ezt az aggregateben kell betartatni. Erre találsz példát a doctrine dokumentációban.
Miért van szükség arra, hogy
Nincs szükség, sőt, ne tudjon
Nem tudom, lehet, hogy csak
Azt szerettem volna, ha csak data transfer objecteken keresztül kommunikáltak volna a moduljaim egymással. Úgy értem, hogy kölcsönösen tudnak egymásba adaptereket injektálni, és ezeknek az adaptereknek a metódusai szolgáltatják vagy várják a DTO-kat. Úgy néz ki, hogy a data access és a business logic között muszáj DTO-k helyett entity-ket átadnom, különben szinkront veszíthet az egész az adatbázissal. Ezt viszont szerettem volna elkerülni, mert valahogy nem tűnt helyesnek, hogy adaton kívül más is menjen a kettő között. Valószínűleg azért, mert vagy adat struktúrával dolgozok, vagy objektummal, viszont az tőlem teljesen idegen, hogy egy objektumnak vannak metódusai, de nincs adatrejtése... Legalábbis ezt gondoltam a domain objectekről. Most megnéztem ezt a konkrét példát, és pl az account-nál van adatrejtés a balance-ra...
Tudnál másik példát mondani erre a megoldásra?
Konkrétan egy szimpla CRUD-os példa érdekelne, mondjuk cikk listázást, létrehozást, olvasást, szerkesztést, törlést hogyan lehetne ilyen domain objectekkel leírni úgy, hogy közben rejtsék is az adataikat. Nekem régebbről java-s példákból az rémlett, hogy ilyenkor az entity nem rejti az adatokat, és simán le lehet róla másolni a property-ket egy DTO-ba... Na ez nem tetszene, mert vagy csak adatot tároljon az ojjektum, és legyen DTO vagy rejtse az adatait.
Miért van külön data access
Konkrét, viszonylag egyszerű és többnyire elégséges megoldás, amiben az írás/olvasás szét van választva (CQRS):
Írás
Ha még konkrétabb példára vagy kíváncsi, javaslom a már korábban belinkelt blogomat, a predaddy-t, valamint a hozzá készített egyszerű példát (ez utóbbi event source-os példa, abból is az a fajta, amikor nincs explicit command handler és repository mivel ezek automatikusan működnek, de pl. a read oldal teljesen hasonló).
Már elkezdtem olvasni.
Próbálok clean architecture szerint, legalábbis azon alapelve szerint eljárni, hogy a business logic-nak tök lényegtelen kell, hogy legyen, hogy milyen adattároló megoldás van alatta, vagy hogy milyen ORM-et használ (ha egyáltalán használ) ahhoz, hogy az adat tárolást elérje. Ez azért van így, mert a business logic-nak valami olyasminek kellene lennie, ami teljesen keretrendszer független, és ezért gyorsan tesztelhető. Az összes keretrendszer függő részt, mint pl a data access ezért le kell választani róla adapterekkel. Ez a része meglepően egyszerű, egyedül egy modul futtató környezetet kellett csinálnom hozzá, ami párszáz sorból megoldható.
Egyelőre csak kísérleti jelleggel, de próbálok valami olyat létrehozni, ahol nem csak vertikálisan (rétegekbe), de horizontálisan (témakörök szerint) is szét vannak választva egymástól a modulok. Most leginkább arra vagyok kíváncsi, hogy vajon javul vagy romlik e a kód olvashatósága és tesztelhetősége, meg hogy egyáltalán megvalósítható e mindez kellő rugalmassággal... A stateless - stateful kérdésnél, amit említettél, ütköztem akadályba jelenleg...
Nagyon le akarod választani
Okés! Ha mondjuk ORM helyett
Ha mondjuk ORM helyett kézzel írott SQL-ekkel nyúlnék az adatbázishoz, egy kis forgalmú oldalon, akkor szerinted mennyivel nőne meg annak az esélye, hogy eltörik a data integrity? Egyáltalán a PHP-s ORM-ek biztosítják, hogy a konkurrens kérések ne zavarják egymást? Vagy ha mondjuk egy domain object több tárolási módot használ, pl fájlrendszer és adatbázis is, akkor hogyan tudom biztosítani, hogy a kettő szinkronban maradjon?
Jó, túl sok lesz a kérdésből... :-) Valamiért nem vagyok kibékülve az ORM-ek használatával, mert nagy rendszerek, és nem értem, hogy olyan egyszerű dolgokhoz, mint egy-egy sql megírása miért kell egy ekkora keretrendszer... Nem elég szimplán mindent bepakolni tranzakcióba, aztán annyi?
Concurrency issuek
Nem, a tranzakció nem elég, és érdekes kérdés, hogy lehet szinkronban tartani két repositoryt. Ilyen problémába még nem ütköztem és szerintem nem is nagyon jön elő a való életben, így első látásra szerintem csak eventual consistency érhető el.
Ha nem tetszik az ORM, mondanám, hogy használj Event Sourcingot (ES), de nem mondom, mert - nem bántásból - előbb próbálj meg normálisan használni ORM-et, tudd, mi az a concurrency és hogy kezelhető, használj DDD-t, majd CQRS-t, és majd utána jöhet az ES, ha egyáltalán szükség van rá. Sok ember szidja az ORM-eket, de nagy részük rosszul használja. A DDD és egyáltalán az aggregatekben való gondolkodás sokat segít és eliminál nagyon sok problémát.
Én egy kicsit máshogy
Nekem ilyesmi akkor szokott előjönni, ha képeket csatolok valamihez. A képek másolásánál is lehet valamilyen fájlrendszer hiba... Ez a fajta probléma általában nem annyira bonyolult, először a fájlt teszem a helyére, utána küldöm el a tranzakciót, aztán ha valami nem stimmel, akkor törlöm a fájlt. Szóval egyelőre csak soros megoldást tudok, párhuzamosat nem, pedig az lenne az igazi...
Köszönöm a linkeket, azt hiszem már csak az olvasás van hátra... :-)
Concurrency issuek
Persze, hogy előjöhetnek, az ORM egyik feladata (gondolom én) a lockok automatizálása, hogy ne kövess el ezzel kapcsolatban hibákat.
Próbáltam keresgélni concurrency test-el kapcsolatos cikkeket, megoldásokat, de az eddigiek alapján úgy néz ki, hogy extrém nehezen tesztelhető dologról van szó. Találtam C++-hoz meg visual studio-hoz egy chess nevű tool-t, ami jó ilyesmire, de ennyi, kifújt...
Amit még írtak, hogy google go alapból úgy lett tervezve, hogy kezelje a konkurrenciát, bármit is jelentsen ez... :-)
Kérdés az, hogy egy kis látogatottságú oldalnál megéri e egyáltalán foglalkozni concurrency management-el... A leggyorsabb megoldás ilyen szempontból tényleg az, hogy beszórsz egy ORM-et az oldal alá, aztán kész...
Ahogy nézem doctrine sem valami jól tesztelt concurrency témakörben. Én legalábbis egyáltalán nem találtam ezzel kapcsolatos teszteket, de lehet nekem maradt ki valami... Közben találtam olyat sto-n, hogy doctrine-ben kézzel kell megadni, hogy milyen típusú lock-ot akarsz, de ez mondjuk 1 éves topic volt. Szóval a doctrine sem csodaszer ezzel kapcsolatban...
Összességében nekem az jött le, hogy a concurrency-vel kapcsolatban jobban járok PHP esetében, ha végigolvasom a pgsql dokumentációban a transaction isolation-al és a lock-al kapcsolatos részeket. Már így is jó ideje hiányosságom ez a témakör, csak nagy vonalakban vagyok tisztában azzal, hogy mennyi szopás van vele...
google go alapból úgy lett
Ettől függetlenül mindenképp több figyelmet igényel, mint egy átlagos, szinkron kód megírása, és csak nagyobb projekteknél lehet rá szükség, egy Apache vagy nginx nagyon sokáig elég.
Ettől függetlenül mindenképp
Én is úgy látom, hogy kis terhelésnél nincs szükség rá, mert nem valószínű, hogy ilyen jellegű problémák jelentkeznek. Majd akkor esek pofára, amikor kiderül, hogy mégis... :-)
A Doctrine-ban az API
De szerintem ez már nagyon offtopic, lévén a domain objectek ORM függetlenek.
Ok, szóval doctrine-ben
Senki nem mondta, hogy nem lehet elkanyarodni az eredeti témától. Én speciel pont ezt szeretem a weblaboros fórumban...
Nem, nem csak optimistic lock
Félreértetted, az
Ha már így szóba került, akkor elolvasom az ezzel kapcsolatos részt is.
http://docs.doctrine-project.org/en/2.0.x/reference/transactions-and-concurrency.html
Arra gondoltam, hogy domain objectek helyett át lehetne adni egy task listet is a db-nek, hogy ezeket hajtsa végre ilyen és ilyen callback-ekkel, így atomi lenne a két réteg között az információ átadás, de valószínűleg könnyebben karbantartható a domain objectes megoldás.
Olvasgatok ezekben a
introduction-to-domain-driven-design-cqrs-and-event-sourcing
Egyelőre minden világosnak tűnik, bár vannak kérdéseim. A legfontosabb, hogy az ES-nél hogyan akadályozzátok meg, hogy kifussatok a tárhelyből (ha pl a képeket fájlokban tároljátok, és sosem törlitek azokat sem), vagy hogy a túl sok record miatt totál belassuljon az adatbázis?
Egyelőre csak arra tudok gondolni, hogy bizonyos időközönként archiválni kell a dolgokat valamilyen más tárolóba, egyesíteni a régi event-ek hatásait, és törölni a régieket, illetve a fájlokat. Gondolom a nagy rendszerek, mint pl fb is valami hasonló módon működnek, ezért lehet egy-egy képet még évek után is elérni, holott már elméletileg régen törölve lettek. Ők valszeg sosem archiválnak, hanem inkább bővítik a tárhelyet. Bár újabban azt vettem észre fb-nál, hogy pár hét vagy hónap után már nincsenek a history-mban a megosztásaim.
A másik érdekes probléma, hogy a cqrs és a ddd hogyan jön össze?
Ha jól értem a cqrs arról szól, hogy elválasztjuk az írást az olvasástól azért, hogy az olvasás jóval gyorsabb legyen, ne kelljen egy csomó mapping-en keresztül mennie, hanem azonnal be tudjuk szórni az adatot a viewmodel-be. Szóval az olvasás egy csomó réteget átugrik, amik az írásban benne vannak, és ebbe akár a domain layer is beletartozhat, vagy nem?
A másodikat meg tudom
Az ES-ről még olvasok, határozottan tetszik a hagyományos adattárolási megoldásokhoz képest, sokkal egyszerűbb megoldás, és határozottan hasznát tudom venni. Használtam már ugyanezt a megoldást, amikor az árazásnak kellett nyomonkövetni a történetét egy webshopban, szóval ez csak annak a kiterjesztése. Gondolom fölé lehet szórni egy jó csomó view-ot, és végeredményben ugyanazt kapjuk olvasás szempontból, mintha rendes tábláink lennének. Írás szempontból viszont sokkal könnyebb dolgunk van, plusz még sokkal több adatot le tudunk kérni, tudunk szép grafikonokat rajzolni, szóval a menedzsment is el lesz ragadtatva...
ES-nél szerintem a megfelelő
Lehet úgy is rendszert építeni cqrs-el kombinálva, hogy külön adatbázist használunk a domain event-ekhez, és külön adatbázist az olvasásra. Az event-eket pedig egy bus-ra rakva egyesével végre lehet hajtani az olvasásra szolgáló adatbázison, és így szinkronba hozni a kettőt egymással konkurrencia vagy hibalehetőség nélkül. Annyi hátránya van ennek a megoldásnak, hogy az olvasásra szolgáló adatbázis nem feltétlen lesz szinkronban az event-eket tárolóval, főleg ha nő az írással kapcsolatos terhelés. Én szerintem kisebb terhelésnél nincs szükség ilyesmire, és elég csak view-okat építeni az event-ek fölé (ha relációs adatbázisban tároljuk őket), vagy bármi mást, ami aggregálja a bennük tárolt adatot, és kesseli az aktuális állapot lekérését.
Event Sourcing
Mivel a tárhely manapság olcsó, ennél szerintem egyszerűbb és gyorsabb, ha a teljes sorról készítesz másolatot, ad absurdum a napi mentésből nyered vissza az adatokat.
Abban látok valamennyi
A külön read db-s és event storage-es megoldás egyáltalán nem jön be. Websocket-el el tudom képzelni, ahol event formájában kapnám meg a választ, viszont HTTP-vel nem értem, hogy hogyan lehet összeegyeztetni. A szinkronizálás a read db és az event storage között teljesen független az aktuális kéréstől, így nincs mód visszaküldeni bármilyen választ php esetében. Esetleg nodejs-nél be lehet tenni ugyanabba a daemon-ba, és lehet hozzácsapni eseménykezelőt, de így is egyrészt bonyolult, másrészt meg lehet, hogy időben annyira el lesz tolva a frissítés, hogy simán timeout-ol a böngésző vagy a webszerver. Egyáltalán nem tűnik nekem PHP-re szabott megoldásnak.
Ahol szerintem kifejezetten hasznos lehet az a kliens oldal. Ott le lehet tárolni js-ben, vagy bármi másban a lépéseket, aztán be lehet tenni a végére egy apply gombot, hogy tényleg menteni akarom, és egy kérésben elküldeni mindent a szervernek. Ritkán fordul elő azért ott is olyan probléma, hogy egy ilyen megoldást kellene alkalmazni.
Kicsit utánajártam azóta
event sourcing előnyei:
- high availability (nagyon sok felhasználót ki tudsz szolgálni egyszerre, mert nem kell a concurrency kezeléssel foglalkoznod) vagy realtime visszaadnod az eredményt
- bármikor visszaállíthatod az adatbázist egy korábbi állapotba
- bármikor készíthetsz backup-ot különösebb erőlködés nélkül, elég az event-eket lemásolni
- bármikor módosíthatod a read adatbázisod szerkezetét, elég utána újra lefuttatni az event-eket és feltölteni a read adatbázist az új szinkronizációs kód segítségével (így pl a deploy-t is lehet teljesen automatizálni, nem kell kézzel hozzáigazítani az új szerkezethez a tárolt adatokat)
event sourcing hátrányai:
- általában eventual consistency-vel használjuk, legalábbis általában a high availability miatt szokták választani. emiatt eleve úgy kell tervezni az UI-t meg minden mást, hogy ennek megfeleljen. sok esetben ez működőképes, sok esetben viszont azonnali eredményt várunk... vannak megoldások, amikkel lehet tákolni a rendszeren, hogy növeljük a user experience-t. ezekhez általában a session-ben kell letárolni az elküldött kéréseket...
- szükség van konfliktus feloldó kód írására a szinkronizációhoz, bár ez lehet skip és hibaüzenet küldés ugyanúgy, mint egy normál rendszernél
- be kell lőni a cron-t vagy daemonokat kell használni a szinkronizációhoz, mert az nem azonnali
- kétféle aggregate kódot kell karbantartani, az egyik a write model-ben a lekérdezések (általában csak id szerinti lekérések), a másik meg a read model lekérései, vagy inkább a read model cache-ének a feltöltése, ha olyan a rendszer
Összességében szerintem csak akkor éri meg használni, ha nagy terhelésű a rendszer, egyébként nincs értelme foglalkozni vele.
Ez nincs így, mert általában külön adatbázist használnak a cacheléshez, szóval az olvasás mindig baromi gyors, az írásra viszont várni kell terheléstől függően, és nem kapsz azonnali választ, hogy sikerült e vagy nem (legalábbis ha eventual consistency-vel használják).
Ha egy processen belül oldod
Még mindig azt mondom, hogy a read storageba ne rakj semmiféle constraint.
Az unique email érdekes kérdés. Viszonylag kevés esetben van ilyen probléma egy rendszerben, de két lehetséges megoldás:
- Az aggregate ID-jét az emailből képzett hashből állítod elő, így írásnál hiba lesz ütközés esetén. Ez nyilván akkor nem elégséges, ha egy aggregateben több unique adat van.
- Használhatsz hibrid rendszert, pl. az ilyen típusú aggregatekhez csinálhatsz explicit táblákat, ahová már rakhatsz unique contraint.
A második megoldást javaslom: authentikáció során nem ID alapján keresed meg az aggregatet, hanem email és jelszó hash alapján. Az authentikációs/authorizációs feladatok külön bounded contextet képeznek, ahol eldöntheted, érdemes-e ES-t használni. Úgy tűnik nem. Viszont a felhasználó más jellegű adatai - amelyek függetlenek az említett feladatoktól - már más BC-be tartoz(hat)nak, ahol használhatsz ES-t.
Nagyon sok esetben a
Általában szétkapják modulokra a kódot, és bounded contextet használnak, akár teljesen eltérő jellegű read database-ekkel. Ahol key-value vagy column a jó, ott nosql-t, ahol meg relációs ott sql-t... Na jó ez mondjuk már tényleg a nagy rendszerekre vonatkozik, azt hiszem amazonról láttam egy videot (talán ezt), hogy ott így csinál(hat)ják. A szépsége az a dolognak, hogy a domain eventek miatt laza a csatolás, az event sourcing miatt meg nem igazán kell tartani adatvesztéstől, vagy bármi egyébtől, szóval gyakorlatilag azt és annyit teszel az event storage elé vagy mögé read model-nek, amit nem szégyellsz. Gyakorlatilag query cache-nek van, semmi többnek...
off:
Nagyjából megint összeszedtem egy cikkre való anyagot DDD, CQRS, event sourcing témában.
Annyi hátránya van ennek a
Így van, ez az eventually consistency. A legtöbb esetben valójában bőven elégséges megoldás az, ha nem azonnal jelenik meg az adat a képernyőn. Ha kevés az írás, akkor szinte azonnal megjelenik az adat, ha meg sok, akkor hagyományos módon megoldva belassulna a rendszer és sokáig tartana egy-egy write request.
Korábbiakra válaszolva:
Lehet DDD-t használni CQRS nélkül, lehet CQRS-t használni ES nélkül, sőt ezt kb. fordított sorrendbe is le lehetne írni. Nincs éles tapasztalatom ES-sel, és azzal, hogy viselkedik egy nagy DB-n, de ha snapshottolsz, akkor elég korlátos mennyiségű eventet kell betölteni, amelyekre az indexelt aggregate ID alapján szűrsz, és amelyeket version alapján rendezel. Egyébként a snapshotok előtti eventeket törölheted a DB-ből, nincs szükség rájuk (ha csak később neked nem lesz rájuk szükséged).
Mennyivel más ez, mintha ugyanabban a DB a read storage is? A szinkronizációt ugyanúgy PHP-ben, programozottan oldod meg. Bár csak POC, kb. ennyiből áll két event lekezelése és a read storage frissítése.
Ha egy command/event, szóval bármilyen message feldolgozása közben hiba van, érdemes azt és a kivételt egy error queue-ba irányítani, így egyrészt azt lehet monitorozni, másrészt később egy esetleges hibajavítást követően újra fel lehet azt használni.
Így van, olvasásnál nem kell a domain, nem kell az üzleti logika, ott pusztán adatokra van szükség, amelyek a megfelelő formában, lehetőleg minél gyorsabban rendelkezésünkre állnak.
Nem tudom, linkeltem-e már, itt egy általam elkövetett ES-es példa projekt, csak hogy látni lehessen, mégis eszik-e vagy isszák, meg hogy néz ki minden predaddyvel.
Nem tudom linkelted e már,
Nem a szinkronizációval van bajom, azt egyszerűen meg lehet oldani. A probléma az, hogy kérés-válasz alapú megoldásoknál nem működik az event sourcing, mert teljesen eltérő consistency model-t használ. Egy REST-hez immediate consistency kell, hogy azonnal vissza lehessen küldeni a válaszban, hogy mi történt, pl egy erőforrás létrehozásánál automatikusan át lehet irányítani az erőforrás url-jére, stb... Az event sourcing viszont eventual consistency-t használ, szóval a kettő teljesen üti egymást. Olyan delivery megoldásoknál, ahol elégséges az eventual consistency, az event sourcing egy jó megoldás lehet. Ahol immediate consistency kell, ott is le lehet menteni az adatok mellett az event-eket is, és fel lehet használni őket backup vagy migrációs célokra, esetleg történeti áttekintést nyújtó funkciók fejlesztésére. Szóval az alap ötlet, hogy mentsük a domain event-eket, a REST esetében sem teljesen értelmetlen...
Magát a szinkronizációt személy szerint nem tartom egy jó ötletnek, mert können sérülhet a data integrity. Ha több domain event van egy tranzakción belül, akkor a tranzakciós határokat az event storage nem fogja tárolni, és előfordulhat, hogy az egyik event gond nélkül bekerül a read db-be, míg a másik valamilyen constraint megsértése miatt nem. Ezzel a megoldással gyakorlatilag a tranzakciók semmit sem érnek. Ez nyilván azért van, mert megpróbáljuk kézzel lekezelni őket késleltetve, ahelyett, hogy az adatbázisra bíznánk...
Az eventually consistency
A read storageben felesleges bármiféle constraint rakni, lévén a domain eventek validált business folyamat eredményeképp keletkeznek, vagyis nem kell őket újra validálni - ami megtörtént az megtörtént. Ha szinkronizáció során egy event feldolgozása sikertelen, az valamilyen "egyszerű" hiba miatt lehet, mivel itt nem szabad komplex logikának lennie. Épp ezért talán elégséges az előző bejegyzésemben írt error queue-s megoldást alkalmazni.
Jó, de hogyan tudok egy
Ez egy baromi jó előadás a
Nekem az jött le, hogy az eventual consistency a legtöbb esetben elfogadható, mármint nagyon sok esetben, ha belegondolunk. Általában elég az új állapotot csak az azt létrehozó kliensében jelezni, és a szerveren nyugodtan tárolhatjuk esemény formájában a módosítást, amit valamikor a jövőben végrehajtunk, vagy elvetünk... A gond itt az egymásra épülő command-eknél van. Ezeknél megkerülhető az azonnali válasz, ha a kliens-ben tároljuk a módosításokat, és folyamatosan küldjük el azt a szervernek a háttérben. Így a kliens által megjelenített adat folyamatosan up to date. Viszont így belefuthat a felhasználó olyan helyzetekbe, hogy dolgozott a módosításon egy csomót, és egy konkurrens módosítás miatt az egész munkája elveszett, holott végig azt hitte, hogy sínen van minden. Ez ami nagyon nem jó ES-el kapcsolatban, meg ezzel az aszinkron read-write szinkronizációval kapcsolatban. Ha ezt sikerülne megoldani valamilyen módon, akkor leválthatná a most elterjedt ORM-es, relációs megoldásokat.
Eventual consistency in REST APIs
Ez is azt írja, hogy feladat függő a dolog, szóval hogyha immediate consistency-re van szükség, akkor azt kell használni, ha meg nincs rá szükség, akkor visszatérhetünk egy 202 accepted header-el REST-nél, aztán később feldolgozzuk a kérést.
Ahogy nézem vannak alternatívái az event sourcing-nak. Eventual consistency a read modelnél elérhető úgy is, hogy erre felkészített adatbázist választunk:
Choosing Consistency By Werner Vogels (Amazon SimpleDB)
Ami még érdekes lehet, hogy kis terhelésű rendszereknél lehet szerver oldali read model nélkül használni, ha csak id alapú lekérések vannak, és nincsen keresés... Bizonyos időközönként a szerver csinálhat snapshot-okat, ha lassú lenne, de maga a kliens is megteheti ezt mondjuk localstorage segítségével... Na meg persze lehet takarítani az event-et sorát, hogyha pl egy created és egy deleted kioltja egymást, akkor sokszor nincs sok értelme bennhagyni... Ezeket az event alapú megoldásokat pl el tudnám úgy képzelni, hogy minden user bizonyos event-eket figyel, aztán ha olyan event érkezik a domain model-től a kérések validálása után, akkor megkapja azt... Egy chat-et pl baromi egyszerűen meg lehet valósítani ilyen elven, de szerintem azon kívül még baromi sok dolgot is...
off:
Én pl most azt tervezem, hogy robotoknál a sampling rate-et befolyásolom az alapján, hogy a feldolgozási sorban mennyi event van, illetve hogy ezeknek a száma időben nő vagy csökken... Na jó ez a része egyelőre csak fejben van meg, még nem kezdtem el komolyabban foglalkozni a robotozás témájával, de kapásból egy érdekes kérdés, hogy socket-ekkel event alapon csináljam e vagy rest-el kérés-válasz alapon... Az egyik arról szól, hogy folyamatosan kapom az adatokat a szenzor hálózatról, aztán figyelem őket, és reagálok, a másik meg arról szól, hogy csak akkor kapok adatot, ha kérek... Nekem az első megoldás ami szimpatikusabb, de valószínűleg gyorsabban fogja meríteni az aksit, mint csak akkor kérni adatot, ha tényleg kell. Na most már tényleg nagyon elkalandoztam :P
Csatolás
Jó esetben ez nem igaz. Nem PHP-s példa, de a JPA például pont standard annotációkat is definiál, ami alatt elvileg kicserélhető az ORM. - Természetesen ilyenkor le kell mondjunk a finomabb hangolás lehetőségeiről ami eszközfüggő.
Azonban enélkül sincs nehéz dolgunk. Ha egy XML alapú konfigot használunk (annotációk helyett), akkor mondjuk egy XSLT segítségével konvertálhatunk a két ORM konfigurációja között.
Egy másik lehetőség, hogy saját annotációkat vezetünk be, és ehhez írunk egy konfig-generálót (én pl Doctrine-hoz megtettem), ekkor az ORM lecserélésekor (a konfig-generátort kell megírni az újabb eszközhöz).
Ha a réteget amivel kapcsolatban vagyunk lecseréljük (legyen az ORM, vagy adatbázis API), mindenképpen megrázkódtatással jár hacsak nem a lehető legminimálisabb funkcionalitást használtuk.
Engem inkább olyan jellegű
Limitációk
Heterogén perzisztencia esetén ha egy objektum rendelkezik olyan mezővel, amit egy másik eszköz felügyel, azt kénytelen vagy tranzient-nek jelölni, és manuálisan (vagy talán egy köztes rétegben) gondoskodni arról, hogyan kell betölteni a gráf azon részét.
Ez utóbbit el tudom képzelni AOP segítségével, ahol az illető getterre rákapcsolsz egy advice-t ami elvégzi a betöltést a másik eszközzel. Így nem szennyezed be a business objektumodat adateléréssel és megmarad a kényelem. (Ez nem különbözik sokban attól, ahogy az ORM egyébként is végzi a lazy loadingot, azaz hogy eltéríti a gettereket egy proxy objektum segítségével). - Erre nem hizsem, hogy sok esetben szükség lesz, vagyis viszonylag kevés kapcsolódási pontot képzelek el a gráf egyik helyen tárolt részéről a másik felé.
(Tipikusan olyan feladatotra gondolok, mint egy GoogleDocs-szerű dokumentumszerkesztő, ahol a metaadatok lehetnek RDBMSben, a dokumentum tartalma meg mondjuk MongoDBben. - Két jól elkülöníthető gráf, itt konkrétan egy darab kapcsolódási ponttal).
Egyelőre nem látok olyat,
Érdekes, megpróbálok keresni, szerintem biztosan vannak ilyen eszközök is.
Hallgass az eszedre!
Buzzword Driven Development Isn't Gonna Help You
Már vártam valami hasonló
Maga a cikk nem hülyeség,
Pontosan. Mert mi van akkor,
Nem veszel figyelembe egy
Nem igazán tudok mérőszámokat arra, hogy mikortól éri meg kézzel írt SQL-ek meg spagetti kód helyett DDD-re váltani, de én úgy vagyok vele, hogy nekem már 1 sor kód után jobb érzés oo kódot írni, mert az szép, a másik meg ronda. A szépség persze relatív... :-)
Gondolkodhatsz akármennyit,
A szépségről pedig neked is linkelek egy nagyon jó bejegyzést: Beyond clean code.
As an industry, we have come to value code that looks pretty. We have come to value readability and correctness. But for some reason, we never really talk about the fact that said code needs to deliver business value. The cleanest code that doesn't deliver value is still crap.
Ezt az idézetet nem értem
+1
Bennem is ez merült fel.
1nf3rn0 következő mondatára
a megfelelő eszközt célszerű
Én úgy gondolom, hogy az én megoldásom a megfelelő eszköz, te meg úgy gondolod, hogy a tiéd, 1:1, hozz bizonyítékot!
1:1
1, Egy újabb CMS-t kell írni, azaz semmi újat nem kell kitalálni, már csináltál ilyet, így pontosan tudod, hogy milyen eszközöket (jelen esetben paradigmákat, például tervezési mintákat, absztrakciókat) fogsz felhasználni. Ilyen esetben a te megoldásod a megfelelő eszköz.
Ezen eszközök használatával gyorsan lehet hatékony kódot írni.
2, Egy új ügyfél egyedi problémáját kell megoldani. Ilyenkor elsőre csak egy célt tudsz kitűzni, amit el kell érni, de még a legalaposabb tervezés után is egy csomó minden fejlesztés/használat közben derül ki. Nagyjából amikor elkészül az első változat, akkor látod át, hogy mire is volt szüksége az ügyfélnek, és ekkor kezdheted el igazából refaktorálni a kódot, előtte mindenképp spagetti lesz. Ekkor kerülnek bele a különböző minták, ekkor ismered fel, hogy mit lehet/kell absztrakcióval megoldani.
Amennyiben előre definiáltad volna, milyen eszközöket használsz, akkor előre meg is kötötted volna a kezed, ami valami módon mindenképp a munka kárára válik.
Létezik egy harmadik lehetőségi is: amennyiben a birtokodban van a Bölcsek Köve, úgy olyan módszert alkalmazol, ami csak tetszik.
2, Egy új ügyfél egyedi
Amennyiben előre definiáltad volna, milyen eszközöket használsz, akkor előre meg is kötötted volna a kezed, ami valami módon mindenképp a munka kárára válik.
Ez így egyáltalán nem igaz. Le kell ülni az ügyféllel, megbeszélni mit akar leírni a user story-t, csinálni belőle use case-eket, és azok alapján már lehet TDD-vel megvalósítani. Abban igazad van, hogy az eszköz választást jobb minél későbbre hagyni, de a hexagonal architecture, clean architecture, DDD, stb... pont ilyen célból lett kitalálva, hogy ne kelljen azonnal eszközt választani, csak meghatározni felületeket, amiket használni fogsz a projektedhez. Pl adatbázis helyett simán betehetsz minden repository-t egy-egy json fájlba és csinálhatsz egy fájlrendszeres modult. Később meg ha úgy gondolod, akkor hozzácsaphatsz egy mysql-es modult, ha a relációs adatbázis mellett döntenél... A projekt többi részének teljesen mindegy, hogy milyen eszközöket használsz a perzisztens adat tárolásra. Ugyanígy tökmindegy kell, hogy legyen nekik a delivery method, szóval hogy mondjuk REST Api-n keresztül, vagy hagyományos webes felületen kapják e a kéréseket. Persze lehet úgy is fejleszteni, ahogy te mondod, hogy később refaktorálod, csak sokkal kevésbé hatékony, és nem feltétlen kapsz egy átgondolt szerkezetet a végén...
egy csomó minden
Egy tapasztaltabb fejlesztő tudja, hogy milyen rétegei lesznek annak az alkalmazásnak, amit le kell fejleszteni. Akkor is, ha esetleg a speckó változik közben. Nemkülönben ha betartasz bizonyos szabályokat, laza csatolást használsz stb, akkor viszonylag fájdalommentesen tudsz leválasztani/hozzárakni/átírni dolgokat, hiszen egy viszonylag jól izolált részen dolgozol, bármihez is kell nyúljál. Míg ez nagyon nem igaz annál a megközelítésnél, amit felhoztál ellenpéldának.
Az én világomban a szépség és
Hohó, azért az, hogy csak úgy
Viszont ne azért használjuk őket, mert csak, hanem mert az adott problémát hatékonyan megoldják (ez feltételezi, hogy ismerjük az adott buzzwörd mögött lévő tényleges dolgot), hatékonyabban, mint a másik XYZ buzzwörd (tehát össze is tudjuk őket hasonlítani).
Praktikák
Cserébe itt egy ellenvélemény, és ennek is van komoly alapja: Reinvent the wheel!.
Más dolog éles projektet
Ahhoz, hogy szakmabeliek
Hány
És a helyzetet még az is
A szakma nyelve az angol.
Ehelyett csak annyit mondok:
Bocsi :D
Miért ORM?
Off
A PDO egységes felületet ad
Az eredeti kérdés a domain objectekre vonatkozott, az ORM-ek csak úgy kerültek be a témába, hogy ők képesek automatizált módon létrehozni ilyeneket. De meg lehet csinálni a domain objecteket kézzel is, ugyanúgy SQL-t kell írni, meg lockokat beállítani, meg anyámkínja... Adat struktúrákkal is ugyanúgy meg lehet csinálni ugyanazt, csak ugyanannak a kódnak kell lefutnia, mint amit a domain object csinál. A domain object-ek használata abban segít, hogy leválaszd az adatelérést az üzleti logikájáról az alkalmazásodnak. Ezt általában elég nehéz megtenni, mert sokszor nem tudod eldönteni, hogy mi tartozzon egyik, és mi a másik rétegbe, illetve mi legyen a határfelületükön. Ennek az az oka, hogy SQL-ben sok olyan dolgot meg lehet csinálni az adatbázis szerveren, amit amúgy a PHP-re vagy más HTTP szerveren futó nyelvre bíznál. Ha úgy döntesz, hogy valamit SQL-re bízol, akkor az a data access-be vándorol, ha meg úgy, hogy PHP-re bízod, akkor meg dönthetsz, hogy a data access-ben vagy a business logic-ban legyen. Én jelenleg nem tudok az ilyen jellegű döntésekre biztos receptet, de kíváncsi vagyok, hogy a nálam tapasztaltabbak hogy szokták megoldani, az ilyen jellegű problémákat...
Objektum gráf, perzisztencia
A DQL érthető, PDO jellegű szerkezet. Inkább ez mint a QueryBuilder. Az egy vicc.
Mondjuk YAML formában tárolom a tábla-mező szerkezetet. A mezőnkénti adatbázis-objektum megfeleltetéshez nekem kell kódot írnom, amit az ORM meghív szükség esetén, vagy végrehajt valami varázslatot?
Van róla magyarázat emberi nyelven?
( Találkoztam jó pár angol szóval, melyekről kiderült nem újak csak máshogy ismertem őket, például: hash array - asszociatív tömb, callback function - amit a szoftveres megszakítás végrehajt, serialíze - memória változók mentése sztringe/fájlba.
Aztán vannak értelmezési problémáim, egyik kedvencem a dependency injection. Oké, lefordítható magyarra is, de mire való? Tévedés joga fenntartva - úgy hiszem, az objektumok elérését teszi lehetővé, pont úgy mint a normál függvények. Az objektumok egyetlen példányváltozóba vannak összevonva, a teljes szerkezet globálissá van téve. )
Dependency Injection
Tegyük fel, hogy függetleníteni szeretnéd tőle, például teszteléshez vagy lehetővé szeretnéd tenni, hogy más adatforrásokkal dolgozhasson egyszerűen a programod. Ilyenkor nem közvetlenül példányosítod a csatolót a kódban, hanem átadod paraméterként (beinjektálod).
Függetlenség
Látszólag helyesen jártunk el. Nem találtuk fel újra a kereket, szabványosan dolgoztunk, követtük a nyájat és kudarc.
Úgy gondolom a függetlenséget a "wrapper" (burkoló osztály?) biztosítja. Az a belsejében persze függ valamitől, de számos hívásában már nem.
A DI-vel kapcsolatban az az érzésem, más területen futott be karriert mint amiről beszélnek. (a viagra is szívgyógyszer lett volna) Ki lehet próbálni, használsz egy szerény objektum könyvtárat, mondjuk 20 különböző osztályt. Nincs a fő objektumba belegyógyítva a többi példánya, mindet külön adjuk át a függvényeknek ... ellentétben az egyetlen példányváltozóval. Nem jellemző.
Nekem elég volt ennyi agyalás.
Az adatbáziscsatoló ebben a
A DI-vel kapcsolatban az az
Függőség neve, teljes neve
Procedurális programozásban is van függőségi probléma, de nem kell ilyen gondossággal körbeprogramozni a használatot. A szükséges függvény meghívható bárhol. Objektumnál előbb példányosítás, ami változót generál. Azt a változót muszály elérni. Paraméteres átadással körbehordozni nemcsak egyszerűen ronda. Ahogy említettétek, a példányokat "objektum gráfba" szervezik, ennek egyik módja a DI. Ez a szervezkedés mint tervezési lépés még nem jött át semmiféle leíráson, pedig alapvető.
( Maga a DI technikailag közel áll a procedurális függvény használathoz, mivel szándék szerint egymás közti függőség nincs az egymás mellé halmozott készleten. A függvény külön előkészítés nélkül meghívható ott ahol állok.)
Függőség neve, teljes neve
Lehet tévedek, de mintha keveredne benned a dependency injection és a dependency injection container fogalma. És továbbra sem értem mit szeretnél ebből az egészből kihozni. A procedurális kód szoros és statikus függőséggel dolgozik, amit a programkód definiál. A DI pedig laza csatolással dolgozik, gyakorlatilag egy runtime függőségi gráf felépítése, amit tehetünk programkódból, konfigurációból, DIC-cel... Nem értem sem az összehasonlítást, sem annak okát. Valamint 2 kör után azt sem, hogy valójában mi bajod a DI-nel :)
Jobb mintával magyarázni.
oMain->oImport->oHelper("what?")->oWork("item")+1
Van némi ellenérzésem a fentivel kapcsolatban. A DI-t csak azért írtam mert az sokat emlegetett. Ezt tudom használni: o->Work() és ezt is Work(), ez elsőt nem igazán. A gyakorlatban mire jó a runtime gráf? ( Az implementációs függőség, függetlenség ebből nem derül ki például.)
A perzisztens azt jelenti
így nem kell
Attól még kellett volna...
Jobb így, hidd el!
Nem győztél meg...
Önellentmondás
Nyelv
Ezt írta inf3rno:
És Vilmos:
Szóval mindenképp hasznos lenne egy közös pont, egy szótárszerűség, ahol vagy angolul vagy magyarul le van minden definiálva, különben csak elbeszélnek az emberek egymás mellett.
Szóval mindenképp hasznos
Persze, én már szorgalmazom egy ideje, hogy hozzunk létre ilyen szótárat, de érdeklődés hiányában elmaradt...
Ezt a szótárat úgy hívják,
Bennem ez akkor merült fel,
Szótár
http://oktatlak.hu/kislexikon/szotar.pdf (931 oldal)
Az a baja, hogy csak egy