[PHP] DDD & Events icm Laravel

Pagina: 1
Acties:

Acties:
  • 0 Henk 'm!

  • Crazy-
  • Registratie: Januari 2002
  • Laatst online: 08-10 18:31

Crazy-

Best life ever

Topicstarter
Dit topic is deels een vraag; deels een discussie- vragen topic.

Zelf ben ik meer en meer gaan verdiepen in DDD, Clean Architecture & SOLID. M.i. is dit een mooie combinatie voor een duidelijke code en opzet.

Mijn volgende doel is om de UI laag compleet los te trekken van de onderliggende (App,Domain etc) lagen en dus framework (of whatever) onafhankelijk te zijn.

Hiermee ben ik een beetje aan te testen en loop tegen een dingetje aan.

Opzet is nu met een Laravel framework.

Issue en als doel: een order betalen

Nu komt er Request binnen in mn OrderController met een UUID als key

Het proces is alsvolgt:

Controller: paymentService->startPayment(orderuuid);

PaymentService:
mollieService->createPayment(order);
transactionService->createTransaction(payment,order);
If paid( orderService->setPaid(order); )


Echter nu loop ik vast; de orderService geeft een $this->dispatch( OrderIsPaidEvent); bij een betaling die gelukt is.

Echter deze zit ingesloten in de orderService->getEvents(); welke niet op te roepen is de Laravel controller en dus niet in queue van Laravel kan pushen

Ergens maak ik een misstap; wellicht dat ik meer richting met Commands (CQRS) moet gaan, maar daar ben in me nog in aan het verdiepen.

Het CRUD verhaal laat ik in deze even in het midden ;)

ik tracht een overstap te maken non DDD naar DDD naar framework onafhankelijke logica te programmeren

[ Voor 0% gewijzigd door Crazy- op 16-07-2017 12:32 . Reden: Ik zit lekker bij zwembad en zal later nog even wat duidelijker opzet maken van Mn topic ;) ]

12,85kWp - ZB 7,5m2/400l - 5kW Pana H WP (CV&SWW) - 13,8kWh accu


Acties:
  • 0 Henk 'm!

  • TheNephilim
  • Registratie: September 2005
  • Laatst online: 08-10 14:19

TheNephilim

Wtfuzzle

Zie https://laravel.com/docs/...ring-events-and-listeners, je kunt een event-listener koppelen om het event af te handelen. Dus de klant een orderbevestiging sturen bijvoorbeeld.

Eventueel kun je op de 'bedankt' pagina, alsnog checken of de betaling wel/niet gelukt is door de gegevens die Mollie mee terug stuurt.

1. Klant doorsturen naar betaalurl.
2. Klant komt terug op bedanktpagina.
3. Mollie roept webhook url aan, dus even controller aanmaken voor die webhook url en daar event dispatchen.
4. Event catchen met listener en orderbevestiging sturen ed.

Wellicht snap ik je probleem niet goed, maar leg dan even uit bij welke stap het mis gaat.

Acties:
  • 0 Henk 'm!

  • Crazy-
  • Registratie: Januari 2002
  • Laatst online: 08-10 18:31

Crazy-

Best life ever

Topicstarter
TheNephilim schreef op maandag 17 juli 2017 @ 10:26:
Zie https://laravel.com/docs/...ring-events-and-listeners, je kunt een event-listener koppelen om het event af te handelen. Dus de klant een orderbevestiging sturen bijvoorbeeld.
Laravel events & listeners is mij wel duidelijk; thank.
Echter de issue is dat mijn orderService()->payOrder(uuid); een nieuwe Event in een dispatch array zet. (Method trait in de service)

normaliter roep je deze service aan in je Controller en kan ik dus $orderServce->getEvents() loopen en event($event); doen in mijn Controller

Echter, ik roep niet orderService aan maar paymentService()

en ik denk dat daar dus de fout zit ... ik moet het vanuit een andere kant gaan benaderen (Ik wil een order betalen ... ofwel iets van PayOrder(uuid) in de orderService() welke weer de paymentService aanroept (??)
3. Mollie roept webhook url aan, dus even controller aanmaken voor die webhook url en daar event dispatchen.
dat is de issue juist > in de webhook doe ik de controle van de betaling en onderliggend wordt de order op Paid gezet.

[ Voor 11% gewijzigd door Crazy- op 17-07-2017 13:19 ]

12,85kWp - ZB 7,5m2/400l - 5kW Pana H WP (CV&SWW) - 13,8kWh accu


Acties:
  • 0 Henk 'm!

  • TheNephilim
  • Registratie: September 2005
  • Laatst online: 08-10 14:19

TheNephilim

Wtfuzzle

Je moet gewoon in die webhook controller je event dispatchen.
normaliter roep je deze service aan in je Controller en kan ik dus $orderServce->getEvents() loopen en event($event); doen in mijn Controller
Dit klopt in ieder geval niet volgens mij, je events loopen en dan afhandelen. Dat zou je met de listeners moeten doen.

Acties:
  • 0 Henk 'm!

  • Crazy-
  • Registratie: Januari 2002
  • Laatst online: 08-10 18:31

Crazy-

Best life ever

Topicstarter
TheNephilim schreef op maandag 17 juli 2017 @ 13:28:
Je moet gewoon in die webhook controller je event dispatchen.


[...]


Dit klopt in ieder geval niet volgens mij, je events loopen en dan afhandelen. Dat zou je met de listeners moeten doen.
Dat laatste klopt en gebeurd uiteraard ook > $this->depatch() is een Service method om in onze Application laag events te maken welke in ons Domain zitten.

Hiermee creeer je de mogelijkheid om framework onafhankelijk Events klaar te maken (Domain\Checkout\Orders\Events)

echter deze dien je uiteraard in je Controller te depatchen (event($event) in je framework Event storage (welke door de Listener wordt opgevangen)

zo kunnen we ooit een ander framework als UI gebruiken en onze domain events blijven gebruken; echter de FW depatcher veranderd in de controller.

Wat ik nu gedaan heb:

In de paymentController->webhook roep ik paymentServcie->checkPayment op m.b.v. de UUID
deze return de Transaction entity

daarna

PHP:
1
2
3
4
5
$transaction = $this->orderService->getOrder($paymentId);

if ($transaction->isPaid()) { 
    dispatchEvents($this->orderService->getEvents());
}


voor nu ... effectief en nog logisch ook op zich. (SRP)
wellicht dat ik beter (later) via Commands dien op te vangen ...echter heb ik hier de logica nog niet helder om dit toe te passen

Edit: ik zie nu ook waar ik de fout maak.
Een order ophalen obv een paymentId!? Ai Ai.
Dit refactor ik naar $paymentService->getOrderByPaymentId($paymentId); welke een Order teruggeeft.

Maar dan loop ik nog tegen het feit dat onderliggend event niet in de UI gestart zal worden en dit wil ik eigenlijk niet in de controller doen; stel dat we payOrder op een andere manier willen triggeren en de event vergeten zit je weer

[ Voor 20% gewijzigd door Crazy- op 17-07-2017 18:52 ]

12,85kWp - ZB 7,5m2/400l - 5kW Pana H WP (CV&SWW) - 13,8kWh accu


Acties:
  • 0 Henk 'm!

  • D-Raven
  • Registratie: November 2001
  • Laatst online: 07-10 10:25
Ik denk dat je je wat meer in DDD moet gaan verdiepen. Want het lijkt erop dat je een paar concepten mist.
Daarnaast, Commands zoals toegepast in CQRS staan los van DDD. Al is het wel een logische combinatie.

Maar het lijkt er nu op dat je de verantwoordelijkheden verkeerd hebt liggen. Zoals het nu eruit ziet, is je controller de laag waar je je orchistratie doet. Terwijl je dit eigenlijk in je Domain layer wilt houden.

Een paar gedachten:

Wanneer dispatch je de events welke gegenereerd worden door je Domain object. De meest logische plek, is op het moment dat je je Domain object saved. Dus bv:

- Order->SetPayment(paymentDetails)
- Order->DoeNogIets()
- Save(Order)

Het publishen van je Events zorgt voor side-effects in je domain (via je event handlers e.d.). Dus je wilt deze pas publishen als je zeker weet dat de acties op je Order object daadwerkelijk plaats gaan vinden.
Nu zijn er soms andere overwegingen, maar over het algemeen is direct na een Save de meest voorkomende plek waar je je domain events published.

Vervolgens doe je de benodigde orchistratie dus niet in je controller.

In je controller wil je gewoon tegen je domain vertellen dat er iets gebeurd is, of moet gebeuren. Als dat niet kan, omdat je domain vind dat er niet aan de juiste voorwaarden voldaan wordt. Dan ga je je error scenario in. Maar je wilt niet in je controller domain beslissingen nemen.
Het domain weet hoe betalingen verwerkt moeten worden, en wie hij daarvoor moet benaderen. De controller is puur de lijmlaag tussen het feit dat je in een web framework zit, en je domain.

Als je orchistratie tussen verschillende domain objecten nodig hebt, dan moet je je gaan afvragen wie is er nu eigenlijk verantwoordelijk voor het proces wat je aan het modelleren bent. Is dat de payment service? Of is dat meer een externe dependency, en is het het Order object? Of wellicht heb je zoveel eisen aan datgene wat je met een Payment kan doen, dat het eigenlijk een eigen object moet zijn. Welke een paymentComplete event triggered, welke op zijn buurt er weer voor zorgt dat het relevante Order object bijgewerkt wordt.
Er zijn meerdere manieren hoe je dit kan oplossen. Ga eens zoeken naar Aggregates en ProcessManager's/Saga's.

Acties:
  • 0 Henk 'm!

  • Crazy-
  • Registratie: Januari 2002
  • Laatst online: 08-10 18:31

Crazy-

Best life ever

Topicstarter
D-Raven schreef op dinsdag 18 juli 2017 @ 14:06:
Ik denk dat je je wat meer in DDD moet gaan verdiepen. Want het lijkt erop dat je een paar concepten mist.
Daarnaast, Commands zoals toegepast in CQRS staan los van DDD. Al is het wel een logische combinatie.

Maar het lijkt er nu op dat je de verantwoordelijkheden verkeerd hebt liggen. Zoals het nu eruit ziet, is je controller de laag waar je je orchistratie doet. Terwijl je dit eigenlijk in je Domain layer wilt houden.
de verantwoordelijkheden zijn nu nog niet helemaal perfect; niet zo zeer de volle onbekendheid, maar eerder verdieping nodig op het gebied van SOLID. DDD is (..) imho niet 1:1 SOLID.
Een paar gedachten:

Wanneer dispatch je de events welke gegenereerd worden door je Domain object. De meest logische plek, is op het moment dat je je Domain object saved. Dus bv:

- Order->SetPayment(paymentDetails)
- Order->DoeNogIets()
- Save(Order)

Het publishen van je Events zorgt voor side-effects in je domain (via je event handlers e.d.). Dus je wilt deze pas publishen als je zeker weet dat de acties op je Order object daadwerkelijk plaats gaan vinden.
Nu zijn er soms andere overwegingen, maar over het algemeen is direct na een Save de meest voorkomende plek waar je je domain events published.

Vervolgens doe je de benodigde orchistratie dus niet in je controller.
Omtrent de opzet ben ik het wel met je eens, nu is deze niet helemaal perfect. Hier ben ik nog een over aan het brainstormen. jouw gedacht heeft me weer aan het denken gezet.

wat logischer (en alles wat onafhankelijker maakt) is het volgende:

In de webhook Controller :

code:
1
 $orderService->updateOrderByPaymentId( $paymentId );

in de method updateOrderByPaymentId() zet ik een new Event() in een array welke in de $orderService->getEvents() zit

De reden hiervan is dat ik uiteindelijk in mn UI/FW de Events dien te dispatchen (in het geval van Laravel via een Listener)

En hier zit ik dus mee : waar maak ik de fout? Op 1 of andere manier dien ik die events in Laravel (lees: UI) laag te krijgen. Ik ben het met je eens dat het niet handig is dit op niveau Controller handmatig te doen (stel dat je dit vergeet...)
In je controller wil je gewoon tegen je domain vertellen dat er iets gebeurd is, of moet gebeuren. Als dat niet kan, omdat je domain vind dat er niet aan de juiste voorwaarden voldaan wordt. Dan ga je je error scenario in. Maar je wilt niet in je controller domain beslissingen nemen.
Het domain weet hoe betalingen verwerkt moeten worden, en wie hij daarvoor moet benaderen. De controller is puur de lijmlaag tussen het feit dat je in een web framework zit, en je domain.

Als je orchistratie tussen verschillende domain objecten nodig hebt, dan moet je je gaan afvragen wie is er nu eigenlijk verantwoordelijk voor het proces wat je aan het modelleren bent. Is dat de payment service? Of is dat meer een externe dependency, en is het het Order object? Of wellicht heb je zoveel eisen aan datgene wat je met een Payment kan doen, dat het eigenlijk een eigen object moet zijn. Welke een paymentComplete event triggered, welke op zijn buurt er weer voor zorgt dat het relevante Order object bijgewerkt wordt.
Er zijn meerdere manieren hoe je dit kan oplossen. Ga eens zoeken naar Aggregates en ProcessManager's/Saga's.
Bovenstaande (een paymentComplete event triggeren om daarna bovenliggende Order te updaten is ook een mooie manier. maar ook weer hier zit de crux : hoe zorg ik dus voor dat ik mijn events kan dispatchen in laravel zonder gebruik te maken van event($event); in mijn Domain laag?

12,85kWp - ZB 7,5m2/400l - 5kW Pana H WP (CV&SWW) - 13,8kWh accu


Acties:
  • 0 Henk 'm!

  • D-Raven
  • Registratie: November 2001
  • Laatst online: 07-10 10:25
Crazy- schreef op dinsdag 18 juli 2017 @ 22:29:
[...]


de verantwoordelijkheden zijn nu nog niet helemaal perfect; niet zo zeer de volle onbekendheid, maar eerder verdieping nodig op het gebied van SOLID. DDD is (..) imho niet 1:1 SOLID.
SOLID heeft niet het alleenrecht op het concept scheiding van verantwoordelijkheden :P
[...]


Omtrent de opzet ben ik het wel met je eens, nu is deze niet helemaal perfect. Hier ben ik nog een over aan het brainstormen. jouw gedacht heeft me weer aan het denken gezet.
Wat zou wel perfect zijn dan ?
wat logischer (en alles wat onafhankelijker maakt) is het volgende:

In de webhook Controller :

code:
1
 $orderService->updateOrderByPaymentId( $paymentId );

in de method updateOrderByPaymentId() zet ik een new Event() in een array welke in de $orderService->getEvents() zit

De reden hiervan is dat ik uiteindelijk in mn UI/FW de Events dien te dispatchen (in het geval van Laravel via een Listener)

En hier zit ik dus mee : waar maak ik de fout? Op 1 of andere manier dien ik die events in Laravel (lees: UI) laag te krijgen. Ik ben het met je eens dat het niet handig is dit op niveau Controller handmatig te doen (stel dat je dit vergeet...)

[...]

Bovenstaande (een paymentComplete event triggeren om daarna bovenliggende Order te updaten is ook een mooie manier. maar ook weer hier zit de crux : hoe zorg ik dus voor dat ik mijn events kan dispatchen in laravel zonder gebruik te maken van event($event); in mijn Domain laag?
Een aantal zaken. Wat is hier je domain object ? de domainService ? Is dat het object welke je order data bevat? Wat ervoor zorgt dat een order niet inconsistent kan raken omdat deze domain regels toepast welke het voorkomt ?

Als ik een Order entiteit zou modelleren volgens DDD. Dan begin ik met een Order class. Hierin zet ik de eigenschappen van deze order, en ga ik methodes definiëren.

Zoals bv: PaymentReceived.

Wat moet ik dan doen om een order te updaten:

code:
1
2
3
4
5
  var order = orderRepository->GetOrder(id); //repository is hier puur een synoniem voor, ergens waar ik de order vandaan haal
   
   order.PaymentReceived();
  
  orderRepository.Save(order);


Wellicht moet ik wel meer doen, en wil ik meerdere methodes op een Order object aanroepen:

code:
1
2
3
4
5
  order.ChangeAddress(adressInfo);
  order.PaymentReceived();
  order.ReadyForShipment();

  orderRepository.Save(order);


Nergens zie ik events terug komen. Het publishen van mn events heb ik namelijk gedelegeerd naar de orderRepository in dit geval.

Mocht deze specifieke patroon van functie's nu veel vaker voorkomen, dan heb ik blijkbaar een overkoepelende activiteit wat die 3 acties op een order omvat.

Nu kan je dit direct op je domain object definiëren. Maar je kan ook zeggen: De 'buitenwereld' communiceert nooit direct met mijn domain layer. Maar altijd via een service. Want ik wil helemaal niet dat de 'buitenwereld' moet weten wanneer het order object gesaved moet worden, en hoe deze opgehaald moet worden.

En daar heb je dan ineens een casus voor je orderService. Welke al deze dingen voor je doet. Zodat je op je orderService alleen maar ProcessPaymentReceived(paymentId) hoeft aan te roepen.

Op die manier is de contract met je buitenwereld, alles wat de orderService exposed. En zijn alle details van hoe je Domain Layer zijn ding doet verhuld in die service. Maar blijven je domain rules (validaties etc) in je Domain object (het Order object).

En hoe zorg je dat je events kan dispatchen, zonder jezelf aan Laravel te knopen? Simpel. Definieer een IServiceDispatcher of IServiceBus interface, of whatever je m wilt noemen. En registreer een implementatie welke laravel gebruikt.

Je domain layer is dan afhankelijk van die interface. Maar de implementatie kan je veranderen naar gelang het platform waarom je zit.

Dan betreffende het nodig hebben van je Events in je UI laag. Ik weet niet precies wat je bedoeld hiermee. Maar je kan toch gewoon een eventSubscriber/handler schrijven welke de benodigde events ontvangt, en deze op een of andere manier beschikbaar maakt in je systeem op zo'n manier dat je deze in je UI kan gebruiken?

[ Voor 196% gewijzigd door D-Raven op 19-07-2017 14:42 ]


Acties:
  • 0 Henk 'm!

  • D-Raven
  • Registratie: November 2001
  • Laatst online: 07-10 10:25
-

[ Voor 100% gewijzigd door D-Raven op 19-07-2017 14:29 ]

Pagina: 1