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

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 ᖛཽ࡚ଠ

    View Slide

  2. RYUTA HAMASAKI
    @avosalmon

    View Slide

  3. View Slide

  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

    View Slide

  5. Modularising the Monolith

    View Slide

  6. "HFOEB
    w ϞϊϦεͱϚΠΫϩαʔϏεͷϝϦοτɾσϝϦοτ
    w ϞδϡϥʔϞϊϦεͱ͸
    w -BSBWFMΞϓϦέʔγϣϯͷϞδϡʔϧԽ
    w Ϟδϡʔϧؒͷίϛϡχέʔγϣϯ
    w ϞδϡϥʔϞϊϦεͳΞϓϦέʔγϣϯͷςετ
    w ੩తίʔυղੳͰυϝΠϯڥքΛڧ੍

    View Slide

  7. -BSBDPO0OMJOF
    https://youtu.be/0Rq-yHAwYjQ?t=4061

    View Slide

  8. .POPMJUIWT.JDSPTFSWJDFT

    View Slide

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

    View Slide

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

    View Slide

  11. #JH#BMMPG.VE

    View Slide

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

    View Slide

  13. .JDSPTFSWJDFT"SDIJUFDUVSF
    HTTP HTTP HTTP

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  17. .PEVMBS.POPMJUI

    View Slide

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

    View Slide

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

    View Slide

  20. Contracts
    Implementation
    Tests
    Contracts
    Implementation
    Tests
    Contracts
    Implementation
    Tests
    Inventory service Order module Payment module
    ஈ֊తͳϚΠΫϩαʔϏεԽ

    View Slide

  21. %JTUSJCVUFE#JH#BMMTPG.VE

    View Slide

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

    View Slide

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

    View Slide

  24. ϞδϡʔϧͱνʔϜߏ੒
    Order


    Module
    Payment


    Module
    Shipping


    Module
    Campaign


    Module
    Auth


    Module
    Inventory


    Module

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  29. .PEVMBSJTJOH-BSBWFM

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  33. View Slide

  34. View Slide

  35. View Slide

  36. View Slide

  37. View Slide



  38. use Illuminate\Support\Facades\Route;


    Route::prefix('order-module')


    ->middleware(['api', 'auth:sanctum'])


    ->group(function () {


    Route::apiResource('orders', OrderController::class);


    });


    // src/Order/routes.php

    View Slide

  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);


    }


    }

    View Slide

  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);


    }


    }

    View Slide

  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);


    }


    }

    View Slide

  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);


    }


    }

    View Slide

  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);


    }


    }

    View Slide

  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);


    }


    }

    View Slide

  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);


    }


    }

    View Slide

  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);


    }


    }

    View Slide

  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();


    }


    }

    View Slide

  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,


    ],


    ];


    }

    View Slide

  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);


    }


    }

    View Slide

  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

    View Slide

  51. {


    "autoload": {


    "psr-4": {


    "App\\": "app/",


    "Phpcon\\": "src/",


    "Database\\Factories\\": "database/factories/",


    "Database\\Seeders\\": "database/seeders/"


    }


    }


    }
    // composer.json

    View Slide

  52. {


    "autoload": {


    "psr-4": {


    "App\\": "app/",


    "Phpcon\\": "src/",


    "Database\\Factories\\": "database/factories/",


    "Database\\Seeders\\": "database/seeders/"


    }


    }


    }
    // composer.json

    View Slide

  53. άϩʔόϧͳΫϥε͸Ͳ͜ʹஔ͘ʁ

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  57. Order
    Inventory
    Payment
    Shipping
    Payment


    Provider
    Warehouse


    System
    Cart
    Cart


    Item
    Order
    Order


    Line

    View Slide

  58. Order
    Inventory
    Payment
    Shipping
    Payment


    Provider
    Warehouse


    System
    Cart
    Cart


    Item
    Order
    Order


    Line
    1. Checkout

    View Slide

  59. Order
    Inventory
    Payment
    Shipping
    Payment


    Provider
    Warehouse


    System
    Cart
    Cart


    Item
    Order
    Order


    Line
    2. Update inventory
    1. Checkout

    View Slide

  60. Order
    Inventory
    Payment
    Shipping
    Payment


    Provider
    Warehouse


    System
    Cart
    Cart


    Item
    Order
    Order


    Line
    2. Update inventory
    3. Process payment
    1. Checkout

    View Slide

  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

    View Slide

  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

    View Slide

  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);


    }


    }

    View Slide

  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);


    }


    }

    View Slide

  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);


    }


    }

    View Slide

  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);


    }


    }

    View Slide

  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);


    }


    }

    View Slide

  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);


    }


    }

    View Slide

  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);


    }


    }

    View Slide

  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);


    }


    }

    View Slide

  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);


    }


    }

    View Slide

  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);


    }


    }

    View Slide

  73. // src/Inventory/Contracts/ProductService.php


    namespace Laracon\Inventory\Contracts;


    interface ProductService


    {


    /**


    * Decrement product stock.


    */


    public function decrementStock(int $productId, int $quantity): void;


    }

    View Slide

  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);


    }


    }

    View Slide

  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);


    }


    }

    View Slide

  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,


    ];


    // ...


    }


    View Slide

  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);


    }


    }

    View Slide

  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);


    }


    }

    View Slide

  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);


    }


    }

    View Slide

  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);


    }


    }

    View Slide

  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;


    }


    }

    View Slide

  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;


    }


    }

    View Slide

  83. %50%BUB5SBOTGFS0CKFDU
    w γϯϓϧͳσʔλίϯςφΫϥε
    w ଞͷϞδϡʔϧʹσʔλΛఏڙ
    w Կͷڍಈ΋࣋ͨͳ͍
    w 1010 1MBJO0ME1)10CKFDU

    View Slide

  84. namespace Laracon\Inventory\Contracts\DataTransferObjects;


    class ProductDto


    {


    public function __construct(


    public readonly int $id,


    public readonly string $name,


    public readonly int $price,


    ) {}


    }

    View Slide

  85. namespace Laracon\Inventory\Contracts;


    use Laracon\Inventory\Contracts\DataTransferObjects\ProductDto;


    interface ProductService


    {


    /**


    * Get product by product id.


    */


    public function getProductById(int $productId): ProductDto;


    }

    View Slide

  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,


    );


    }


    }

    View Slide

  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);


    }


    }

    View Slide

  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);


    }


    }

    View Slide

  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);


    }


    }

    View Slide

  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;


    }


    }

    View Slide

  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;


    }


    }

    View Slide

  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;


    }


    }

    View Slide

  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;


    }


    }

    View Slide

  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);


    }


    }

    View Slide

  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);


    }


    }

    View Slide

  96. namespace Laracon\Payment\Contracts;


    interface PaymentService


    {


    /**


    * Process payment for the given amount.


    */


    public function charge(int $orderId, int $amount): void;


    }

    View Slide

  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',


    ]);


    // ...


    }


    }

    View Slide

  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'));


    });


    }


    }

    View Slide

  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);


    }


    }

    View Slide

  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);


    }


    }

    View Slide

  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);


    }


    }

    View Slide

  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);


    }


    }

    View Slide

  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);


    }


    }

    View Slide

  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'));


    });


    }


    }

    View Slide

  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’));


    });


    }


    }

    View Slide

  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

    View Slide

  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

    View Slide

  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);


    }


    }

    View Slide

  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);


    }


    }

    View Slide

  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);


    }


    }

    View Slide

  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,


    ]


    ];


    }

    View Slide

  112. namespace Laracon\Shipping\Domain\Listeners;


    use Laracon\Order\Contracts\Events\OrderFulfilled;


    class NotifyWarehouse


    {


    public function handle(OrderFulfilled $event)


    {


    // Notify warehouse system


    }


    }

    View Slide

  113. 5FTUJOH.PEVMFT

    View Slide










  114. ./src/**/Tests/Unit








    ./src/**/Tests/Feature














    ./src








    ./src/**/Tests/*





















    View Slide










  115. ./src/**/Tests/Unit








    ./src/**/Tests/Feature














    ./src








    ./src/**/Tests/*





















    View Slide










  116. ./src/**/Tests/Unit








    ./src/**/Tests/Feature














    ./src








    ./src/**/Tests/*





















    View Slide

  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();


    // ...


    View Slide

  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();


    // ...


    View Slide

  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();


    // ...


    View Slide

  120. Ϟδϡʔϧؒͷ֎෦Ωʔ੍໿͸ີ݁߹ʹͭͳ͕Δ
    Order module Inventory module
    Cart


    Items
    Products
    product_id


    id


    View Slide

  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();


    // ...


    View Slide

  122. // ...


    $order = postJson('/order-module/orders', ['cart_id' => $cart->id])


    ->assertCreated()


    ->json('data');


    assertDatabaseHas('orders', [


    'id' => $order['id'],


    ]);


    Event::assertDispatched(OrderFulfilled::class, $order['id']);

    View Slide

  123. ϞδϡʔϧͷυϝΠϯڥքΛڧ੍

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  139. %FQUSBDJO$*

    View Slide

  140. View Slide

  141. View Slide

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

    View Slide

  143. DB::table('products')


    ->where('id', $orderLine->product_id)


    ->decrement(‘stock', $orderLine->quantity);
    // src/Order/Domain/Models/Order.php

    View Slide

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

    View Slide

  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

    View Slide

  146. THANK YOU
    @avosalmon

    View Slide