$30 off During Our Annual Pro Sale. View Details »

Modularising the Monolith - PHP Conference Japan 2022

Ryuta Hamasaki
September 24, 2022

Modularising the Monolith - PHP Conference Japan 2022

様々なロジックが密に結合したモノリシックなアプリケーションは開発速度を遅くする一方で、マイクロサービスアーキテクチャはサービス間のコミュニケーションやトランザクションなどといった別の課題があります。
モジュラーモノリスは、モノリシックなアプリケーションの中にドメイン境界を引いて疎結合な状態をつくる、モノリスとマイクロサービスの中間的なアーキテクチャです。

このトークでは、モジュラーモノリスをLaravelアプリケーションに適用する方法を具体例を用いながら紹介します。Laravelを例にしていますが、他のフレームワークにも適用できる内容です。

Ryuta Hamasaki

September 24, 2022
Tweet

More Decks by Ryuta Hamasaki

Other Decks in Technology

Transcript

  1. Modularising the Monolith PHP Conference Japan 2022 Ryuta Hamasaki ᖛཽ࡚ଠ

  2. RYUTA HAMASAKI @avosalmon

  3. None
  4. Verification Solutions Service Provider >1M Accredified documents 9 markets 3

    industries >9M verifications >500 Active issuers A B O U T A C C R E D I F Y
  5. Modularising the Monolith

  6. "HFOEB w ϞϊϦεͱϚΠΫϩαʔϏεͷϝϦοτɾσϝϦοτ w ϞδϡϥʔϞϊϦεͱ͸ w -BSBWFMΞϓϦέʔγϣϯͷϞδϡʔϧԽ w Ϟδϡʔϧؒͷίϛϡχέʔγϣϯ w

    ϞδϡϥʔϞϊϦεͳΞϓϦέʔγϣϯͷςετ w ੩తίʔυղੳͰυϝΠϯڥքΛڧ੍
  7. -BSBDPO0OMJOF https://youtu.be/0Rq-yHAwYjQ?t=4061

  8. .POPMJUIWT.JDSPTFSWJDFT

  9. 4)*15)&130%6$5 505)&."3,&5'*345

  10. ϞϊϦεͷϝϦοτ w ͭͷϦϙδτϦ w ͭͷ$*$%ύΠϓϥΠϯ w ηοτͷΠϯϑϥ w %#τϥϯβΫγϣϯ͕γϯϓϧ

  11. #JH#BMMPG.VE

  12. w ػೳؒͷີ݁߹ w υϝΠϯڥք͕ͳ͍ w ͢΂ͯͷίʔυΛͲ͔͜ΒͰ΋ݺͼग़ͤΔ w ϦϙδτϦશମͷίʔυΛཧղ͢Δඞཁ͕͋Δ #JH#BMMPG.VE

  13. .JDSPTFSWJDFT"SDIJUFDUVSF HTTP HTTP HTTP

  14. ϚΠΫϩαʔϏεͷϝϦοτ w υϝΠϯؒͷڥք͕໌֬ w ֤αʔϏεΛݸผʹσϓϩΠͰ͖Δ w αʔϏε͝ͱʹεέʔϧΞ΢τͰ͖Δ w αʔϏε͝ͱʹҟͳΔٕज़ελοΫΛ࢖͑Δ

  15. ϚΠΫϩαʔϏεͷ՝୊ w ෳ਺ϦϙδτϦͷ؅ཧ w ෳ਺ͷ$*$%ύΠϓϥΠϯ w αʔϏεؒ௨৴ͷωοτϫʔΫΦʔόʔϔου w αʔϏεΛ·͍ͨͩτϥϯβΫγϣϯ

  16. ٕज़తҙࢥܾఆτϨʔυΦϑ

  17. .PEVMBS.POPMJUI

  18. .PEVMBS.POPMJUI Contracts Implementation Tests Contracts Implementation Tests Contracts Implementation Tests

    Inventory module Order module Payment module
  19. Contracts Implementation Tests Contracts Implementation Tests Contracts Implementation Tests Inventory

    module Order module Payment module .PEVMBS.POPMJUI
  20. Contracts Implementation Tests Contracts Implementation Tests Contracts Implementation Tests Inventory

    service Order module Payment module ஈ֊తͳϚΠΫϩαʔϏεԽ
  21. %JTUSJCVUFE#JH#BMMTPG.VE

  22. ϚΠΫϩαʔϏε͸มߋίετ͕ߴ͍ HTTP HTTP HTTP

  23. ϞδϡϥʔϞϊϦε͸มߋίετ͕௿͍ Contracts Implementation Tests Contracts Implementation Tests Contracts Implementation Tests

    Inventory module Order module Payment module
  24. ϞδϡʔϧͱνʔϜߏ੒ Order Module Payment Module Shipping Module Campaign Module Auth

    Module Inventory Module
  25. ϞδϡϥʔϞϊϦε͸ େن໛։ൃʹ΋༗ޮʁ

  26. https://shopify.engineering/deconstructing-monolith-designing-software-maximizes-developer-productivity

  27. ϞδϡϥʔϞϊϦεʹదͨ͠ΞϓϦέʔγϣϯ w ෳ਺ͷϏδωευϝΠϯΛ΋ͭϞϊϦε w νʔϜͷ෼ׂΛݕ౼͍ͯ͠Δ w ϚΠΫϩαʔϏεͷલஈ֊ͱͯ͠

  28. ϞδϡϥʔϞϊϦεͷσϝϦοτ w ςετͷ࣮ߦ͕࣌ؒ௕͘ͳΔ w (JUSFCBTFͷස౓͕ߴ͘ͳΔ w Ϟδϡʔϧ͝ͱʹεέʔϧΞ΢τͰ͖ͳ͍

  29. .PEVMBSJTJOH-BSBWFM

  30. %&'"6-5 4536$563&

  31. .0%6-"3 4536$563&

  32. .0%6-"3 4536$563&

  33. None
  34. None
  35. None
  36. None
  37. None
  38. <?php use Illuminate\Support\Facades\Route; Route::prefix('order-module') ->middleware(['api', 'auth:sanctum']) ->group(function () { Route::apiResource('orders',

    OrderController::class); }); // src/Order/routes.php
  39. namespace Phpcon\Order\Providers; class OrderServiceProvider extends ServiceProvider { public function boot()

    { $this->loadRoutesFrom(__DIR__.'/../routes.php'); $this->loadMigrationsFrom(__DIR__.'/../Infrastructure/Database/Migrations'); $this->loadTranslationsFrom(__DIR__.'/../Resources/lang', 'order'); $this->loadViewsFrom(__DIR__.'/../Resources/views', 'order'); } public function register() { $this->mergeConfigFrom(__DIR__.'/../config/order.php', 'order'); $this->commands([DoSomething::class]); $this->app->register(AuthServiceProvider::class); $this->app->register(EventServiceProvider::class); $this->app->register(RouteServiceProvider::class); } }
  40. namespace Phpcon\Order\Providers; class OrderServiceProvider extends ServiceProvider { public function boot()

    { $this->loadRoutesFrom(__DIR__.'/../routes.php'); $this->loadMigrationsFrom(__DIR__.'/../Infrastructure/Database/Migrations'); $this->loadTranslationsFrom(__DIR__.'/../Resources/lang', 'order'); $this->loadViewsFrom(__DIR__.'/../Resources/views', 'order'); } public function register() { $this->mergeConfigFrom(__DIR__.'/../config/order.php', 'order'); $this->commands([DoSomething::class]); $this->app->register(AuthServiceProvider::class); $this->app->register(EventServiceProvider::class); $this->app->register(RouteServiceProvider::class); } }
  41. namespace Phpcon\Order\Providers; class OrderServiceProvider extends ServiceProvider { public function boot()

    { $this->loadRoutesFrom(__DIR__.'/../routes.php'); $this->loadMigrationsFrom(__DIR__.'/../Infrastructure/Database/Migrations'); $this->loadTranslationsFrom(__DIR__.'/../Resources/lang', 'order'); $this->loadViewsFrom(__DIR__.'/../Resources/views', 'order'); } public function register() { $this->mergeConfigFrom(__DIR__.'/../config/order.php', 'order'); $this->commands([DoSomething::class]); $this->app->register(AuthServiceProvider::class); $this->app->register(EventServiceProvider::class); $this->app->register(RouteServiceProvider::class); } }
  42. namespace Phpcon\Order\Providers; class OrderServiceProvider extends ServiceProvider { public function boot()

    { $this->loadRoutesFrom(__DIR__.'/../routes.php'); $this->loadMigrationsFrom(__DIR__.'/../Infrastructure/Database/Migrations'); $this->loadTranslationsFrom(__DIR__.'/../Resources/lang', 'order'); $this->loadViewsFrom(__DIR__.'/../Resources/views', 'order'); } public function register() { $this->mergeConfigFrom(__DIR__.'/../config/order.php', 'order'); $this->commands([DoSomething::class]); $this->app->register(AuthServiceProvider::class); $this->app->register(EventServiceProvider::class); $this->app->register(RouteServiceProvider::class); } }
  43. namespace Phpcon\Order\Providers; class OrderServiceProvider extends ServiceProvider { public function boot()

    { $this->loadRoutesFrom(__DIR__.'/../routes.php'); $this->loadMigrationsFrom(__DIR__.'/../Infrastructure/Database/Migrations'); $this->loadTranslationsFrom(__DIR__.'/../Resources/lang', 'order'); $this->loadViewsFrom(__DIR__.'/../Resources/views', 'order'); } public function register() { $this->mergeConfigFrom(__DIR__.'/../config/order.php', 'order'); $this->commands([DoSomething::class]); $this->app->register(AuthServiceProvider::class); $this->app->register(EventServiceProvider::class); $this->app->register(RouteServiceProvider::class); } }
  44. namespace Phpcon\Order\Providers; class OrderServiceProvider extends ServiceProvider { public function boot()

    { $this->loadRoutesFrom(__DIR__.'/../routes.php'); $this->loadMigrationsFrom(__DIR__.'/../Infrastructure/Database/Migrations'); $this->loadTranslationsFrom(__DIR__.'/../Resources/lang', 'order'); $this->loadViewsFrom(__DIR__.'/../Resources/views', 'order'); } public function register() { $this->mergeConfigFrom(__DIR__.'/../config/order.php', 'order'); $this->commands([DoSomething::class]); $this->app->register(AuthServiceProvider::class); $this->app->register(EventServiceProvider::class); $this->app->register(RouteServiceProvider::class); } }
  45. namespace Phpcon\Order\Providers; class OrderServiceProvider extends ServiceProvider { public function boot()

    { $this->loadRoutesFrom(__DIR__.'/../routes.php'); $this->loadMigrationsFrom(__DIR__.'/../Infrastructure/Database/Migrations'); $this->loadTranslationsFrom(__DIR__.'/../Resources/lang', 'order'); $this->loadViewsFrom(__DIR__.'/../Resources/views', 'order'); } public function register() { $this->mergeConfigFrom(__DIR__.'/../config/order.php', 'order'); $this->commands([DoSomething::class]); $this->app->register(AuthServiceProvider::class); $this->app->register(EventServiceProvider::class); $this->app->register(RouteServiceProvider::class); } }
  46. namespace Phpcon\Order\Providers; class OrderServiceProvider extends ServiceProvider { public function boot()

    { $this->loadRoutesFrom(__DIR__.'/../routes.php'); $this->loadMigrationsFrom(__DIR__.'/../Infrastructure/Database/Migrations'); $this->loadTranslationsFrom(__DIR__.'/../Resources/lang', 'order'); $this->loadViewsFrom(__DIR__.'/../Resources/views', 'order'); } public function register() { $this->mergeConfigFrom(__DIR__.'/../config/order.php', 'order'); $this->commands([DoSomething::class]); $this->app->register(AuthServiceProvider::class); $this->app->register(EventServiceProvider::class); $this->app->register(RouteServiceProvider::class); } }
  47. namespace Phpcon\Order\Providers; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; use Laracon\Order\Application\Policies\OrderPolicy; use Laracon\Order\Domain\Models\Order;

    class AuthServiceProvider extends ServiceProvider { protected $policies = [ Order::class => OrderPolicy::class, ]; public function boot() { $this->registerPolicies(); } }
  48. namespace Phpcon\Order\Providers; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; use Laracon\Order\Domain\Listeners\HandleOrderShipment; use Laracon\Shipping\Contracts\Events\ParcelShipped;

    class EventServiceProvider extends ServiceProvider { /** * The event listener mappings for the application. * * @var array */ protected $listen = [ ParcelShipped::class => [ HandleOrderShipment::class, ], ]; }
  49. namespace Phpcon\Order\Providers; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; use Illuminate\Support\Facades\Route; use Phpcon\Order\Application\Http\Middleware\SomeMiddleware;

    use Phpcon\Order\Domain\Models\Order; class RouteServiceProvider extends ServiceProvider { public function boot() { Route::bind('order', function ($value) { return Order::with('orderLines')->findOrFail($value); }); $this->app[‘router']->aliasMiddleware('do-something', SomeMiddleware::class); } }
  50. 'providers' => [ // ... \Phpcon\Inventory\Providers\InventoryServiceProvider::class, \Phpcon\Order\Providers\OrderServiceProvider::class, \Phpcon\Payment\Providers\PaymentServiceProvider::class, \Phpcon\Shipping\Providers\ShippingServiceProvider::class, ],

    // config/app.php
  51. { "autoload": { "psr-4": { "App\\": "app/", "Phpcon\\": "src/", "Database\\Factories\\":

    "database/factories/", "Database\\Seeders\\": "database/seeders/" } } } // composer.json
  52. { "autoload": { "psr-4": { "App\\": "app/", "Phpcon\\": "src/", "Database\\Factories\\":

    "database/factories/", "Database\\Seeders\\": "database/seeders/" } } } // composer.json
  53. άϩʔόϧͳΫϥε͸Ͳ͜ʹஔ͘ʁ

  54. Ϟδϡʔϧؒͷίϛϡχέʔγϣϯ

  55. ίϯτϥΫτʹΑΔίϛϡχέʔγϣϯ Module A Module B Contract Implementation Downstream Upstream

  56. Module A Module B ίϯτϥΫτແ͠ͷίϛϡχέʔγϣϯ Downstream Upstream Implementation

  57. Order Inventory Payment Shipping Payment Provider Warehouse System Cart Cart

    Item Order Order Line
  58. Order Inventory Payment Shipping Payment Provider Warehouse System Cart Cart

    Item Order Order Line 1. Checkout
  59. Order Inventory Payment Shipping Payment Provider Warehouse System Cart Cart

    Item Order Order Line 2. Update inventory 1. Checkout
  60. Order Inventory Payment Shipping Payment Provider Warehouse System Cart Cart

    Item Order Order Line 2. Update inventory 3. Process payment 1. Checkout
  61. Order Inventory Payment Shipping Payment Provider Warehouse System Cart Cart

    Item Order Order Line 2. Update inventory 3. Process payment 4. Save order 1. Checkout
  62. Order Inventory Payment Shipping Payment Provider Warehouse System Cart Cart

    Item Order Order Line 2. Update inventory 3. Process payment 5. Notify warehouse 4. Save order 1. Checkout
  63. class OrderController extends Controller { public function store(StoreOrderRequest $request) {

    $cart = Cart::with('cartItems')->findOrFail($request->cart_id); $order = new Order(['user_id' => $request->user()->id]); try { DB::transaction(function () use ($order, $cart) { $cart->cartItems->each(function (CartItem $cartItem) use ($order) { $cartItem->product->decrement('stock', $cartItem->quantity); $order->addOrderLine($cartItem->product, $cartItem->quantity); }); $order->checkout(); $stripe = new StripePayment(config(‘services.stripe.key')); $stripe->charge($order->id, $order->total_amount); }); } catch (\Exception $e) { abort(Response::HTTP_BAD_REQUEST, trans('order::errors.failed')); } return new OrderResource($order); } }
  64. class OrderController extends Controller { public function store(StoreOrderRequest $request) {

    $cart = Cart::with('cartItems')->findOrFail($request->cart_id); $order = new Order(['user_id' => $request->user()->id]); try { DB::transaction(function () use ($order, $cart) { $cart->cartItems->each(function (CartItem $cartItem) use ($order) { $cartItem->product->decrement('stock', $cartItem->quantity); $order->addOrderLine($cartItem->product, $cartItem->quantity); }); $order->checkout(); $stripe = new StripePayment(config(‘services.stripe.key')); $stripe->charge($order->id, $order->total_amount); }); } catch (\Exception $e) { abort(Response::HTTP_BAD_REQUEST, trans('order::errors.failed')); } return new OrderResource($order); } }
  65. class OrderController extends Controller { public function store(StoreOrderRequest $request) {

    $cart = Cart::with('cartItems')->findOrFail($request->cart_id); $order = new Order(['user_id' => $request->user()->id]); try { DB::transaction(function () use ($order, $cart) { $cart->cartItems->each(function (CartItem $cartItem) use ($order) { $cartItem->product->decrement('stock', $cartItem->quantity); $order->addOrderLine($cartItem->product, $cartItem->quantity); }); $order->checkout(); $stripe = new StripePayment(config(‘services.stripe.key')); $stripe->charge($order->id, $order->total_amount); }); } catch (\Exception $e) { abort(Response::HTTP_BAD_REQUEST, trans('order::errors.failed')); } return new OrderResource($order); } }
  66. class OrderController extends Controller { public function store(StoreOrderRequest $request) {

    $cart = Cart::with('cartItems')->findOrFail($request->cart_id); $order = new Order(['user_id' => $request->user()->id]); try { DB::transaction(function () use ($order, $cart) { $cart->cartItems->each(function (CartItem $cartItem) use ($order) { $cartItem->product->decrement('stock', $cartItem->quantity); $order->addOrderLine($cartItem->product, $cartItem->quantity); }); $order->checkout(); $stripe = new StripePayment(config(‘services.stripe.key')); $stripe->charge($order->id, $order->total_amount); }); } catch (\Exception $e) { abort(Response::HTTP_BAD_REQUEST, trans('order::errors.failed')); } return new OrderResource($order); } }
  67. class OrderController extends Controller { public function store(StoreOrderRequest $request) {

    $cart = Cart::with('cartItems')->findOrFail($request->cart_id); $order = new Order(['user_id' => $request->user()->id]); try { DB::transaction(function () use ($order, $cart) { $cart->cartItems->each(function (CartItem $cartItem) use ($order) { $cartItem->product->decrement('stock', $cartItem->quantity); $order->addOrderLine($cartItem->product, $cartItem->quantity); }); $order->checkout(); $stripe = new StripePayment(config(‘services.stripe.key')); $stripe->charge($order->id, $order->total_amount); }); } catch (\Exception $e) { abort(Response::HTTP_BAD_REQUEST, trans('order::errors.failed')); } return new OrderResource($order); } }
  68. class OrderController extends Controller { public function store(StoreOrderRequest $request) {

    $cart = Cart::with('cartItems')->findOrFail($request->cart_id); $order = new Order(['user_id' => $request->user()->id]); try { DB::transaction(function () use ($order, $cart) { $cart->cartItems->each(function (CartItem $cartItem) use ($order) { $cartItem->product->decrement('stock', $cartItem->quantity); $order->addOrderLine($cartItem->product, $cartItem->quantity); }); $order->checkout(); $stripe = new StripePayment(config(‘services.stripe.key')); $stripe->charge($order->id, $order->total_amount); }); } catch (\Exception $e) { abort(Response::HTTP_BAD_REQUEST, trans('order::errors.failed')); } return new OrderResource($order); } }
  69. class OrderController extends Controller { public function store(StoreOrderRequest $request) {

    $cart = Cart::with('cartItems')->findOrFail($request->cart_id); $order = new Order(['user_id' => $request->user()->id]); try { DB::transaction(function () use ($order, $cart) { $cart->cartItems->each(function (CartItem $cartItem) use ($order) { $cartItem->product->decrement('stock', $cartItem->quantity); $order->addOrderLine($cartItem->product, $cartItem->quantity); }); $order->checkout(); $stripe = new StripePayment(config(‘services.stripe.key')); $stripe->charge($order->id, $order->total_amount); }); } catch (\Exception $e) { abort(Response::HTTP_BAD_REQUEST, trans('order::errors.failed')); } return new OrderResource($order); } }
  70. class OrderController extends Controller { public function store(StoreOrderRequest $request) {

    $cart = Cart::with('cartItems')->findOrFail($request->cart_id); $order = new Order(['user_id' => $request->user()->id]); try { DB::transaction(function () use ($order, $cart) { $cart->cartItems->each(function (CartItem $cartItem) use ($order) { $cartItem->product->decrement('stock', $cartItem->quantity); $order->addOrderLine($cartItem->product, $cartItem->quantity); }); $order->checkout(); $stripe = new StripePayment(config(‘services.stripe.key')); $stripe->charge($order->id, $order->total_amount); }); } catch (\Exception $e) { abort(Response::HTTP_BAD_REQUEST, trans('order::errors.failed')); } return new OrderResource($order); } }
  71. namespace Laracon\Order\Domain\Models; class CartItem extends Model { // ... public

    function cart(): BelongsTo { return $this->belongsTo(Cart::class); } public function product(): BelongsTo { return $this->belongsTo(Product::class); } }
  72. namespace Laracon\Order\Domain\Models; class CartItem extends Model { // ... public

    function cart(): BelongsTo { return $this->belongsTo(Cart::class); } public function product(): BelongsTo { return $this->belongsTo(Product::class); } }
  73. // src/Inventory/Contracts/ProductService.php namespace Laracon\Inventory\Contracts; interface ProductService { /** * Decrement

    product stock. */ public function decrementStock(int $productId, int $quantity): void; }
  74. namespace Laracon\Inventory\Infrastructure\Services; use Laracon\Inventory\Contracts\ProductService as ProductServiceContract; class ProductService implements ProductServiceContract

    { public function decrementStock(int $productId, int $quantity): void { $product = Product::find($productId); if (!$product) { throw new ProductNotFoundException($productId); } if ($product->stock < $quantity) { throw new OutOfStockException($productId); } if (!$product->is_active) { throw new InactiveProductException($productId); } $product->decrement('stock', $quantity); } }
  75. namespace Laracon\Inventory\Infrastructure\Services; use Laracon\Inventory\Contracts\ProductService as ProductServiceContract; class ProductService implements ProductServiceContract

    { public function decrementStock(int $productId, int $quantity): void { $product = Product::find($productId); if (!$product) { throw new ProductNotFoundException($productId); } if ($product->stock < $quantity) { throw new OutOfStockException($productId); } if (!$product->is_active) { throw new InactiveProductException($productId); } $product->decrement('stock', $quantity); } }
  76. namespace Laracon\Inventory\Providers; use Laracon\Inventory\Contracts\ProductService as ProductServiceContract; use Laracon\Inventory\Infrastructure\Services\ProductService; class InventoryServiceProvider

    extends ServiceProvider { public $bindings = [ ProductServiceContract::class => ProductService::class, ]; // ... }
  77. use Laracon\Inventory\Contracts\ProductService; class OrderController extends Controller { public function __construct(private

    ProductService $productService) {} public function store(StoreOrderRequest $request) { $cart = Cart::with('cartItems')->findOrFail($request->cart_id); $order = new Order(['user_id' => $request->user()->id]); try { DB::transaction(function () use ($order, $cart) { $cart->cartItems->each(function (CartItem $cartItem) use ($order) { $cartItem->product->decrement('stock', $cartItem->quantity); $order->addOrderLine($cartItem->product, $cartItem->quantity); }); // ... }); } catch (\Exception $e) { abort(Response::HTTP_BAD_REQUEST, trans('order::errors.failed')); } return new OrderResource($order); } }
  78. use Laracon\Inventory\Contracts\ProductService; class OrderController extends Controller { public function __construct(private

    ProductService $productService) {} public function store(StoreOrderRequest $request) { $cart = Cart::with('cartItems')->findOrFail($request->cart_id); $order = new Order(['user_id' => $request->user()->id]); try { DB::transaction(function () use ($order, $cart) { $cart->cartItems->each(function (CartItem $cartItem) use ($order) { $cartItem->product->decrement('stock', $cartItem->quantity); $order->addOrderLine($cartItem->product, $cartItem->quantity); }); // ... }); } catch (\Exception $e) { abort(Response::HTTP_BAD_REQUEST, trans('order::errors.failed')); } return new OrderResource($order); } }
  79. use Laracon\Inventory\Contracts\ProductService; class OrderController extends Controller { public function __construct(private

    ProductService $productService) {} public function store(StoreOrderRequest $request) { $cart = Cart::with('cartItems')->findOrFail($request->cart_id); $order = new Order(['user_id' => $request->user()->id]); try { DB::transaction(function () use ($order, $cart) { $cart->cartItems->each(function (CartItem $cartItem) use ($order) { $this->productService->decrementStock($cartItem->product_id, $cartItem->quantity); $order->addOrderLine($cartItem->product, $cartItem->quantity); }); // ... }); } catch (\Exception $e) { abort(Response::HTTP_BAD_REQUEST, trans('order::errors.failed')); } return new OrderResource($order); } }
  80. use Laracon\Inventory\Contracts\ProductService; class OrderController extends Controller { public function __construct(private

    ProductService $productService) {} public function store(StoreOrderRequest $request) { $cart = Cart::with('cartItems')->findOrFail($request->cart_id); $order = new Order(['user_id' => $request->user()->id]); try { DB::transaction(function () use ($order, $cart) { $cart->cartItems->each(function (CartItem $cartItem) use ($order) { $this->productService->decrementStock($cartItem->product_id, $cartItem->quantity); $order->addOrderLine($cartItem->product, $cartItem->quantity); }); // ... }); } catch (\Exception $e) { abort(Response::HTTP_BAD_REQUEST, trans('order::errors.failed')); } return new OrderResource($order); } }
  81. namespace Laracon\Order\Domain\Models; use Laracon\Inventory\Domain\Models\Product; class Order extends Model { //

    ... protected $orderLines = []; public function orderLines(): HasMany { return $this->hasMany(OrderLine::class); } public function addOrderLine(Product $product, int $quantity): void { $orderLine = new OrderLine([ 'product_id' => $product->id, 'product_name' => $product->name, 'price' => $product->price, 'quantity' => $quantity, ]); $this->orderLines[] = $orderLine; } }
  82. namespace Laracon\Order\Domain\Models; use Laracon\Inventory\Domain\Models\Product; class Order extends Model { //

    ... protected $orderLines = []; public function orderLines(): HasMany { return $this->hasMany(OrderLine::class); } public function addOrderLine(Product $product, int $quantity): void { $orderLine = new OrderLine([ 'product_id' => $product->id, 'product_name' => $product->name, 'price' => $product->price, 'quantity' => $quantity, ]); $this->orderLines[] = $orderLine; } }
  83. %50%BUB5SBOTGFS0CKFDU w γϯϓϧͳσʔλίϯςφΫϥε w ଞͷϞδϡʔϧʹσʔλΛఏڙ w Կͷڍಈ΋࣋ͨͳ͍ w 1010 1MBJO0ME1)10CKFDU

  84. namespace Laracon\Inventory\Contracts\DataTransferObjects; class ProductDto { public function __construct( public readonly

    int $id, public readonly string $name, public readonly int $price, ) {} }
  85. namespace Laracon\Inventory\Contracts; use Laracon\Inventory\Contracts\DataTransferObjects\ProductDto; interface ProductService { /** * Get

    product by product id. */ public function getProductById(int $productId): ProductDto; }
  86. namespace Laracon\Inventory\Infrastructure\Services; use Laracon\Inventory\Contracts\DataTransferObjects\ProductDto; use Laracon\Inventory\Contracts\ProductService as ProductServiceContract; use Laracon\Inventory\Domain\Models\Product;

    class ProductService implements ProductServiceContract { public function getProductById(int $productId): ProductDto { $product = Product::find($productId); if (!$product) { throw new ProductNotFoundException($productId); } return new ProductDto( $product->id, $product->name, $product->price, ); } }
  87. use Laracon\Inventory\Contracts\ProductService; class OrderController extends Controller { public function __construct(private

    ProductService $productService) {} public function store(StoreOrderRequest $request) { $cart = Cart::with('cartItems')->findOrFail($request->cart_id); $order = new Order(['user_id' => $request->user()->id]); try { DB::transaction(function () use ($order, $cart) { $cart->cartItems->each(function (CartItem $cartItem) use ($order) { $this->productService->decrementStock($cartItem->product_id, $cartItem->quantity); $order->addOrderLine($cartItem->product, $cartItem->quantity); }); // ... }); } catch (\Exception $e) { abort(Response::HTTP_BAD_REQUEST, trans('order::errors.failed')); } return new OrderResource($order); } }
  88. use Laracon\Inventory\Contracts\ProductService; class OrderController extends Controller { public function __construct(private

    ProductService $productService) {} public function store(StoreOrderRequest $request) { $cart = Cart::with('cartItems')->findOrFail($request->cart_id); $order = new Order(['user_id' => $request->user()->id]); try { DB::transaction(function () use ($order, $cart) { $cart->cartItems->each(function (CartItem $cartItem) use ($order) { $this->productService->decrementStock($cartItem->product_id, $cartItem->quantity); $product = $this->productService->getProductById($cartItem->product_id); $order->addOrderLine($cartItem->product, $cartItem->quantity); }); // ... }); } catch (\Exception $e) { abort(Response::HTTP_BAD_REQUEST, trans('order::errors.failed')); } return new OrderResource($order); } }
  89. use Laracon\Inventory\Contracts\ProductService; class OrderController extends Controller { public function __construct(private

    ProductService $productService) {} public function store(StoreOrderRequest $request) { $cart = Cart::with('cartItems')->findOrFail($request->cart_id); $order = new Order(['user_id' => $request->user()->id]); try { DB::transaction(function () use ($order, $cart) { $cart->cartItems->each(function (CartItem $cartItem) use ($order) { $this->productService->decrementStock($cartItem->product_id, $cartItem->quantity); $product = $this->productService->getProductById($cartItem->product_id); $order->addOrderLine($product, $cartItem->quantity); }); // ... }); } catch (\Exception $e) { abort(Response::HTTP_BAD_REQUEST, trans('order::errors.failed')); } return new OrderResource($order); } }
  90. namespace Laracon\Order\Domain\Models; use Laracon\Inventory\Domain\Models\Product; class Order extends Model { //

    ... protected $orderLines = []; public function addOrderLine(Product $product, int $quantity): void { $orderLine = new OrderLine([ 'product_id' => $product->id, 'product_name' => $product->name, 'price' => $product->price, 'quantity' => $quantity, ]); $this->orderLines[] = $orderLine; } }
  91. namespace Laracon\Order\Domain\Models; use Laracon\Inventory\Domain\Models\Product; class Order extends Model { //

    ... protected $orderLines = []; public function addOrderLine(Product $product, int $quantity): void { $orderLine = new OrderLine([ 'product_id' => $product->id, 'product_name' => $product->name, 'price' => $product->price, 'quantity' => $quantity, ]); $this->orderLines[] = $orderLine; } }
  92. namespace Laracon\Order\Domain\Models; use Laracon\Inventory\Contracts\DataTransferObjects\ProductDto; class Order extends Model { //

    ... protected $orderLines = []; public function addOrderLine(ProductDto $product, int $quantity): void { $orderLine = new OrderLine([ 'product_id' => $product->id, 'product_name' => $product->name, 'price' => $product->price, 'quantity' => $quantity, ]); $this->orderLines[] = $orderLine; } }
  93. namespace Laracon\Order\Domain\Models; use Laracon\Inventory\Contracts\DataTransferObjects\ProductDto; class Order extends Model { //

    ... protected $orderLines = []; public function addOrderLine(ProductDto $product, int $quantity): void { $orderLine = new OrderLine([ 'product_id' => $product->id, 'product_name' => $product->name, 'price' => $product->price, 'quantity' => $quantity, ]); $this->orderLines[] = $orderLine; } }
  94. use Laracon\Inventory\Contracts\ProductService; class OrderController extends Controller { public function __construct(private

    ProductService $productService) {} public function store(StoreOrderRequest $request) { $cart = Cart::with('cartItems')->findOrFail($request->cart_id); $order = new Order(['user_id' => $request->user()->id]); try { DB::transaction(function () use ($order, $cart) { // ... $order->checkout(); $stripe = new StripePayment(config(‘services.stripe.key')); $stripe->charge($order->id, $order->total_amount); }); } catch (\Exception $e) { abort(Response::HTTP_BAD_REQUEST, trans('order::errors.failed')); } return new OrderResource($order); } }
  95. use Laracon\Inventory\Contracts\ProductService; class OrderController extends Controller { public function __construct(private

    ProductService $productService) {} public function store(StoreOrderRequest $request) { $cart = Cart::with('cartItems')->findOrFail($request->cart_id); $order = new Order(['user_id' => $request->user()->id]); try { DB::transaction(function () use ($order, $cart) { // ... $order->checkout(); $stripe = new StripePayment(config(‘services.stripe.key')); $stripe->charge($order->id, $order->total_amount); }); } catch (\Exception $e) { abort(Response::HTTP_BAD_REQUEST, trans('order::errors.failed')); } return new OrderResource($order); } }
  96. namespace Laracon\Payment\Contracts; interface PaymentService { /** * Process payment for

    the given amount. */ public function charge(int $orderId, int $amount): void; }
  97. namespace Laracon\Payment\Infrastructure\Services; use Stripe\StripeClient; use Laracon\Payment\Contracts\PaymentService as PaymentServiceContract; class StripePayment

    implements PaymentServiceContract { private StripeClient $stripe; public function __construct(string $stripeKey) { $this->stripe = new StripeClient($stripeKey); } public function charge(int $orderId, int $amount): void { $this->stripe->charges->create([ 'amount' => $amount, 'currency' => 'usd', ]); // ... } }
  98. namespace Laracon\Payment\Providers; use Illuminate\Support\ServiceProvider; use Laracon\Payment\Contracts\PaymentService as PaymentServiceContract; use Laracon\Payment\Infrastructure\Services\StripePayment;

    class PaymentServiceProvider extends ServiceProvider { public function register() { $this->app->bind(PaymentServiceContract::class, function ($app) { return new StripePayment(config(‘services.stripe.key')); }); } }
  99. use Laracon\Inventory\Contracts\ProductService; class OrderController extends Controller { public function __construct(

    private ProductService $productService, ) {} public function store(StoreOrderRequest $request) { // ... try { DB::transaction(function () use ($order, $cart) { // ... $stripe = new StripePayment(config('service.stripe.key')); $stripe->charge($order->id, $order->total_amount); }); } catch (\Exception $e) { abort(Response::HTTP_BAD_REQUEST, trans('order::errors.failed')); } return new OrderResource($order); } }
  100. use Laracon\Inventory\Contracts\ProductService; use Laracon\Payment\Contracts\PaymentService; class OrderController extends Controller { public

    function __construct( private ProductService $productService, private PaymentService $paymentService, ) {} public function store(StoreOrderRequest $request) { // ... try { DB::transaction(function () use ($order, $cart) { // ... $stripe = new StripePayment(config('service.stripe.key')); $stripe->charge($order->id, $order->total_amount); }); } catch (\Exception $e) { abort(Response::HTTP_BAD_REQUEST, trans('order::errors.failed')); } return new OrderResource($order); } }
  101. use Laracon\Inventory\Contracts\ProductService; use Laracon\Payment\Contracts\PaymentService; class OrderController extends Controller { public

    function __construct( private ProductService $productService, private PaymentService $paymentService, ) {} public function store(StoreOrderRequest $request) { // ... try { DB::transaction(function () use ($order, $cart) { // ... $stripe = new StripePayment(config('service.stripe.key')); $stripe->charge($order->id, $order->total_amount); }); } catch (\Exception $e) { abort(Response::HTTP_BAD_REQUEST, trans('order::errors.failed')); } return new OrderResource($order); } }
  102. use Laracon\Inventory\Contracts\ProductService; use Laracon\Payment\Contracts\PaymentService; class OrderController extends Controller { public

    function __construct( private ProductService $productService, private PaymentService $paymentService, ) {} public function store(StoreOrderRequest $request) { // ... try { DB::transaction(function () use ($order, $cart) { // ... $stripe->charge($order->id, $order->total_amount); }); } catch (\Exception $e) { abort(Response::HTTP_BAD_REQUEST, trans('order::errors.failed')); } return new OrderResource($order); } }
  103. use Laracon\Inventory\Contracts\ProductService; use Laracon\Payment\Contracts\PaymentService; class OrderController extends Controller { public

    function __construct( private ProductService $productService, private PaymentService $paymentService, ) {} public function store(StoreOrderRequest $request) { // ... try { DB::transaction(function () use ($order, $cart) { // ... $this->paymentService->charge($order->id, $order->total_amount); }); } catch (\Exception $e) { abort(Response::HTTP_BAD_REQUEST, trans('order::errors.failed')); } return new OrderResource($order); } }
  104. namespace Laracon\Payment\Providers; use Illuminate\Support\ServiceProvider; use Laracon\Payment\Contracts\PaymentService as PaymentServiceContract; use Laracon\Payment\Infrastructure\Services\StripePayment;

    class PaymentServiceProvider extends ServiceProvider { public function register() { $this->app->bind(PaymentServiceContract::class, function ($app) { return new StripePayment(config(‘services.stripe.key')); }); } }
  105. namespace Laracon\Payment\Providers; use Illuminate\Support\ServiceProvider; use Laracon\Payment\Contracts\PaymentService as PaymentServiceContract; use Laracon\Payment\Infrastructure\Services\PaddlePayment;

    class PaymentServiceProvider extends ServiceProvider { public function register() { $this->app->bind(PaymentServiceContract::class, function ($app) { return new PaddlePayment(config(‘services.paddle.key’)); }); } }
  106. Order Inventory Payment Shipping Payment Provider Warehouse System Cart Cart

    Item Order Order Line 2. Update inventory 3. Process payment 5. Notify warehouse 4. Save order 1. Checkout
  107. Order Inventory Payment Shipping Payment Provider Warehouse System Cart Cart

    Item Order Order Line OrderFul fi lled Publish Subscribe 2. Update inventory 3. Process payment 4. Save order 1. Checkout
  108. class OrderController extends Controller { // ... public function store(StoreOrderRequest

    $request) { // ... try { DB::transaction(function () use ($order, $cart) { // ... $this->paymentService->charge($order->id, $order->total_amount); }); } catch (\Exception $e) { abort(Response::HTTP_BAD_REQUEST, trans('order::errors.failed')); } return new OrderResource($order); } }
  109. class OrderController extends Controller { // ... public function store(StoreOrderRequest

    $request) { // ... try { DB::transaction(function () use ($order, $cart) { // ... $this->paymentService->charge($order->id, $order->total_amount); }); } catch (\Exception $e) { abort(Response::HTTP_BAD_REQUEST, trans('order::errors.failed')); } OrderFulfilled::dispatch($order->id); return new OrderResource($order); } }
  110. class OrderController extends Controller { // ... public function store(StoreOrderRequest

    $request) { // ... try { DB::transaction(function () use ($order, $cart) { // ... $this->paymentService->charge($order->id, $order->total_amount); }); } catch (\Exception $e) { abort(Response::HTTP_BAD_REQUEST, trans('order::errors.failed')); } OrderFulfilled::dispatch($order->id); return new OrderResource($order); } }
  111. namespace Laracon\Shipping\Providers; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; use Laracon\Order\Contracts\Events\OrderFulfilled; use Laracon\Shipping\Domain\Listeners\NotifyWarehouse;

    class EventServiceProvider extends ServiceProvider { protected $listen = [ OrderFulfilled::class => [ NotifyWarehouse::class, ] ]; }
  112. namespace Laracon\Shipping\Domain\Listeners; use Laracon\Order\Contracts\Events\OrderFulfilled; class NotifyWarehouse { public function handle(OrderFulfilled

    $event) { // Notify warehouse system } }
  113. 5FTUJOH.PEVMFT

  114. <phpunit> <testsuites> <testsuite name="Unit"> <directory suffix="Test.php">./src/**/Tests/Unit</directory> </testsuite> <testsuite name="Feature"> <directory

    suffix="Test.php">./src/**/Tests/Feature</directory> </testsuite> </testsuites> <coverage processUncoveredFiles="true"> <include> <directory suffix=".php">./src</directory> </include> <exclude> <directory suffix="Test.php">./src/**/Tests/*</directory> </exclude> <report> <clover outputFile="coverage/clover.xml"/> </report> </coverage> <!-- ... --> </phpunit>
  115. <phpunit> <testsuites> <testsuite name="Unit"> <directory suffix="Test.php">./src/**/Tests/Unit</directory> </testsuite> <testsuite name="Feature"> <directory

    suffix="Test.php">./src/**/Tests/Feature</directory> </testsuite> </testsuites> <coverage processUncoveredFiles="true"> <include> <directory suffix=".php">./src</directory> </include> <exclude> <directory suffix="Test.php">./src/**/Tests/*</directory> </exclude> <report> <clover outputFile="coverage/clover.xml"/> </report> </coverage> <!-- ... --> </phpunit>
  116. <phpunit> <testsuites> <testsuite name="Unit"> <directory suffix="Test.php">./src/**/Tests/Unit</directory> </testsuite> <testsuite name="Feature"> <directory

    suffix="Test.php">./src/**/Tests/Feature</directory> </testsuite> </testsuites> <coverage processUncoveredFiles="true"> <include> <directory suffix=".php">./src</directory> </include> <exclude> <directory suffix="Test.php">./src/**/Tests/*</directory> </exclude> <report> <clover outputFile="coverage/clover.xml"/> </report> </coverage> <!-- ... --> </phpunit>
  117. it('creates a new order', function () { Event::fake(); $user =

    User::factory()->create(); $cart = Cart::factory()->create(['user_id' => $user->id]); // create some other test data... mock(ProductService::class, function ($mock) use ($product) { $mock->shouldReceive('decrementStock') ->with($product->id, 1) ->once(); $mock->shouldReceive('getProductById') ->with($product->id) ->once() ->andReturn($product); }); mock(PaymentService::class) ->shouldReceive('charge') ->once(); // ...
  118. it('creates a new order', function () { Event::fake(); $user =

    User::factory()->create(); $cart = Cart::factory()->create(['user_id' => $user->id]); // create some other test data... mock(ProductService::class, function ($mock) use ($product) { $mock->shouldReceive('decrementStock') ->with($product->id, 1) ->once(); $mock->shouldReceive('getProductById') ->with($product->id) ->once() ->andReturn($product); }); mock(PaymentService::class) ->shouldReceive('charge') ->once(); // ...
  119. it('creates a new order', function () { Event::fake(); $user =

    User::factory()->create(); $cart = Cart::factory()->create(['user_id' => $user->id]); // create some other test data... mock(ProductService::class, function ($mock) use ($product) { $mock->shouldReceive('decrementStock') ->with($product->id, 1) ->once(); $mock->shouldReceive('getProductById') ->with($product->id) ->once() ->andReturn($product); }); mock(PaymentService::class) ->shouldReceive('charge') ->once(); // ...
  120. Ϟδϡʔϧؒͷ֎෦Ωʔ੍໿͸ີ݁߹ʹͭͳ͕Δ Order module Inventory module Cart Items Products product_id id

  121. it('creates a new order', function () { Event::fake(); $user =

    User::factory()->create(); $cart = Cart::factory()->create(['user_id' => $user->id]); // create some other test data... mock(ProductService::class, function ($mock) use ($product) { $mock->shouldReceive('decrementStock') ->with($product->id, 1) ->once(); $mock->shouldReceive('getProductById') ->with($product->id) ->once() ->andReturn($product); }); mock(PaymentService::class) ->shouldReceive('charge') ->once(); // ...
  122. // ... $order = postJson('/order-module/orders', ['cart_id' => $cart->id]) ->assertCreated() ->json('data');

    assertDatabaseHas('orders', [ 'id' => $order['id'], ]); Event::assertDispatched(OrderFulfilled::class, $order['id']);
  123. ϞδϡʔϧͷυϝΠϯڥքΛڧ੍

  124. %FQUSBDΛ࢖ͬͨ੩తίʔυղੳ w ϨΠϠʔΛఆٛ w ϨΠϠʔؒΞΫηεͷϧʔϧΛఆٛ w ϧʔϧʹҧ൓͍ͯ͠ΔίʔυΛݕ஌ w ґଘؔ܎ΛՄࢹԽ

  125. composer require qossmic/deptrac-shim --dev sudo apt-get install graphviz // for

    visualising dependency graph
  126. parameters: paths: - ./src - ./app layers: - name: Common

    collectors: - type: directory regex: ./app/.* - name: Contracts collectors: - type: directory regex: .*/Contracts/.* - name: Vendor collectors: - type: bool must_not: - type: directory regex: ./(src|app)/.* - name: Inventory collectors: - type: bool must: - type: directory regex: ./src/Inventory/.* must_not: - type: directory regex: ./src/Inventory/Contracts/.* // deptrac.yaml
  127. parameters: paths: - ./src - ./app layers: - name: Common

    collectors: - type: directory regex: ./app/.* - name: Contracts collectors: - type: directory regex: .*/Contracts/.* - name: Vendor collectors: - type: bool must_not: - type: directory regex: ./(src|app)/.* - name: Inventory collectors: - type: bool must: - type: directory regex: ./src/Inventory/.* must_not: - type: directory regex: ./src/Inventory/Contracts/.* // deptrac.yaml
  128. parameters: paths: - ./src - ./app layers: - name: Common

    collectors: - type: directory regex: ./app/.* - name: Contracts collectors: - type: directory regex: .*/Contracts/.* - name: Vendor collectors: - type: bool must_not: - type: directory regex: ./(src|app)/.* - name: Inventory collectors: - type: bool must: - type: directory regex: ./src/Inventory/.* must_not: - type: directory regex: ./src/Inventory/Contracts/.* // deptrac.yaml
  129. parameters: paths: - ./src - ./app layers: - name: Common

    collectors: - type: directory regex: ./app/.* - name: Contracts collectors: - type: directory regex: .*/Contracts/.* - name: Vendor collectors: - type: bool must_not: - type: directory regex: ./(src|app)/.* - name: Inventory collectors: - type: bool must: - type: directory regex: ./src/Inventory/.* must_not: - type: directory regex: ./src/Inventory/Contracts/.* // deptrac.yaml
  130. parameters: paths: - ./src - ./app layers: - name: Common

    collectors: - type: directory regex: ./app/.* - name: Contracts collectors: - type: directory regex: .*/Contracts/.* - name: Vendor collectors: - type: bool must_not: - type: directory regex: ./(src|app)/.* - name: Inventory collectors: - type: bool must: - type: directory regex: ./src/Inventory/.* must_not: - type: directory regex: ./src/Inventory/Contracts/.* // deptrac.yaml
  131. parameters: paths: - ./src - ./app layers: - name: Common

    collectors: - type: directory regex: ./app/.* - name: Contracts collectors: - type: directory regex: .*/Contracts/.* - name: Vendor collectors: - type: bool must_not: - type: directory regex: ./(src|app)/.* - name: Inventory collectors: - type: bool must: - type: directory regex: ./src/Inventory/.* must_not: - type: directory regex: ./src/Inventory/Contracts/.* // deptrac.yaml
  132. parameters: paths: - ./src - ./app layers: - name: Common

    collectors: - type: directory regex: ./app/.* - name: Contracts collectors: - type: directory regex: .*/Contracts/.* - name: Vendor collectors: - type: bool must_not: - type: directory regex: ./(src|app)/.* - name: Inventory collectors: - type: bool must: - type: directory regex: ./src/Inventory/.* must_not: - type: directory regex: ./src/Inventory/Contracts/.* // deptrac.yaml
  133. parameters: paths: - ./src - ./app layers: - name: Common

    collectors: - type: directory regex: ./app/.* - name: Contracts collectors: - type: directory regex: .*/Contracts/.* - name: Vendor collectors: - type: bool must_not: - type: directory regex: ./(src|app)/.* - name: Inventory collectors: - type: bool must: - type: directory regex: ./src/Inventory/.* must_not: - type: directory regex: ./src/Inventory/Contracts/.* // deptrac.yaml
  134. parameters: paths: - ./src - ./app layers: # ... ruleset:

    Common: - Vendor Contracts: - Vendor - Common Inventory: - Contracts - Vendor - Common Order: - Contracts - Vendor - Common Payment: - Contracts - Vendor - Common Shipping: - Contracts - Vendor - Common // deptrac.yaml
  135. parameters: paths: - ./src - ./app layers: # ... ruleset:

    Common: - Vendor Contracts: - Vendor - Common Inventory: - Contracts - Vendor - Common Order: - Contracts - Vendor - Common Payment: - Contracts - Vendor - Common Shipping: - Contracts - Vendor - Common // deptrac.yaml
  136. parameters: paths: - ./src - ./app layers: # ... ruleset:

    Common: - Vendor Contracts: - Vendor - Common Inventory: - Contracts - Vendor - Common Order: - Contracts - Vendor - Common Payment: - Contracts - Vendor - Common Shipping: - Contracts - Vendor - Common // deptrac.yaml
  137. parameters: paths: - ./src - ./app layers: # ... ruleset:

    Common: - Vendor Contracts: - Vendor - Common Inventory: - Contracts - Vendor - Common Order: - Contracts - Vendor - Common Payment: - Contracts - Vendor - Common Shipping: - Contracts - Vendor - Common // deptrac.yaml
  138. parameters: paths: - ./src - ./app layers: # ... ruleset:

    Common: - Vendor Contracts: - Vendor - Common Inventory: - Contracts - Vendor - Common Order: - Contracts - Vendor - Common Payment: - Contracts - Vendor - Common Shipping: - Contracts - Vendor - Common // deptrac.yaml
  139. %FQUSBDJO$*

  140. None
  141. None
  142. ੩తίʔυղੳ͸׬ᘳͰ͸ͳ͍

  143. DB::table('products') ->where('id', $orderLine->product_id) ->decrement(‘stock', $orderLine->quantity); // src/Order/Domain/Models/Order.php

  144. ·ͱΊ w ϚΠΫϩαʔϏεͷલஈ֊ͱͯ͠ͷϞδϡϥʔϞϊϦε w ϞδϡʔϧԽ͢ΔͨΊͷσΟϨΫτϦߏ଄ w ίϯτϥΫτΛ௨ͨ͠Ϟδϡʔϧؒ௨৴ w ϞδϡʔϧͷςετͰ͸ɺίϯτϥΫτΛϞοΫ w

    %FQUSBDΛ࢖ͬͯυϝΠϯͷڥքΛڧ੍
  145. ϦϯΫू •https://github.com/avosalmon/modular-monolith-laravel •Modularising the Monolith - Laracon Online 2022 •Deconstructing

    the Monolith: Designing Software that Maximizes Developer Productivity •Under Deconstruction: The State of Shopify’s Monolith •Long live the Monolith! Monolithic Architecture != Big Ball of Mud •Strategic Monoliths and Microservices •Domain-Driven Laravel
  146. THANK YOU @avosalmon