Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Restless Microservices with Symfony

Da2d2829b89cde136392973a35b68959?s=47 nealio82
September 13, 2019

Restless Microservices with Symfony

When we think about microservices we usually imagine a bunch of self-contained and job-focused applications, each specifying and managing their own boundaries and dependencies, and talking to each other over HTTP / REST APIs.

Used in the right context, the microservice architecture can deliver number of great benefits to a business. But those benefits come with tradeoffs.

Unfortunately a lot of people find out too far down the road just how difficult REST-based microservices can be to implement and maintain.

In this presentation we dug into the pros and cons of using microservices, considered if it was worth sticking with a monolith for projects, and then saw how MyBuilder.com have tackled the problem in the real world using Symfony, keeping services isolated and testable while addressing some of the challenges that many people face when trying to adopt such a pattern.

Da2d2829b89cde136392973a35b68959?s=128

nealio82

September 13, 2019
Tweet

Transcript

  1. RESTLESS MICROSERVICES

  2. RESTLESS MICROSERVICES with

  3. None
  4. None
  5. None
  6. WE ARE HIRING!

  7. RESTLESS MICROSERVICES

  8. RESTLESS MICROSERVICES with

  9. None
  10. None
  11. Billing

  12. Billing Profile

  13. Billing Profile Upload songs

  14. Billing Search / buy music Profile Upload songs

  15. Billing Search / buy music Profile Upload songs

  16. bin public src templates config billing marketplace profile upload Billing

    Search / buy music Profile Upload songs
  17. bin public src templates config marketplace profile upload Billing Search

    / buy music Profile Upload songs billing
  18. bin public src templates config marketplace profile upload billing

  19. bin public src templates config marketplace profile upload billing

  20. bin public src templates config marketplace profile upload billing

  21. None
  22. Search / buy music Upload songs Billing Profile

  23. Search / buy music Upload songs Billing Profile

  24. Search / buy music Upload songs Billing Profile

  25. Search / buy music Upload songs Billing Profile

  26. Search / buy music Upload songs Billing Profile

  27. Search / buy music Upload songs Billing Profile

  28. Search / buy music Upload songs Billing Profile

  29. Search / buy music Upload songs Billing Profile

  30. Search / buy music Upload songs Billing Profile

  31. Search / buy music Upload songs Billing Profile

  32. Should I use microservices?

  33. What problems do they solve? Should I use microservices?

  34. Noback, M (2017): “Microservices for everyone”, p3 Splitting one big

    software solution into multiple smaller ones will make it easier to cut away bad parts, to integrate new parts, and to run experiments, at infrastructure and code level. This entails a higher speed of change than what we normally experience when working on a monolithic application.
  35. Search / buy music Upload songs Billing Profile

  36. Search / buy music Upload songs Billing Profile

  37. With a large, monolithic service, we have to scale everything

    together. […] if that behaviour is locked up in a giant monolithic application, we have to handle scaling everything as a piece. With smaller services, we can just scale those services that need scaling, allowing us to run other parts of the system on smaller, less powerful hardware. Newman, S (2015): “Building Microservices”, p5
  38. Search / buy music Upload songs Billing Profile

  39. Search / buy music Upload songs Billing Profile

  40. Search / buy music Upload songs Billing Profile

  41. Wolff, E (2016): “Microservices”, p57 A failure in the deployment

    of a single Microservice [doesn’t] impact the system as a whole.
  42. Search / buy music Upload songs Billing Profile

  43. Search / buy music Upload songs Billing Profile

  44. Search / buy music Upload songs Billing Profile

  45. What problems do they solve? Should I use microservices?

  46. What problems do they solve? What new problems do they

    introduce? Should I use microservices?
  47. Wolff, E (2016): “Microservices”, p72 With a Deployment Monolith it

    is relatively straightforward to monitor the system. When problems arise, the administrator can log into the system [and] analyse errors. Microservice based systems contain so many systems that this approach is no longer feasible. Consequently, there has to be a monitoring system that brings monitoring information from all the services together.
  48. Search / buy music Upload songs Billing Profile

  49. Search / buy music Upload songs Billing Profile

  50. Search / buy music Upload songs Billing Profile

  51. Newman, S (2015): “Building Microservices”, p5 […] we need to

    understand the new sources of failure that distributed systems have to deal with. Networks can and will fail, as will machines.
  52. Search / buy music Upload songs Billing Profile

  53. Search / buy music Upload songs Billing Profile

  54. What problems do they solve? What new problems do they

    introduce? Should I use microservices?
  55. What problems do they solve? What new problems do they

    introduce? Should I stick to a monolith? Should I use microservices?
  56. None
  57. None
  58. None
  59. None
  60. None
  61. None
  62. Search / buy music Upload songs Billing Profile

  63. Search / buy music Upload songs Billing Profile { "id":

    "9541df33-2077-499e-a6f3-579eec03cd0c", "eventType": "invoice", "value": "100" } JSON over HTTP
  64. None
  65. Fidao, C (2014): https://fideloper.com/hexagonal-architecture

  66. Fidao, C (2014): https://fideloper.com/hexagonal-architecture

  67. Fidao, C (2014): https://fideloper.com/hexagonal-architecture

  68. Fidao, C (2014): https://fideloper.com/hexagonal-architecture

  69. Fidao, C (2014): https://fideloper.com/hexagonal-architecture

  70. Fidao, C (2014): https://fideloper.com/hexagonal-architecture

  71. Fidao, C (2014): https://fideloper.com/hexagonal-architecture

  72. Fidao, C (2014): https://fideloper.com/hexagonal-architecture

  73. None
  74. None
  75. None
  76. WE ARE HIRING!

  77. Fidao, C (2014): https://fideloper.com/hexagonal-architecture

  78. None
  79. None
  80. None
  81. None
  82. None
  83. None
  84. None
  85. None
  86. marketplace upload billing profile

  87. marketplace upload billing profile bin public src config bin public

    src config bin public src config bin public src config templates
  88. marketplace upload billing profile bin public src config bin public

    src config bin public src config bin public src config templates { "id": "9541df33-2077-499e-a6f3-579eec03cd0c", "eventType": "invoice", "value": "100" } JSON over HTTP
  89. marketplace upload billing profile bin public src config bin public

    src config bin public src config bin public src config templates
  90. marketplace upload billing profile

  91. marketplace upload billing profile

  92. bin public src templates config billing marketplace profile upload

  93. None
  94. bin public src templates config billing marketplace profile upload

  95. bin public src templates config billing marketplace profile upload

  96. None
  97. None
  98. None
  99. $ composer require acme/billing-package

  100. None
  101. { "name": "acme/billing-package", "description": "Handles billing for things in our

    application", "version": "0.0.1", "license": "proprietary", "require": { "php": ">=7.3" }, "require-dev": { "phpunit/phpunit": "^8" }, "config": { "bin-dir": "bin" }, "autoload": { "psr-4": { "Acme\\Package\\Billing\\": "src/" } }, "autoload-dev": { "psr-4": { "Tests\\": "tests/" } } } composer.json
  102. { "name": "acme/billing-package", "description": "Handles billing for things in our

    application", "version": "0.0.1", "license": "proprietary", "require": { "php": ">=7.3" }, "require-dev": { "phpunit/phpunit": "^8" }, "config": { "bin-dir": "bin" }, "autoload": { "psr-4": { "Acme\\Package\\Billing\\": "src/" } }, "autoload-dev": { "psr-4": { "Tests\\": "tests/" } } } composer.json
  103. composer.json { "name": "acme/billing-package", "description": "Handles billing for things in

    our application", "version": "0.0.1", "license": "proprietary", "require": { "php": ">=7.3" }, "require-dev": { "phpunit/phpunit": "^8" }, "config": { "bin-dir": "bin" }, "autoload": { "psr-4": { "Acme\\Package\\Billing\\": "src/" } }, "autoload-dev": { "psr-4": { "Tests\\": "tests/" } } }
  104. src composer.json tests CardPayment.php CustomerRepository.php PaymentProcessor.php <?php namespace Acme\Package\Billing; class

    CardPayment { private $paymentProcessor; private $repository; public function __construct( PaymentProcessor $paymentProcessor, CustomerRepository $repository ) { $this->paymentProcessor = $paymentProcessor; $this->repository = $repository; } public function chargeCard(int $customerId, int $amountInPounds): void { $customer = $this->repository->find($customerId); if (null === $customer) { throw new \Exception('Customer not found'); } $this->paymentProcessor->debitAccount( $customer, $amountInPounds ); } public function listPayments(int $customerId): array { // ... }
  105. src composer.json tests CardPayment.php CustomerRepository.php PaymentProcessor.php <?php namespace Acme\Package\Billing; class

    CardPayment { private $paymentProcessor; private $repository; public function __construct( PaymentProcessor $paymentProcessor, CustomerRepository $repository ) { $this->paymentProcessor = $paymentProcessor; $this->repository = $repository; } public function chargeCard(int $customerId, int $amountInPounds): void { $customer = $this->repository->find($customerId); if (null === $customer) { throw new \Exception('Customer not found'); } $this->paymentProcessor->debitAccount( $customer, $amountInPounds ); } public function listPayments(int $customerId): array { // ... }
  106. src composer.json tests CardPayment.php CustomerRepository.php PaymentProcessor.php <?php namespace Acme\Package\Billing; class

    CardPayment { private $paymentProcessor; private $repository; public function __construct( PaymentProcessor $paymentProcessor, CustomerRepository $repository ) { $this->paymentProcessor = $paymentProcessor; $this->repository = $repository; } public function chargeCard(int $customerId, int $amountInPounds): void { $customer = $this->repository->find($customerId); if (null === $customer) { throw new \Exception('Customer not found'); } $this->paymentProcessor->debitAccount( $customer, $amountInPounds ); } public function listPayments(int $customerId): array { // ... }
  107. src composer.json tests CardPayment.php CustomerRepository.php PaymentProcessor.php <?php namespace Acme\Package\Billing; class

    CardPayment { private $paymentProcessor; private $repository; public function __construct( PaymentProcessor $paymentProcessor, CustomerRepository $repository ) { $this->paymentProcessor = $paymentProcessor; $this->repository = $repository; } public function chargeCard(int $customerId, int $amountInPounds): void { $customer = $this->repository->find($customerId); if (null === $customer) { throw new \Exception('Customer not found'); } $this->paymentProcessor->debitAccount( $customer, $amountInPounds ); } public function listPayments(int $customerId): array { // ... }
  108. src composer.json tests CardPayment.php CustomerRepository.php PaymentProcessor.php <?php namespace Acme\Package\Billing; class

    CardPayment { private $paymentProcessor; private $repository; public function __construct( PaymentProcessor $paymentProcessor, CustomerRepository $repository ) { $this->paymentProcessor = $paymentProcessor; $this->repository = $repository; } public function chargeCard(int $customerId, int $amountInPounds): void { $customer = $this->repository->find($customerId); if (null === $customer) { throw new \Exception('Customer not found'); } $this->paymentProcessor->debitAccount( $customer, $amountInPounds ); } public function listPayments(int $customerId): array { // ... }
  109. <?php namespace Acme\Package\Billing; interface PaymentProcessor { public function debitAccount(Customer $customer,

    int $amount): void; public function fetchTransactions(Customer $customer): array; } src composer.json tests CardPayment.php CustomerRepository.php PaymentProcessor.php
  110. src composer.json tests CardPayment.php CustomerRepository.php PaymentProcessor.php <?php namespace Acme\Package\Billing; interface

    PaymentProcessor { public function debitAccount(Customer $customer, int $amount): void; public function fetchTransactions(Customer $customer): array; }
  111. src composer.json tests CardPayment.php CustomerRepository.php PaymentProcessor.php CardPaymentTest.php <?php namespace Tests;

    // ... class CardPaymentTest extends TestCase { protected function setUp() { $this->processor = $this->createMock(PaymentProcessor::class); $this->repository = $this->createMock(CustomerRepository::class); $this->cardPayment = new CardPayment($this->processor,$this->repository); } public function test_a_card_is_charged_the_correct_amount(): void { $customer = new Customer(); // ... $this->cardPayment->chargeCard($customer->getId(), 100); } public function test_an_exception_is_thrown_when_customer_is_not_found(): void { // ... } // ... }
  112. <?php namespace Tests; // ... class CardPaymentTest extends TestCase {

    protected function setUp() { $this->processor = $this->createMock(PaymentProcessor::class); $this->repository = $this->createMock(CustomerRepository::class); $this->cardPayment = new CardPayment($this->processor,$this->repository); } public function test_a_card_is_charged_the_correct_amount(): void { $customer = new Customer(); // ... $this->cardPayment->chargeCard($customer->getId(), 100); } public function test_an_exception_is_thrown_when_customer_is_not_found(): void { // ... } // ... } src composer.json tests CardPayment.php CustomerRepository.php PaymentProcessor.php CardPaymentTest.php
  113. None
  114. None
  115. $ composer require acme/billing-package

  116. $ composer require acme/...

  117. bin public src config vendor acme billing-package profile-package upload-package

  118. bin public src config vendor acme billing-package profile-package upload-package adapter

    PaymentProcessorAdapter.php <?php namespace App\Adapter; use Acme\Package\Billing\PaymentProcessor; class PaymentProcessorAdapter implements PaymentProcessor { private $stripeApiKey; public function __construct(string $stripeApiKey) { $this->stripeApiKey = $stripeApiKey; } public function debitAccount(Customer $customer, int $amount): void { \Stripe\Stripe::setApiKey($this->stripeApiKey); \Stripe\Charge::create([ "amount" => $amount, "currency" => "gbp", "source" => $customer->getAuthToken() ]); } public function fetchTransactions(Customer $customer): array { // ... } }
  119. bin public src config adapter PaymentProcessorAdapter.php vendor acme billing-package profile-package

    upload-package <?php namespace App\Adapter; use Acme\Package\Billing\PaymentProcessor; class PaymentProcessorAdapter implements PaymentProcessor { private $stripeApiKey; public function __construct(string $stripeApiKey) { $this->stripeApiKey = $stripeApiKey; } public function debitAccount(Customer $customer, int $amount): void { \Stripe\Stripe::setApiKey($this->stripeApiKey); \Stripe\Charge::create([ "amount" => $amount, "currency" => "gbp", "source" => $customer->getAuthToken() ]); } public function fetchTransactions(Customer $customer): array { // ... } }
  120. None
  121. bin public src config adapter PaymentProcessorAdapter.php vendor acme billing-package profile-package

    upload-package <?php namespace App\Adapter; use Acme\Package\Billing\PaymentProcessor; class PaymentProcessorAdapter implements PaymentProcessor { private $stripeApiKey; public function __construct(string $stripeApiKey) { $this->stripeApiKey = $stripeApiKey; } public function debitAccount(Customer $customer, int $amount): void { \Stripe\Stripe::setApiKey($this->stripeApiKey); \Stripe\Charge::create([ "amount" => $amount, "currency" => "gbp", "source" => $customer->getAuthToken() ]); } public function fetchTransactions(Customer $customer): array { // ... } }
  122. Search / buy music Upload songs Billing Profile

  123. Search / buy music Upload songs Billing Profile Admin

  124. Search / buy music Upload songs Billing Profile Admin

  125. None
  126. { "name": "acme/billing-bridge", "description": "Handles the infrastructure for the billing

    package", "version": "0.0.1", "license": "proprietary", "require": { "php": ">=7.3", "ext-PDO": "*", "ext-json": "*", "acme/billing-package": "*@dev", "symfony/config": ">=2.8,<3.5", "symfony/http-kernel": ">=2.8,<3.5", "symfony/dependency-injection": "<3.5" }, "require-dev": { "phpunit/phpunit": "^8" }, "autoload": { "psr-4": { "Acme\\Bridge\\Billing\\": "./src" } }, "autoload-dev": { "files": [ "tests/AppKernel.php" ] } } composer.json
  127. { "name": "acme/billing-bridge", "description": "Handles the infrastructure for the billing

    package", "version": "0.0.1", "license": "proprietary", "require": { "php": ">=7.3", "ext-PDO": "*", "ext-json": "*", "acme/billing-package": "*@dev", "symfony/config": ">=2.8,<3.5", "symfony/http-kernel": ">=2.8,<3.5", "symfony/dependency-injection": "<3.5" }, "require-dev": { "phpunit/phpunit": "^8" }, "autoload": { "psr-4": { "Acme\\Bridge\\Billing\\": "./src" } }, "autoload-dev": { "files": [ "tests/AppKernel.php" ] } } composer.json
  128. { "name": "acme/billing-bridge", "description": "Handles the infrastructure for the billing

    package", "version": "0.0.1", "license": "proprietary", "require": { "php": ">=7.3", "ext-PDO": "*", "ext-json": "*", "acme/billing-package": "*@dev", "symfony/config": ">=2.8,<3.5", "symfony/http-kernel": ">=2.8,<3.5", "symfony/dependency-injection": "<3.5" }, "require-dev": { "phpunit/phpunit": "^8" }, "autoload": { "psr-4": { "Acme\\Bridge\\Billing\\": "./src" } }, "autoload-dev": { "files": [ "tests/AppKernel.php" ] } } composer.json
  129. { "name": "acme/billing-bridge", "description": "Handles the infrastructure for the billing

    package", "version": "0.0.1", "license": "proprietary", "require": { "php": ">=7.3", "ext-PDO": "*", "ext-json": "*", "acme/billing-package": "*@dev", "symfony/config": ">=2.8,<3.5", "symfony/http-kernel": ">=2.8,<3.5", "symfony/dependency-injection": "<3.5" }, "require-dev": { "phpunit/phpunit": "^8" }, "autoload": { "psr-4": { "Acme\\Bridge\\Billing\\": "./src" } }, "autoload-dev": { "files": [ "tests/AppKernel.php" ] } } composer.json
  130. { "name": "acme/billing-bridge", "description": "Handles the infrastructure for the billing

    package", "version": "0.0.1", "license": "proprietary", "require": { "php": ">=7.3", "ext-PDO": "*", "ext-json": "*", "acme/billing-package": "*@dev", "symfony/config": ">=2.8,<3.5", "symfony/http-kernel": ">=2.8,<3.5", "symfony/dependency-injection": "<3.5" }, "require-dev": { "phpunit/phpunit": "^8" }, "autoload": { "psr-4": { "Acme\\Bridge\\Billing\\": "./src" } }, "autoload-dev": { "files": [ "tests/AppKernel.php" ] } } composer.json
  131. { "name": "acme/billing-bridge", "description": "Handles the infrastructure for the billing

    package", "version": "0.0.1", "license": "proprietary", "require": { "php": ">=7.3", "ext-PDO": "*", "ext-json": "*", "acme/billing-package": "*@dev", "symfony/config": ">=2.8,<3.5", "symfony/http-kernel": ">=2.8,<3.5", "symfony/dependency-injection": "<3.5" }, "require-dev": { "phpunit/phpunit": "^8" }, "autoload": { "psr-4": { "Acme\\Bridge\\Billing\\": "./src" } }, "autoload-dev": { "files": [ "tests/AppKernel.php" ] } } composer.json
  132. src DependencyInjection BillingBridgeExtension.php PaymentProcessor Resources Config services.xml composer.json tests AppKernel.php

    <?php namespace Acme\Bridge\Billing\DependencyInjection; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; class Configuration implements ConfigurationInterface { public function getConfigTreeBuilder() { $treeBuilder = new TreeBuilder(); $rootNode = $treeBuilder->root('billing_bridge'); $rootNode ->children() ->arrayNode('services') ->isRequired() ->children() ->scalarNode('pdo_connection') ->isRequired() ->cannotBeEmpty() ->end() ->end() ->end() ->arrayNode('parameters') ->isRequired() ->children() ->scalarNode('payment_provider_key') ->isRequired() ->cannotBeEmpty() ->end() ->end() ->end() ->end(); Configuration.php StripePaymentProcessor.php
  133. src DependencyInjection BillingBridgeExtension.php PaymentProcessor Resources Config services.xml composer.json tests AppKernel.php

    Configuration.php <?php namespace Acme\Bridge\Billing\DependencyInjection; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; class Configuration implements ConfigurationInterface { public function getConfigTreeBuilder() { $treeBuilder = new TreeBuilder(); $rootNode = $treeBuilder->root('billing_bridge'); $rootNode ->children() ->arrayNode('services') ->isRequired() ->children() ->scalarNode('pdo_connection') ->isRequired() ->cannotBeEmpty() ->end() ->end() ->end() ->arrayNode('parameters') ->isRequired() ->children() ->scalarNode('payment_provider_key') ->isRequired() ->cannotBeEmpty() ->end() ->end() ->end() ->end(); StripePaymentProcessor.php
  134. src DependencyInjection BillingBridgeExtension.php PaymentProcessor Resources Config services.xml composer.json tests AppKernel.php

    Configuration.php <?php namespace Acme\Bridge\Billing\DependencyInjection; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; class Configuration implements ConfigurationInterface { public function getConfigTreeBuilder() { $treeBuilder = new TreeBuilder(); $rootNode = $treeBuilder->root('billing_bridge'); $rootNode ->children() ->arrayNode('services') ->isRequired() ->children() ->scalarNode('pdo_connection') ->isRequired() ->cannotBeEmpty() ->end() ->end() ->end() ->arrayNode('parameters') ->isRequired() ->children() ->scalarNode('payment_provider_key') ->isRequired() ->cannotBeEmpty() ->end() ->end() ->end() ->end(); StripePaymentProcessor.php
  135. <?php namespace Acme\Bridge\Billing\DependencyInjection; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; class Configuration implements

    ConfigurationInterface { public function getConfigTreeBuilder() { $treeBuilder = new TreeBuilder(); $rootNode = $treeBuilder->root('billing_bridge'); $rootNode ->children() ->arrayNode('services') ->isRequired() ->children() ->scalarNode('pdo_connection') ->isRequired() ->cannotBeEmpty() ->end() ->end() ->end() ->arrayNode('parameters') ->isRequired() ->children() ->scalarNode('payment_provider_key') ->isRequired() ->cannotBeEmpty() ->end() ->end() ->end() ->end(); src DependencyInjection BillingBridgeExtension.php PaymentProcessor Resources Config services.xml composer.json tests AppKernel.php Configuration.php StripePaymentProcessor.php
  136. src DependencyInjection BillingBridgeExtension.php PaymentProcessor Resources Config services.xml composer.json tests AppKernel.php

    Configuration.php <?php namespace Acme\Bridge\Billing\DependencyInjection; use InvalidArgumentException; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader; use Symfony\Component\HttpKernel\DependencyInjection\Extension; class BillingBridgeExtension extends Extension { public function load(array $configs, ContainerBuilder $container): void { $config = $this->processConfiguration(new Configuration(), $configs); $loader = new Loader\XmlFileLoader( $container, new FileLocator(__DIR__ . '/../Resources/config') ); $container->setAlias( 'billing_bridge.pdo_connection', $config['services']['pdo_connection'] ); $container->setAlias( 'billing_bridge.payment_provider_key', $config['parameters']['payment_provider_key'] ); $loader->load('services.xml'); } } StripePaymentProcessor.php
  137. src DependencyInjection BillingBridgeExtension.php PaymentProcessor Resources Config services.xml composer.json tests AppKernel.php

    Configuration.php <?php namespace Acme\Bridge\Billing\DependencyInjection; use InvalidArgumentException; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader; use Symfony\Component\HttpKernel\DependencyInjection\Extension; class BillingBridgeExtension extends Extension { public function load(array $configs, ContainerBuilder $container): void { $config = $this->processConfiguration(new Configuration(), $configs); $loader = new Loader\XmlFileLoader( $container, new FileLocator(__DIR__ . '/../Resources/config') ); $container->setAlias( 'billing_bridge.pdo_connection', $config['services']['pdo_connection'] ); $container->setAlias( 'billing_bridge.payment_provider_key', $config['parameters']['payment_provider_key'] ); $loader->load('services.xml'); } } StripePaymentProcessor.php
  138. src DependencyInjection BillingBridgeExtension.php PaymentProcessor Resources Config services.xml composer.json tests AppKernel.php

    Configuration.php <?php namespace Acme\Bridge\Billing\DependencyInjection; use InvalidArgumentException; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader; use Symfony\Component\HttpKernel\DependencyInjection\Extension; class BillingBridgeExtension extends Extension { public function load(array $configs, ContainerBuilder $container): void { $config = $this->processConfiguration(new Configuration(), $configs); $loader = new Loader\XmlFileLoader( $container, new FileLocator(__DIR__ . '/../Resources/config') ); $container->setAlias( 'billing_bridge.pdo_connection', $config['services']['pdo_connection'] ); $container->setAlias( 'billing_bridge.payment_provider_key', $config['parameters']['payment_provider_key'] ); $loader->load('services.xml'); } } StripePaymentProcessor.php
  139. src DependencyInjection BillingBridgeExtension.php PaymentProcessor StripePaymentProcessor.php Resources Config services.xml composer.json tests

    AppKernel.php Configuration.php <?php namespace Acme\Bridge\Billing\Adapter; use Acme\Package\Billing\PaymentProcessor; class StripePaymentProcessor implements PaymentProcessor { private $stripeApiKey; public function __construct(string $stripeApiKey) { $this->stripeApiKey = $stripeApiKey; } public function debitAccount(Customer $customer, int $amount): void { \Stripe\Stripe::setApiKey($this->stripeApiKey); \Stripe\Charge::create([ "amount" => $amount, "currency" => "gbp", "source" => $customer->getAuthToken() ]); } public function fetchTransactions(Customer $customer): array { // ... } }
  140. src DependencyInjection BillingBridgeExtension.php PaymentProcessor StripePaymentProcessor.php Resources Config services.xml composer.json tests

    AppKernel.php Configuration.php <?php namespace Acme\Bridge\Billing\Adapter; use Acme\Package\Billing\PaymentProcessor; class StripePaymentProcessor implements PaymentProcessor { private $stripeApiKey; public function __construct(string $stripeApiKey) { $this->stripeApiKey = $stripeApiKey; } public function debitAccount(Customer $customer, int $amount): void { \Stripe\Stripe::setApiKey($this->stripeApiKey); \Stripe\Charge::create([ "amount" => $amount, "currency" => "gbp", "source" => $customer->getAuthToken() ]); } public function fetchTransactions(Customer $customer): array { // ... } }
  141. src DependencyInjection BillingBridgeExtension.php PaymentProcessor StripePaymentProcessor.php Resources Config services.xml composer.json tests

    AppKernel.php Configuration.php <?php namespace Acme\Bridge\Billing\Adapter; use Acme\Package\Billing\PaymentProcessor; class StripePaymentProcessor implements PaymentProcessor { private $stripeApiKey; public function __construct(string $stripeApiKey) { $this->stripeApiKey = $stripeApiKey; } public function debitAccount(Customer $customer, int $amount): void { // ... } public function fetchTransactions(Customer $customer): array { \Stripe\Stripe::setApiKey($this->stripeApiKey); $list = \Stripe\Charge::all([ "customer" => $customer->getAuthToken() ]); return array_map(function ($payment) { return new PaymentDto($payment['name'], $payment['amount']); }, $list); } }
  142. src DependencyInjection BillingBridgeExtension.php PaymentProcessor StripePaymentProcessor.php Resources Config services.xml composer.json tests

    AppKernel.php Configuration.php <?php namespace Acme\Bridge\Billing\Adapter; use Acme\Package\Billing\PaymentProcessor; class StripePaymentProcessor implements PaymentProcessor { private $stripeApiKey; public function __construct(string $stripeApiKey) { $this->stripeApiKey = $stripeApiKey; } public function debitAccount(Customer $customer, int $amount): void { // ... } public function fetchTransactions(Customer $customer): array { \Stripe\Stripe::setApiKey($this->stripeApiKey); $list = \Stripe\Charge::all([ "customer" => $customer->getAuthToken() ]); return array_map(function ($payment) { return new PaymentDto($payment['name'], $payment['amount']); }, $list); } }
  143. <?php namespace Acme\Bridge\Billing\Repository; use Acme\Package\Billing\CustomerRepository; class PdoCustomerRepository implements CustomerRepository {

    private $connection; public function __construct(\PDO $connection) { $this->connection = $connection; } public function find(int $customerId): Customer { $query = $this->connection ->prepare('SELECT name, token FROM customer_accounts WHERE id = :id'); $query->bindValue(':id', $customerId); $result = $query->execute(); $customer = $result->fetchAll(\PDO::FETCH_ASSOC); return new Customer($customer[0]['name'], $customer[0]['token']); } } src DependencyInjection BillingBridgeExtension.php PaymentProcessor StripePaymentProcessor.php Resources Config services.xml tests AppKernel.php Configuration.php Repository PdoCustomerRepository.php
  144. <?php namespace Acme\Bridge\Billing\Repository; use Acme\Package\Billing\CustomerRepository; class PdoCustomerRepository implements CustomerRepository {

    private $connection; public function __construct(\PDO $connection) { $this->connection = $connection; } public function find(int $customerId): Customer { $query = $this->connection ->prepare('SELECT name, token FROM customer_accounts WHERE id = :id'); $query->bindValue(':id', $customerId); $result = $query->execute(); $customer = $result->fetchAll(\PDO::FETCH_ASSOC); return new Customer($customer[0]['name'], $customer[0]['token']); } } src DependencyInjection BillingBridgeExtension.php PaymentProcessor Resources Config services.xml tests AppKernel.php Configuration.php Repository PdoCustomerRepository.php StripePaymentProcessor.php
  145. <?php namespace Acme\Bridge\Billing\Repository; use Acme\Package\Billing\CustomerRepository; class PdoCustomerRepository implements CustomerRepository {

    private $connection; public function __construct(\PDO $connection) { $this->connection = $connection; } public function find(int $customerId): Customer { $query = $this->connection ->prepare('SELECT name, token FROM customer_accounts WHERE id = :id'); $query->bindValue(':id', $customerId); $result = $query->execute(); $customer = $result->fetchAll(\PDO::FETCH_ASSOC); return new Customer($customer[0]['name'], $customer[0]['token']); } } src DependencyInjection BillingBridgeExtension.php PaymentProcessor Resources Config services.xml tests AppKernel.php Configuration.php Repository PdoCustomerRepository.php StripePaymentProcessor.php
  146. <?xml version="1.0" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony services-1.0.xsd"> <services>

    <service id="acme.billing.card_payment" class="Acme\Package\Billing\CardPayment"> <argument type="service" id="billing_bridge.payment_provider" /> <argument type="service" id="billing_bridge.customer_repository" /> </service> <service id="billing_bridge.customer_repository" class="Acme\Bridge\Billing\Repository\PdoCustomerRepository" public="false"> <argument type="service" id="billing_bridge.pdo_connection" /> </service> <service id="billing_bridge.payment_provider" class="Acme\Bridge\Billing\PaymentProcessor\StripePaymentProcessor" public="false"> <argument>%billing_bridge.payment_provider_key%</argument> </service> </services> </container> src DependencyInjection PaymentProcessor Configuration.php BillingBridgeExtension.php Resources Config services.xml tests AppKernel.php Repository PdoCustomerRepository.php StripePaymentProcessor.php
  147. <?xml version="1.0" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony services-1.0.xsd"> <services>

    <service id="acme.billing.card_payment" class="Acme\Package\Billing\CardPayment"> <argument type="service" id="billing_bridge.payment_provider" /> <argument type="service" id="billing_bridge.customer_repository" /> </service> <service id="billing_bridge.customer_repository" class="Acme\Bridge\Billing\Repository\PdoCustomerRepository" public="false"> <argument type="service" id="billing_bridge.pdo_connection" /> </service> <service id="billing_bridge.payment_provider" class="Acme\Bridge\Billing\PaymentProcessor\StripePaymentProcessor" public="false"> <argument>%billing_bridge.payment_provider_key%</argument> </service> </services> </container> src DependencyInjection PaymentProcessor Configuration.php BillingBridgeExtension.php Resources Config services.xml tests AppKernel.php Repository PdoCustomerRepository.php StripePaymentProcessor.php
  148. <?xml version="1.0" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony services-1.0.xsd"> <services>

    <service id="acme.billing.card_payment" class="Acme\Package\Billing\CardPayment"> <argument type="service" id="billing_bridge.payment_provider" /> <argument type="service" id="billing_bridge.customer_repository" /> </service> <service id="billing_bridge.customer_repository" class="Acme\Bridge\Billing\Repository\PdoCustomerRepository" public="false"> <argument type="service" id="billing_bridge.pdo_connection" /> </service> <service id="billing_bridge.payment_provider" class="Acme\Bridge\Billing\PaymentProcessor\StripePaymentProcessor" public="false"> <argument>%billing_bridge.payment_provider_key%</argument> </service> </services> </container> src DependencyInjection PaymentProcessor Configuration.php BillingBridgeExtension.php Resources Config services.xml tests AppKernel.php Repository PdoCustomerRepository.php StripePaymentProcessor.php
  149. src DependencyInjection PaymentProcessor Configuration.php BillingBridgeExtension.php Resources Config services.xml tests AppKernel.php

    Repository PdoCustomerRepository.php StripePaymentProcessor.php <?xml version="1.0" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony services-1.0.xsd"> <services> <service id="acme.billing.card_payment" class="Acme\Package\Billing\CardPayment"> <argument type="service" id="billing_bridge.payment_provider" /> <argument type="service" id="billing_bridge.customer_repository" /> </service> <service id="billing_bridge.customer_repository" class="Acme\Bridge\Billing\Repository\PdoCustomerRepository" public="false"> <argument type="service" id="billing_bridge.pdo_connection" /> </service> <service id="billing_bridge.payment_provider" class="Acme\Bridge\Billing\PaymentProcessor\StripePaymentProcessor" public="false"> <argument>%billing_bridge.payment_provider_key%</argument> </service> </services> </container>
  150. src DependencyInjection PaymentProcessor Configuration.php BillingBridgeExtension.php Resources Config services.xml tests AppKernel.php

    Repository PdoCustomerRepository.php StripePaymentProcessor.php <?xml version="1.0" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony services-1.0.xsd"> <services> <service id="acme.billing.card_payment" class="Acme\Package\Billing\CardPayment"> <argument type="service" id="billing_bridge.payment_provider" /> <argument type="service" id="billing_bridge.customer_repository" /> </service> <service id="billing_bridge.customer_repository" class="Acme\Bridge\Billing\Repository\PdoCustomerRepository" public="false"> <argument type="service" id="billing_bridge.pdo_connection" /> </service> <service id="billing_bridge.payment_provider" class="Acme\Bridge\Billing\PaymentProcessor\StripePaymentProcessor" public="false"> <argument>%billing_bridge.payment_provider_key%</argument> </service> </services> </container>
  151. None
  152. $ composer require acme/billing-bridge

  153. app config config.yml billing_bridge: services: pdo_connection: doctrine.dbal.billing_connection parameters: payment_provider_key: '%env(stripe_secret_key)%'

  154. app config config.yml src Controller MarketplaceController.php <?php namespace App\Controller; use

    Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Response; class MarketplaceController extends Controller { public function chargeCardAction($customerId, $amount): Response { // ... $this->container ->get('acme.billing.card_payment') ->debitAccount($customerId, $amount); } public function listChargesAction($customerId): Response { // ... $charges = $this->container ->get('acme.billing.card_payment') ->listPayments($customerId); return new Response(\json_encode($charges)); } }
  155. None
  156. None
  157. Domain Objects

  158. Domain Objects Scalars / JSON

  159. Domain Objects Scalars / JSON

  160. Domain Objects Scalars / JSON

  161. Domain Objects Scalars / JSON

  162. Domain Objects Scalars / JSON

  163. None
  164. None
  165. Unit tests Integration tests E2E tests

  166. None
  167. None
  168. None
  169. None
  170. None
  171. None
  172. None
  173. None
  174. Search / buy music Upload songs Billing Profile

  175. Search / buy music Upload songs Billing Profile

  176. Search / buy music Upload songs Billing Profile

  177. Search / buy music Upload songs Billing Profile

  178. Layer Role Has domain knowledge? Services Dependencies Testing level Package

    Business logic Yes Provides services to bridge None / Language level Unit Bridge Wiring & Concretions Minimal Provides / forwards services to application layer Package / Symfony DI Integration Application / FW Talks to the outside world No Uses services provided by bridge layer Bridge End-to-end
  179. Pros Cons Less boilerplate for creating separate services More boilerplate

    for integrating domain code Layers kept isolated & easily testable Scaling not 100% microservice-esque Clear separation of concerns Requires effort in understanding Easily port domain code into applications Not 100% separately deployable Easily switch between monolith or microservice Not truly asynchronous Communication via JSON between services Breaking one thing breaks all things Full log trace from request to response Low latency (method calls vs network calls)
  180. None
  181. None
  182. WE ARE HIRING!