Implementing reliable PHP-based microservices

Implementing reliable PHP-based microservices

Event: PHP Dublin Meetup

Using a microservice architecture is totally possible using PHP. PHP has improved as a language and also as an ecosystem. Let's understand some new concepts and technologies that help us to create well-developed and reliable services.

7bb3a66a199daec275d5ad339724c754?s=128

Junior Grossi

May 13, 2020
Tweet

Transcript

  1. 19.

    @junior_grossi "Microservices" is an architectural style It's not gonna solve

    problems you don't have yet! (You may never have those problems)
  2. 29.

    @junior_grossi ASYNC COMMUNICATION Frontend API Gateway Queue Service A Service

    B Service C book for a class BOOKING_REQUEST 204 (listening to B_R event) GET /v2/bar {"bar":"123"} BOOKING_CONFIRMED results via websockets if required (listening to B_C event) Send notification
  3. 30.

    @junior_grossi ASYNC COMMUNICATION A service subscribes to an event Use

    the data (payload) to do something // event identifier "BOOKING_REQUEST" // event payload { "userId": "1234567890", "classId": "2345", "classStartDate": "2020­05­14T15:19:21+00:00" }
  4. 32.

    @junior_grossi BUG INVESTIGATION Frontend API Gateway Queue Service A Service

    B Service C book for a class BOOKING_REQUEST 204 (listening to B_R event) GET /v2/bar {"bar":"123"} BOOKING_CONFIRMED results via websockets if required (listening to B_C event) Send notification
  5. 33.

    @junior_grossi DON'T SKIMP ON LOGS Log all requests sent and

    received Log all responses sent and received Log all data from fired events Log all data from received events Log all important decisions "With microservices, logging is like a very close friend."
  6. 36.

    @junior_grossi RESPONSIBILITY Each service needs a reason to exists It's

    allowed to duplicate code, but not data Don't you have some data? Ask another service! Think twice before synchronizing/copying data
  7. 37.

    @junior_grossi WHAT TO AVOID: 2 or more services sharing the

    same DB (which service changed the data?) partial domain implementations (when you have related logic splitted in 2+ services)
  8. 39.

    @junior_grossi PRODUCTIVITY Avoid the "language soup" (pick 2 or 3

    max) 1. You hired John. He's good in PHP 2. He starts a new service in Scala 3. He keeps maintaining that service 4. John leaves the company 5. You need now PHP + Scala engineers
  9. 40.

    @junior_grossi PRODUCTIVITY Use similar so ware architecture and patterns Easier

    to move between projects Easier to move between languages Easier to maintain Easier to integrate
  10. 41.
  11. 45.

    @junior_grossi Robert C. Martin "The goal of so ware architecture

    is to minimize the human resources required to build and maintain the required system."
  12. 47.

    @junior_grossi LAYERED ARCHITECTURE "Leave options opened" (believe me, they do

    change) Independent layers Independent use cases Independent commands
  13. 49.

    @junior_grossi HEXAGONAL ARCHITECTURE "Hexagonal Architecture - Message-Oriented So ware Design"

    Matthias Noback - @matthiasnoback https://youtu.be/K1EJBmwg9EQ
  14. 50.

    @junior_grossi ABOUT YOUR ARCHITECTURE: Framework independent (framework-as-a-tool) Testable without any

    external element UI independent (Web / Console) Database independent Independent of any external agent
  15. 53.

    @junior_grossi SCREAMING ARCHITECTURE "The architecture says by itself" What is

    your service about? (Answer by reading the code!) Payments? Bookings? Authentication? Or an app written in Laravel, Slim or Symfony?
  16. 54.

    @junior_grossi "A good so ware architecture allows decisions about frameworks,

    databases, web-servers, and other environmental issues and tools, to be deferred and delayed." Uncle Bob
  17. 56.

    @junior_grossi THE DEPENDENCY RULE "source code dependencies must point only

    inward, toward higher-level policies" Nothing from "inside" should know anything concrete about "outside".
  18. 62.

    @junior_grossi final class CreateUserController { private CreateUserHandler $handler; public function

    __construct(CreateUserHandler $handler) { $this­>handler = $handler; } public function __invoke(RequestInterface $request): ResponseInterface { $userId = $this­>handler­>handle( new CreateUserCommand( $request­>getAttribute('email'), $request­>getAttribute('firstName'), $request­>getAttribute('lastName'), $request­>getAttribute('password'), ) ); return new JsonResponse(compact('userId'), 201); } }
  19. 63.
  20. 64.

    @junior_grossi final class CreateUserCommand { private string $email; private string

    $firstName; private string $lastName; private string $password; public function __construct( string $email, string $firstName, string $lastName, string $password ) { $this­>email = $email; $this­>firstName = $firstName; $this­>lastName = $lastName; $this­>password = $password; } // Getters: getEmail(), getFirstName(), getLastName(), getPassword() }
  21. 65.

    @junior_grossi 3. HANDLER It's the logic of our use case

    Receives the data as a Command class
  22. 66.

    @junior_grossi final class CreateUserHandler implements HandlerInterface { private UserRepositoryInterface $userRepository;

    private PasswordEncrypterInterface $passwordEncrypter; public function __construct( UserRepositoryInterface $userRepository, PasswordEncrypterInterface $passwordEncrypter ) { $this­>userRepository = $userRepository; $this­>passwordEncrypter = $passwordEncrypter; } public function handle(CreateUserCommand $command): Uuid { return $this­>userRepository­>add( new User( Uuid::uuid4(), $command­>getEmail(), $command­>getFirstName(), $command­>getLastName(), $this­>passwordEncrypter­>encrypt( $command­>getPassword() ),
  23. 67.

    @junior_grossi "UI independent (Web / Console)" 1. A er creating

    a new studio 2. Create a new admin user Event-oriented (async) running in CLI
  24. 68.

    @junior_grossi STUDIO_CREATED Command Bus Event A Event B StudioCreatedEvent.php Listener

    1 Listener 2 Listener 3 CreateAdminUserListener.php Listener 4
  25. 69.

    @junior_grossi final class CreateAdminUserListener implements ListenerInterface { private CreateUserHandler $handler;

    public function __construct(CreateUserHandler $handler) { $this­>handler = $handler; } public function execute(StudioCreatedEvent $event): void { $this­>handler­>handle( new CreateUserCommand( // use the payload from $event ) ); } }
  26. 72.

    @junior_grossi Coroutine based Async PHP programming framework Build high-performance, scalable,

    concurrent TCP, UDP, Unix Socket, HTTP, WebSocket services with PHP and fluent Coroutine API. > It extends some use cases for PHP https://www.swoole.co.uk/
  27. 80.

    @junior_grossi ADVICE ⚠ Use it for new services only (it's

    not a good idea for that legacy API you have)
  28. 84.

    @junior_grossi Coroutine Based Concurrency with PHP and Swoole Bruce Dou

    | @doubaokun Feb 2020 PHPUK2020 https://speakerdeck.com/doubaokun/coroutine-based-concurrency-with-php-and-swoole