TOTAL RECALL - The application that never forgets

TOTAL RECALL - The application that never forgets

Building an application upon CQRS and Event Sourcing offers three pretty nice benefits.

1. Well structured code and business logic, separated by read and write concerns.
2. The source of truth - A reliable and recoverable view into the past of your application state.
3. The opportunity to answer future questions that you don't know about today.

This talk tries to show you examples for these benefits and how to give your application a memory.

At #PHPkonf 20th of May 2017 in Istanbul, Turkey

8ad631306f5ab343446a967b98e64c0e?s=128

Holger Woltersdorf

May 20, 2017
Tweet

Transcript

  1. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF 1 MAY 20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF THE APPLICATION THAT NEVER FORGETS
  2. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF 2 IT’S MY WIFE’S BIRTHDAY HELP!
  3. HOLGER WOLTERSDORF CIO • FATHER • HUSBAND • PHP DEV

    WITH ♥ github.com/hollodotme @hollodotme
  4. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF 4
  5. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF 5 SEPTEMBER 22nd ARNE BLANKERTS thePHPcc SEBASTIAN HEUER kartenmacherei JAN BURKL ZEND MARCO PIVETTA Ocramius STEPHAN HOCHDÖRFER bitExpert ALEXANDER MIERTSCH prooph software CHRISTIAN LÜCK reactPHP
  6. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF 6 CQRS (COMMAND QUERY RESPONSIBILITY SEGREGATION)
  7. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF CQS => CQRS 7 ๏ CQRS EVOLVED FROM THE CQS IMPLEMENTATION PATTERN ๏ First stated by Bertrand Meyer in the 1990’s 
 when working on the Eiffel programming language
 ๏ Describes a semantic implementation pattern for object methods ๏ Queries: return results, but do not change the observable state (free of side effects) ๏ Commands: change the state of objects, but do not return values Bertrand Meyer
  8. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF CQS => CQRS 8 <?php declare(strict_types=1); class Number { /** @var int */ private $number = 0; public function getIncrementedNumber() : int { $this->number++; return $this->number; } } BAD
  9. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF CQS => CQRS 9 <?php declare(strict_types=1); class Number { /** @var int */ private $number = 0; public function getNumber() : int # Query { return $this->number; } public function increment() : void # Command { $this->number++; } } GOOD
  10. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF CQS => CQRS 10 ๏ CQRS IS AN ARCHITECTURE PATTERN ๏ Described by Greg Young’s CQRS documents in 2010 ๏ One part responsible to represent the current state of the application ๏ One part responsible to change the state of the application ๏ Command and Query have a broader semantic meaning than in CQS ๏ Key concept is to split your application into: ๏ A read side that is able to answer queries (read-only) ๏ A write side that is able to process commands (write-only) ๏ Give each side an appropriate storage GREG YOUNG
  11. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF 11 CQRS DEMO
  12. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF 12 THREE APPLICATION LAYERS REQUEST HANDLING LAYER BUSINESS LOGIC LAYER PERSISTENCE LAYER CQRS CAN BE APPLIED TO ALL OF THEM KNOWS HTTP KNOWS BUSINESS RULES KNOWS DATABASE
  13. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF 13 THREE APPLICATION LAYERS READ REQUESTS BUSINESS LOGIC REPRESENTATION WRITE REQUESTS STATE AFTER CQRS WAS APPLIED VIEW LOGIC
  14. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF 14 THREE APPLICATION LAYERS READ REQUESTS BUSINESS LOGIC REPRESENTATION WRITE REQUESTS STATE HOW DO WE GET FROM HERE… … TO THERE? VIEW LOGIC
  15. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF 15 THREE APPLICATION LAYERS READ REQUESTS BUSINESS LOGIC REPRESENTATION WRITE REQUESTS STATE PUBLISH SUBSCRIBE REDIRECT (+ SESSION) VIEW LOGIC
  16. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF REPRESENTATION 16 THREE APPLICATION LAYERS READ REQUESTS BUSINESS LOGIC REPRESENTATION WRITE REQUESTS STATE VIEW LOGIC PUBLISH SUBSCRIBE REDIRECT (+ SESSION) REPRESENTATION MESSAGE BROKER ASYNC EVENTUAL CONSISTENCY !
  17. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF REPRESENTATION 17 THREE APPLICATION LAYERS READ REQUESTS BUSINESS LOGIC REPRESENTATION WRITE REQUESTS STATE VIEW LOGIC PUBLISH SUBSCRIBE REDIRECT (+ SESSION) REPRESENTATION PHP FASTCGI CLIENT ASYNC EVENTUAL CONSISTENCY !
  18. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF 18 CQRS (REQUEST HANDLING LAYER) ๏ GET-REQUEST ๏ NO VIEW LOGIC ๏ COULD BE A STATIC
 HTML PAGE ๏ SUBMIT TRIGGERS
 POST REQUEST
  19. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF 19 CQRS (REQUEST HANDLING LAYER) ๏ GET-REQUEST 
 AFTER REDIRECT ๏ CHECK FOR ERRORS 
 IN SESSION ๏ ERRORS RENDERED INTO VIEW ๏ ONLY SUBMIT 
 TRIGGERS POST REQUEST ๏ RELOAD DOESN’T
  20. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF CQRS (REQUEST HANDLING LAYER) 20 class POSTRequestHandler { public function handle( $request, $session ) : void { #... $validator->checkUserInput( $request ); if ( $validator->failed() ) { $session->addErrors( $validator->getErrors() ); (new Redirect())->respond( '/newsletter-form' ); return; } #... } }
  21. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF CQRS (REQUEST HANDLING LAYER) 21 class GETRequestHandler { public function handle( $request, $session ) : void { $page = new TemplatePage('/newsletter-form'); if ( $session->hasErrors() ) { $page->addErrors( $session->getErrors() ); } $page->display(); } }
  22. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF 22 CQRS (REQUEST HANDLING LAYER) ๏ GET-REQUEST ๏ NO VIEW LOGIC ๏ COULD BE A STATIC
 HTML PAGE ๏ CAN BE DELETED 
 AFTER SUBSCRIPTION
 IS FINISHED
  23. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF CQRS (REQUEST HANDLING LAYER) 23 class POSTRequestHandler { public function handle( $request, $session ) : void { #... input validation passed ... $command = new InitSubscriptionCommand( new Fullname( $request->get( 'fullname' ) ), new Email( $request->get( 'email' ) ) ); $handler = new InitSubscriptionCommandHandler(); $result = $handler->handle( $command ); if ( $result->succeeded() ) { # $publisher->publish( $result ); (new Redirect())->respond( '/success-' . $result->getSubscription()->getId() ); return; } } }
  24. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF CQRS (REQUEST HANDLING LAYER) 24 class InitSubscriptionResult { public function succeeded() : bool public function getErrors() : array public function getSubscription() : NewsletterSubscription } class Subscriber { public function notify( InitSubscriptionResult $result ) : void { $subscription = $result->getSubscription(); $page = new TemplatePage('/newsletter-init-success'); $page->assign( 'subscription', $subscription ); $page->saveAsHTML( '/newsletter-' . $subscription->getId() ); } }
  25. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF 25 CQRS (REQUEST HANDLING LAYER) ๏ E-MAIL ๏ CALL TO ACTION
 FOR A STATE CHANGE ๏ POST REQUESTS
 NOT AVAILABLE 
 FROM E-MAILS!
  26. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF 26 RESPECT HTTP VERBS! CQRS (REQUEST HANDLING LAYER)
  27. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF 27 […] the convention has been established that the GET and HEAD methods SHOULD NOT have the significance of taking an action other than retrieval. These methods ought to be considered "safe". https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html CQRS (REQUEST HANDLING LAYER)
  28. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF 28 CQRS (REQUEST HANDLING LAYER) ๏ DON’T CHANGE STATE FROM GET REQUESTS! ๏ GET-REQUEST ๏ NO VIEW LOGIC ๏ COULD BE A STATIC
 HTML PAGE, PREPARED BASED ON SUCCESSFUL 
 INIT RESULT ๏ SUBMIT TRIGGERS 
 POST REQUEST ๏ CAN BE DELETED 
 AFTER SUBSCRIPTION
 IS FINISHED
  29. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF 29 CQRS (REQUEST HANDLING LAYER) ๏ GET-REQUEST
 AFTER REDIRECT ๏ NO VIEW LOGIC ๏ COULD BE A STATIC
 HTML PAGE, PREPARED BASED ON SUCCESSFUL 
 FINISH RESULT
  30. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF 30 BTW ;-) icehawk.github.io PHP7 micro framework - respecting CQRS
  31. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF 31 PERSISTENCE LAYER & STATE SubscriberID Fullname Email Interval Subscribed Confirmed Unsubscribed ‹UUID-HW› Holger Woltersdorf hollodotme @PHPkonf.org ONLY WHEN PHPkonf happens 2017-05-19 2017-05-20 2017-05-21 ‹UUID-JD› Jane Doe jane@doe.com Monthly 2017-03-01 NULL NULL SubscriberID Topic ‹UUID-HW› HIPSTER JEANS ‹UUID-HW› NERD CAPS ‹UUID-HW› MEME SHIRTS
  32. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF 32 PERSISTENCE LAYER & STATE WHAT HAPPENS, IF… I subscribe again for MEME SHIRTS only with WEEKLY interval
  33. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF 33 PERSISTENCE LAYER & STATE SubscriberID Fullname Email Interval Subscribed Confirmed Unsubscribed ‹UUID-HW› Holger Woltersdorf hollodotme @PHPkonf.org Weekly 2017-05-22 2017-05-23 NULL ‹UUID-JD› Jane Doe jane@doe.com Monthly 2017-03-01 NULL NULL SubscriberID Topic ‹UUID-HW› MEME SHIRTS
  34. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF 34 PERSISTENCE LAYER & STATE ‹‹ MY E-MAIL DUPLICATE CHECK WORKED OUT! ›› DEVELOPER: YES! ADMIN: YES! ‹‹ YOU KEPT OUR DATABASE SLIGHT AND FAST! ››
  35. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF ‹‹ ARE YOU INSANE? YOU JUST ERASED VALUABLE B.I. DATA! ›› 35 PERSISTENCE LAYER & STATE BUSINESS ANALYST:
  36. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF CQRS (PERSISTENCE LAYER - STATE) 36 What have we lost? ACTUALLY ALL INFORMATION ABOUT THE FIRST SUBSCRIPTION except e-mail & full name
  37. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF CQRS (PERSISTENCE LAYER - STATE) 37 What else have we lost? ANALYSABLE DATA ABOUT THE USER’S DECISION TO REACTIVATE AND CHANGE SUBSCRIPTION
  38. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF 38 CQRS (PERSISTENCE LAYER - STATE) The application state is observable, BUT ONLY AT THE EXACT POINT OF TIME we look at it.
  39. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF 39 + EVENT SOURCING
  40. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF EVENT SOURCING 40 ๏ EVENT SOURCING IS AN ANALYSIS PATTERN ๏ Article by Martin Fowler in 2005 ๏ CQRS + Event Sourcing also part of Greg Young’s CQRS documents ๏ Pattern was already used in accounting software way earlier ๏ Key concept: 
 
 "Record all changes to application state as a sequence of events."
 — Martin Fowler GREG YOUNG MARTIN FOWLER
  41. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF 41 INSTALLING EVENT SOURCING READ REQUESTS BUSINESS LOGIC REPRESENTATION WRITE REQUESTS STATE PUBLISH SUBSCRIBE REDIRECT (+ SESSION) VIEW LOGIC
  42. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF 42 INSTALLING EVENT SOURCING READ REQUESTS BUSINESS LOGIC REPRESENTATION WRITE REQUESTS STATE PUBLISH SUBSCRIBE REDIRECT (+ SESSION) VIEW LOGIC
  43. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF 43 INSTALLING EVENT SOURCING READ REQUESTS BUSINESS LOGIC REPRESENTATION WRITE REQUESTS EVENT STORE PUBLISH SUBSCRIBE REDIRECT (+ SESSION) VIEW LOGIC READ HISTORY OF CHANGES RECORD NEW CHANGES
  44. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF EVENT SOURCING 44 BUSINESS ENTITY EVENTSTORE COMMAND TRIGGER CHANGES EMITS EVENTS (ALL NEW CHANGES) BUILT FROM EVENT STREAM (HISTORY OF CHANGES) PUBLISH NEWLY RECORDED EVENTS
  45. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF 45 EVENT STORE Seq StreamType StreamID StreamSeq EventID Payload Occurred MetaX Meta… 10 Newsletter
 Subscription ‹UUID-HW› 1 Subscription was initiated {"json"} 2017-05-21 T12:13:14 SHOP TY … 11 Newsletter
 Subscription ‹UUID-HW› 2 Interval was changed {"json"} 2017-05-22 T10:09:08 SHOP TY … 12 Newsletter
 Subscription ‹UUID-HW› 3 Topic was assigned {"json"} 2017-05-22 T10:09:08 SHOP TY … 13 Newsletter Subscription ‹UUID-HW› 4 Topic was assigned {"json"} 2017-05-22 T10:09:08 SHOP TY … 14 Newsletter
 Subscription ‹UUID-HW› 5 Topic was assigned {"json"} 2017-05-22 T10:09:08 SHOP TY … 15 Newsletter
 Subscription ‹UUID-HW› 6 Subscription was confirmed {"json"} 2017-05-22 T10:09:08 SHOP TY … 33 Newsletter
 Subscription ‹UUID-HW› 7 Subscription was canceled {"json"} 2017-05-25 T07:09:09 SHOP DE … 50 Newsletter
 Subscription ‹UUID-HW› 8 Subscription was reactivated {"json"} 2017-06-01 T14:15:16 SHOP DE … 51 Newsletter
 Subscription ‹UUID-HW› 9 Topic was removed {"json"} 2017-06-01 T14:15:16 SHOP DE … 52 Newsletter Subscription ‹UUID-HW› 10 Topic was removed {"json"} 2017-06-01 T14:15:16 SHOP DE … 53 Newsletter
 Subscription ‹UUID-HW› 11 Interval was changed {"json"} 2017-06-01 T14:15:16 SHOP DE … 54 Newsletter
 Subscription ‹UUID-HW› 12 Subscription was confirmed {"json"} 2017-06-02 T16:17:18 SHOP DE …
  46. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF Seq StreamType StreamID StreamSeq EventID Payload Occurred MetaX Meta… 10 Newsletter
 Subscription ‹UUID-HW› 1 Subscription was initiated {"json"} 2017-05-21 T12:13:14 SHOP TY … 11 Newsletter
 Subscription ‹UUID-HW› 2 Interval was changed {"json"} 2017-05-22 T10:09:08 SHOP TY … 12 Newsletter
 Subscription ‹UUID-HW› 3 Topic was assigned {"json"} 2017-05-22 T10:09:08 SHOP TY … 13 Newsletter Subscription ‹UUID-HW› 4 Topic was assigned {"json"} 2017-05-22 T10:09:08 SHOP TY … 14 Newsletter
 Subscription ‹UUID-HW› 5 Topic was assigned {"json"} 2017-05-22 T10:09:08 SHOP TY … 15 Newsletter
 Subscription ‹UUID-HW› 6 Subscription was confirmed {"json"} 2017-05-22 T10:09:08 SHOP TY … 33 Newsletter
 Subscription ‹UUID-HW› 7 Subscription was canceled {"json"} 2017-05-25 T07:09:09 SHOP DE … 50 Newsletter
 Subscription ‹UUID-HW› 8 Subscription was reactivated {"json"} 2017-06-01 T14:15:16 SHOP DE … 51 Newsletter
 Subscription ‹UUID-HW› 9 Topic was removed {"json"} 2017-06-01 T14:15:16 SHOP DE … 52 Newsletter Subscription ‹UUID-HW› 10 Topic was removed {"json"} 2017-06-01 T14:15:16 SHOP DE … 53 Newsletter
 Subscription ‹UUID-HW› 11 Interval was changed {"json"} 2017-06-01 T14:15:16 SHOP DE … 54 Newsletter
 Subscription ‹UUID-HW› 12 Subscription was confirmed {"json"} 2017-06-02 T16:17:18 SHOP DE … 46 EVENT STORE SEQUENCE SORTING ALL EVENTS IN THE EVENT STORE (AUTO-INCREMENT)
  47. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF Seq StreamType StreamID StreamSeq EventID Payload Occurred MetaX Meta… 10 Newsletter
 Subscription ‹UUID-HW› 1 Subscription was initiated {"json"} 2017-05-21 T12:13:14 SHOP TY … 11 Newsletter
 Subscription ‹UUID-HW› 2 Interval was changed {"json"} 2017-05-22 T10:09:08 SHOP TY … 12 Newsletter
 Subscription ‹UUID-HW› 3 Topic was assigned {"json"} 2017-05-22 T10:09:08 SHOP TY … 13 Newsletter Subscription ‹UUID-HW› 4 Topic was assigned {"json"} 2017-05-22 T10:09:08 SHOP TY … 14 Newsletter
 Subscription ‹UUID-HW› 5 Topic was assigned {"json"} 2017-05-22 T10:09:08 SHOP TY … 15 Newsletter
 Subscription ‹UUID-HW› 6 Subscription was confirmed {"json"} 2017-05-22 T10:09:08 SHOP TY … 33 Newsletter
 Subscription ‹UUID-HW› 7 Subscription was canceled {"json"} 2017-05-25 T07:09:09 SHOP DE … 50 Newsletter
 Subscription ‹UUID-HW› 8 Subscription was reactivated {"json"} 2017-06-01 T14:15:16 SHOP DE … 51 Newsletter
 Subscription ‹UUID-HW› 9 Topic was removed {"json"} 2017-06-01 T14:15:16 SHOP DE … 52 Newsletter Subscription ‹UUID-HW› 10 Topic was removed {"json"} 2017-06-01 T14:15:16 SHOP DE … 53 Newsletter
 Subscription ‹UUID-HW› 11 Interval was changed {"json"} 2017-06-01 T14:15:16 SHOP DE … 54 Newsletter
 Subscription ‹UUID-HW› 12 Subscription was confirmed {"json"} 2017-06-02 T16:17:18 SHOP DE … 47 EVENT STORE StreamType NAME OF THE BUSINESS ENTITY StreamID UNIQUE ID OF SPECIFIC BUSINESS ENTITY StreamSeq VERSION OF THE SPECIFIC BUSINESS ENTITY INSTANCE (USED TO SORT THE ENTITY’S EVENT STREAM)
  48. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF Seq StreamType StreamID StreamSeq EventID Payload Occurred MetaX Meta… 10 Newsletter
 Subscription ‹UUID-HW› 1 Subscription was initiated {"json"} 2017-05-21 T12:13:14 SHOP TY … 11 Newsletter
 Subscription ‹UUID-HW› 2 Interval was changed {"json"} 2017-05-22 T10:09:08 SHOP TY … 12 Newsletter
 Subscription ‹UUID-HW› 3 Topic was assigned {"json"} 2017-05-22 T10:09:08 SHOP TY … 13 Newsletter Subscription ‹UUID-HW› 4 Topic was assigned {"json"} 2017-05-22 T10:09:08 SHOP TY … 14 Newsletter
 Subscription ‹UUID-HW› 5 Topic was assigned {"json"} 2017-05-22 T10:09:08 SHOP TY … 15 Newsletter
 Subscription ‹UUID-HW› 6 Subscription was confirmed {"json"} 2017-05-22 T10:09:08 SHOP TY … 33 Newsletter
 Subscription ‹UUID-HW› 7 Subscription was canceled {"json"} 2017-05-25 T07:09:09 SHOP DE … 50 Newsletter
 Subscription ‹UUID-HW› 8 Subscription was reactivated {"json"} 2017-06-01 T14:15:16 SHOP DE … 51 Newsletter
 Subscription ‹UUID-HW› 9 Topic was removed {"json"} 2017-06-01 T14:15:16 SHOP DE … 52 Newsletter Subscription ‹UUID-HW› 10 Topic was removed {"json"} 2017-06-01 T14:15:16 SHOP DE … 53 Newsletter
 Subscription ‹UUID-HW› 11 Interval was changed {"json"} 2017-06-01 T14:15:16 SHOP DE … 54 Newsletter
 Subscription ‹UUID-HW› 12 Subscription was confirmed {"json"} 2017-06-02 T16:17:18 SHOP DE … 48 EVENT STORE EventID NAME OF THE CHANGE (USED TO IDENTIFY EVENT CLASS) Payload ALL RELEVANT DATA THAT IS NEEDED TO REPRODUCE THE CHANGE USE ONE TECHNOLOGY INDEPENDENT DATA FORMAT! Occurred TIMESTAMP WHEN THE CHANGE WAS RECORDED DO NOT USE THIS FOR SORTING EVENT STREAMS DO NOT RELY ON TIMESTAMPS! IT’S JUST META DATA
  49. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF Seq StreamType StreamID StreamSeq EventID Payload Occurred MetaX Meta… 10 Newsletter
 Subscription ‹UUID-HW› 1 Subscription was initiated {"json"} 2017-05-21 T12:13:14 SHOP TY … 11 Newsletter
 Subscription ‹UUID-HW› 2 Interval was changed {"json"} 2017-05-22 T10:09:08 SHOP TY … 12 Newsletter
 Subscription ‹UUID-HW› 3 Topic was assigned {"json"} 2017-05-22 T10:09:08 SHOP TY … 13 Newsletter Subscription ‹UUID-HW› 4 Topic was assigned {"json"} 2017-05-22 T10:09:08 SHOP TY … 14 Newsletter
 Subscription ‹UUID-HW› 5 Topic was assigned {"json"} 2017-05-22 T10:09:08 SHOP TY … 15 Newsletter
 Subscription ‹UUID-HW› 6 Subscription was confirmed {"json"} 2017-05-22 T10:09:08 SHOP TY … 33 Newsletter
 Subscription ‹UUID-HW› 7 Subscription was canceled {"json"} 2017-05-25 T07:09:09 SHOP DE … 50 Newsletter
 Subscription ‹UUID-HW› 8 Subscription was reactivated {"json"} 2017-06-01 T14:15:16 SHOP DE … 51 Newsletter
 Subscription ‹UUID-HW› 9 Topic was removed {"json"} 2017-06-01 T14:15:16 SHOP DE … 52 Newsletter Subscription ‹UUID-HW› 10 Topic was removed {"json"} 2017-06-01 T14:15:16 SHOP DE … 53 Newsletter
 Subscription ‹UUID-HW› 11 Interval was changed {"json"} 2017-06-01 T14:15:16 SHOP DE … 54 Newsletter
 Subscription ‹UUID-HW› 12 Subscription was confirmed {"json"} 2017-06-02 T16:17:18 SHOP DE … 49 EVENT STORE ADDITIONAL META DATA BUSINESS IRRELEVANT DEPENDS ON YOUR DOMAIN AND INFRASTRUCTURE FOR EXAMPLE SHOP INSTANCE SERVER INSTANCE AUTH-USER (IF NOT BUSINESS RELEVANT) APPLICATION VERSION ETC. ADD WHATEVER YOU NEED
  50. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF Seq StreamType StreamID StreamSeq EventID Payload Occurred MetaX Meta… 10 Newsletter
 Subscription ‹UUID-HW› 1 Subscription was initiated {"json"} 2017-05-21 T12:13:14 SHOP TY … 11 Newsletter
 Subscription ‹UUID-HW› 2 Interval was changed {"json"} 2017-05-22 T10:09:08 SHOP TY … 12 Newsletter
 Subscription ‹UUID-HW› 3 Topic was assigned {"json"} 2017-05-22 T10:09:08 SHOP TY … 13 Newsletter Subscription ‹UUID-HW› 4 Topic was assigned {"json"} 2017-05-22 T10:09:08 SHOP TY … 14 Newsletter
 Subscription ‹UUID-HW› 5 Topic was assigned {"json"} 2017-05-22 T10:09:08 SHOP TY … 15 Newsletter
 Subscription ‹UUID-HW› 6 Subscription was confirmed {"json"} 2017-05-22 T10:09:08 SHOP TY … 33 Newsletter
 Subscription ‹UUID-HW› 7 Subscription was canceled {"json"} 2017-05-25 T07:09:09 SHOP DE … 50 Newsletter
 Subscription ‹UUID-HW› 8 Subscription was reactivated {"json"} 2017-06-01 T14:15:16 SHOP DE … 51 Newsletter
 Subscription ‹UUID-HW› 9 Topic was removed {"json"} 2017-06-01 T14:15:16 SHOP DE … 52 Newsletter Subscription ‹UUID-HW› 10 Topic was removed {"json"} 2017-06-01 T14:15:16 SHOP DE … 53 Newsletter
 Subscription ‹UUID-HW› 11 Interval was changed {"json"} 2017-06-01 T14:15:16 SHOP DE … 54 Newsletter
 Subscription ‹UUID-HW› 12 Subscription was confirmed {"json"} 2017-06-02 T16:17:18 SHOP DE … 50 EVENT STORE FIRST SUBSCRIPTION PLEASE NOTE: GIVING BIRTH TO A NEW BUSINESS ENTITY IS A CHANGE TO APPLICATION STATE
  51. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF Seq StreamType StreamID StreamSeq EventID Payload Occurred MetaX Meta… 10 Newsletter
 Subscription ‹UUID-HW› 1 Subscription was initiated {"json"} 2017-05-21 T12:13:14 SHOP TY … 11 Newsletter
 Subscription ‹UUID-HW› 2 Interval was changed {"json"} 2017-05-22 T10:09:08 SHOP TY … 12 Newsletter
 Subscription ‹UUID-HW› 3 Topic was assigned {"json"} 2017-05-22 T10:09:08 SHOP TY … 13 Newsletter Subscription ‹UUID-HW› 4 Topic was assigned {"json"} 2017-05-22 T10:09:08 SHOP TY … 14 Newsletter
 Subscription ‹UUID-HW› 5 Topic was assigned {"json"} 2017-05-22 T10:09:08 SHOP TY … 15 Newsletter
 Subscription ‹UUID-HW› 6 Subscription was confirmed {"json"} 2017-05-22 T10:09:08 SHOP TY … 33 Newsletter
 Subscription ‹UUID-HW› 7 Subscription was canceled {"json"} 2017-05-25 T07:09:09 SHOP DE … 50 Newsletter
 Subscription ‹UUID-HW› 8 Subscription was reactivated {"json"} 2017-06-01 T14:15:16 SHOP DE … 51 Newsletter
 Subscription ‹UUID-HW› 9 Topic was removed {"json"} 2017-06-01 T14:15:16 SHOP DE … 52 Newsletter Subscription ‹UUID-HW› 10 Topic was removed {"json"} 2017-06-01 T14:15:16 SHOP DE … 53 Newsletter
 Subscription ‹UUID-HW› 11 Interval was changed {"json"} 2017-06-01 T14:15:16 SHOP DE … 54 Newsletter
 Subscription ‹UUID-HW› 12 Subscription was confirmed {"json"} 2017-06-02 T16:17:18 SHOP DE … 51 EVENT STORE SECOND SUBSCRIPTION PLEASE NOTE: EXPRESS EXACTLY WHAT YOU ARE DOING INITIATED !== REACTIVATED AVOID AGGREGATION OF CHANGES YOU’LL LOSE VALUABLE INFORMATION Topics were changed !== Topic was added + Topic was removed
  52. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF EVENT STORE PAYLOAD 52 { "SubscriptionID": "‹UUID-HW›", "Fullname": "Holger Woltersdorf", "Email": "hollodotme@PHPkonf.org" } "Subscription was initialised" { "SubscriptionID": "‹UUID-HW›" } vs. "Subscription was reactivated"
  53. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF EVENT STORE PAYLOAD 53 DO NOT ADD PREVIOUS VALUES TO PAYLOAD IT SHOULD HAVE BEEN RECORDED BEFORE STOP THINKING RELATIONAL START THINKING TEMPORAL
  54. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF EVENT SOURCING 54 interface EventInterface { public function getStreamID() : StreamIDInterface public function getEventID() : EventID public function getPayload() : Payload public static function fromPayload( Payload $payload ) : EventInterface } interface EventEnvelopeInterface { public function getHeader() : EventHeaderInterface public function getEvent() : EventInterface }
  55. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF EVENT SOURCING 55 interface EventHeaderInterface { public function getStreamType() : StreamType public function getStreamID() : StreamIDInterface public function getStreamSeq() : StreamSequence public function getOccurred() : Timestamp public function getMetaX() : ShopName public function getMeta…() : … }
  56. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF EVENT SOURCING 56 ๏ EVENT STORE IS IMMUTABLE AND APPEND-ONLY ๏ EVENT STORE IS THE ONLY SOURCE OF TRUTH ๏ USE TECHNOLOGY INDEPENDENT PAYLOAD FORMAT (XML/JSON/ETC.)
 DO NOT USE OBJECT SERIALISATION! ๏ EVENTS ARE IMMUTABLE ๏ SEPARATE PAYLOAD FROM META DATA (EVENT ENVELOPE) ๏ VALUE OBJECTS ARE IMMUTABLE ๏ EVENT STREAM IS A SORTED LIST OF EVENTS ๏ BUSINESS ENTITIES CAN NOT BE INSTANTIATED WITHOUT EVENT STREAM
  57. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF EVENT SOURCING (BUSINESS ENTITY) 57 class NewsletterSubscription { private $streamType; private $streamSeq; private $changes; # ... private function __construct() { $this->streamType = new StreamType( 'Newsletter Subscription' ); $this->streamSeq = new StreamSeq( 0 ); $this->changes = new EventStream(); } # ... }
  58. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF EVENT SOURCING (BUSINESS ENTITY) 58 class NewsletterSubscription { public static function initiate( Email $email, Fullname $fullname ) : NewsletterSubscription { $subscription = new self(); $subscription->recordThat( new SubscriptionWasInitiated( SubscriptionID::generate(), $email, $fullname ) ); return $subscription; } # ... } ๏ DO NOT CREATE ENTITIES WITHOUT RECORDING AN EVENT
  59. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF EVENT SOURCING (BUSINESS ENTITY) 59 class NewsletterSubscription { #... private function recordThat( EventInterface $event ) : void { $envelope = EnvelopeBuilder::build( $this->getStreamType(), $this->streamSeq->increment(), $event ); $this->changes->append( $envelope ); $this->apply( EventEnvelopeInterface $envelope ); } #... }
  60. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF EVENT SOURCING (BUSINESS ENTITY) 60 class NewsletterSubscription { #... private function apply( EventEnvelopeInterface $envelope ) : void { $header = $envelope->getHeader(); $event = $envelope->getEvent(); $this->streamSeq = $header->getStreamSeq(); $methodName = 'when' . $event->getEventID()->toUpperCamelCase(); $this->$methodName( $event ); # $this->whenSubscriptionWasInitiated( $event ); } #... }
  61. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF EVENT SOURCING (BUSINESS ENTITY) 61 class NewsletterSubscription { #... protected function whenSubscriptionWasInitiated( SubscriptionWasInitiated $event ) : void { $this->email = $event->getEmail(); $this->fullname = $event->getFullname(); # This is your "real" constructor # Init properties here } #... }
  62. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF EVENT SOURCING (BUSINESS ENTITY) 62 class NewsletterSubscription { #... public static function reconstitute( EventStream $eventStream ) : self { $subscription = new self(); foreach ( $eventStream as $envelope ) { $subscription->apply( $envelope ); } return $subscription; } #... }
  63. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF EVENT SOURCING (BUSINESS ENTITY) 63 abstract class AbstractBusinessEntity { final protected function __construct() final protected function recordThat( EventInterface $event ): void private function apply( EventEnvelopeInterface $envelope ) : void final public static function reconstitute( EventStream $eventStream ) }
  64. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF EVENT SOURCING (BUSINESS ENTITY) 64 class NewsletterSubscription extends AbstractBusinessEntity { private $topics; public function assignTopic( Topic $topic ) : void protected function whenTopicWasAssigned( TopicWasAssigned $event ) : void } class NewsletterSubscription extends AbstractBusinessEntity { use NewsletterSubscriptionChanging; public function assignTopic( Topic $topic ) : void } USE A TRAIT TO EXPOSE PUBLIC API ONLY trait NewsletterSubscriptionChanging { private $topics; protected function whenTopicWasAssigned( TopicWasAssigned $event ) : void }
  65. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF EVENT SOURCING 65 ๏ QUESTIONS WE CAN ASK NOW: ๏ WHAT WAS THE (UN-)SUBSCRIBE RATIO IN 2016? ๏ HOW MANY USERS REACTIVATED THEIR SUBSCRIPTION IN 2017? ๏ HOW MANY USERS REACTIVATED MORE THAN ONCE? ๏ WHICH TOPIC WAS REMOVED THE MOST? ๏ HOW MANY USERS CHANGED INTERVAL BEFORE SUBSCRIPTION WAS CANCELED?
  66. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF 66 EVENT SOURCING READ REQUESTS BUSINESS LOGIC REPRESENTATION WRITE REQUESTS EVENT STORE PUBLISH SUBSCRIBE REDIRECT (+ SESSION) VIEW LOGIC READ HISTORY OF CHANGES RECORD NEW CHANGES
  67. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF RECORD NEW CHANGES READ HISTORY OF CHANGES 67 EVENT SOURCING READ REQUESTS BUSINESS LOGIC REPRESENTATION WRITE REQUESTS EVENT STORE PUBLISH SUBSCRIBE REDIRECT (+ SESSION) VIEW LOGIC
  68. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF RECORD NEW CHANGES READ HISTORY OF CHANGES 68 EVENT SOURCING (PROJECTION) READ REQUESTS BUSINESS LOGIC PROJECTION WRITE REQUESTS EVENT STORE PUBLISH SUBSCRIBE REDIRECT (+ SESSION) VIEW LOGIC
  69. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF RECORD NEW CHANGES READ HISTORY OF CHANGES 69 EVENT SOURCING (PROJECTION) READ REQUESTS BUSINESS LOGIC WRITE REQUESTS REDIRECT (+ SESSION) VIEW LOGIC REPRESENTATION REPRESENTATION EVENT STORE PUBLISH SUBSCRIBE PROJECTION MESSAGE BROKER ASYNC EVENTUAL CONSISTENCY !
  70. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF EVENT SOURCING (PROJECTING) 70 ๏ PUSH VIEWS ๏ Gets notified about a new change (event) ๏ Can directly project this information ๏ PULL VIEWS ๏ Gets a query and pulls up appropriate event streams to gather information ๏ Can respond or project the result ๏ MIXED VIEWS ๏ Gets notified and gathers missing information from event store ๏ Projects this information
  71. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF EVENT SOURCING (PROJECTING) 71 PLEASE NOTE: PROJECTIONS SHOULD CONVERT STATE INFORMATION TO THE BEST DATA FORMAT FOR YOUR READ-USE-CASE THIS COULD ALSO BE ANOTHER EVENT STORE WITH A SUBSET OF STREAMS / EVENTS
  72. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF CQRS ADVANTAGES 72 ๏ WHAT CAN WE GAIN FROM APPLYING CQRS? ๏ Separate input / output handling ๏ Opportunity to optimise each side for its operational purpose ๏ Depends on your application domain ๏ e.g. eCommerce Shops usually have a read / write ratio of 95% / 5%
 so optimisation for reading is a good idea ๏ Clean code, reusable queries and commands, decoupling, simpler tests ๏ No unintended side effects ๏ Technology independence for each side ๏ Good prerequisite for individual horizontal scalability
  73. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF EVENT SOURCING ADVANTAGES 73 ๏ WHAT CAN WE GAIN FROM USING EVENT SOURCING? ๏ High scalability ๏ Simplification of storing application state ("one size fits all") ๏ Traceability of all changes ever made to application state ๏ Retroactive data analysis at any time ๏ Adapting new business requirements with ease ๏ Restoring state representation at any given point in time ๏ Replay after bug fixes to reestablish view consistency ๏ Easy backup
  74. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF 74 QUESTIONS?
  75. THANK YOU! github.com/hollodotme @hollodotme / @F9T3ch fortuneglobe.com phpug-dresden.org @phpugdd HOLGER

    WOLTERSDORF icehawk.github.io speakerdeck.com/hollodotme Slides available at:
  76. TOTAL RECALL - THE APPLICATION THAT NEVER FORGETS • MAY

    20th 2017 • PHPkonf. • ISTANBUL HOLGER WOLTERSDORF LINKS / REFERENCES 76 ๏ CQS: ๏ https://martinfowler.com/bliki/CommandQuerySeparation.html ๏ https://en.wikipedia.org/wiki/Command%E2%80%93query_separation ๏ Bertrand Meyer: ๏ https://de.wikipedia.org/wiki/Bertrand_Meyer ๏ https://bertrandmeyer.com ๏ Book "Object-Oriented Software Construction 2Ed" by Bertrand Meyer: http://amzn.to/2qdnc2i ๏ Greg Young’s CQRS documents: https://cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf ๏ GET/HEAD methods: https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html ๏ Article about Event Sourcing by Martin Fowler: https://martinfowler.com/eaaDev/EventSourcing.html ๏ PHP FastCGI Client: https://github.com/hollodotme/fast-cgi-client ๏ IceHawk micro framework: https://icehawk.github.io