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 full-size slide

  2. RYUTA HAMASAKI
    @avosalmon

    View full-size slide

  3. 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 full-size slide

  4. Modularising the Monolith

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  7. .POPMJUIWT.JDSPTFSWJDFT

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  10. #JH#BMMPG.VE

    View full-size slide

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

    View full-size slide

  12. .JDSPTFSWJDFT"SDIJUFDUVSF
    HTTP HTTP HTTP

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  16. .PEVMBS.POPMJUI

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  20. %JTUSJCVUFE#JH#BMMTPG.VE

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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


    Module
    Payment


    Module
    Shipping


    Module
    Campaign


    Module
    Auth


    Module
    Inventory


    Module

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  28. .PEVMBSJTJOH-BSBWFM

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide



  32. 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 full-size slide

  33. 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 full-size slide

  34. 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 full-size slide

  35. 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 full-size slide

  36. 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 full-size slide

  37. 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 full-size slide

  38. 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 full-size 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 full-size 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 full-size slide

  41. 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 full-size slide

  42. 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 full-size slide

  43. 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 full-size slide

  44. '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 full-size slide

  45. {


    "autoload": {


    "psr-4": {


    "App\\": "app/",


    "Phpcon\\": "src/",


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


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


    }


    }


    }
    // composer.json

    View full-size slide

  46. {


    "autoload": {


    "psr-4": {


    "App\\": "app/",


    "Phpcon\\": "src/",


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


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


    }


    }


    }
    // composer.json

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  51. Order
    Inventory
    Payment
    Shipping
    Payment


    Provider
    Warehouse


    System
    Cart
    Cart


    Item
    Order
    Order


    Line

    View full-size slide

  52. Order
    Inventory
    Payment
    Shipping
    Payment


    Provider
    Warehouse


    System
    Cart
    Cart


    Item
    Order
    Order


    Line
    1. Checkout

    View full-size slide

  53. Order
    Inventory
    Payment
    Shipping
    Payment


    Provider
    Warehouse


    System
    Cart
    Cart


    Item
    Order
    Order


    Line
    2. Update inventory
    1. Checkout

    View full-size slide

  54. Order
    Inventory
    Payment
    Shipping
    Payment


    Provider
    Warehouse


    System
    Cart
    Cart


    Item
    Order
    Order


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

    View full-size slide

  55. 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 full-size slide

  56. 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 full-size slide

  57. 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 full-size slide

  58. 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 full-size slide

  59. 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 full-size slide

  60. 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 full-size slide

  61. 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 full-size slide

  62. 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 full-size 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 full-size 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 full-size slide

  65. 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 full-size slide

  66. 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 full-size slide

  67. // src/Inventory/Contracts/ProductService.php


    namespace Laracon\Inventory\Contracts;


    interface ProductService


    {


    /**


    * Decrement product stock.


    */


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


    }

    View full-size slide

  68. 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 full-size slide

  69. 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 full-size slide

  70. 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 full-size slide

  71. 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 full-size slide

  72. 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 full-size slide

  73. 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 full-size slide

  74. 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 full-size slide

  75. 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 full-size slide

  76. 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 full-size slide

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

    View full-size slide

  78. namespace Laracon\Inventory\Contracts\DataTransferObjects;


    class ProductDto


    {


    public function __construct(


    public readonly int $id,


    public readonly string $name,


    public readonly int $price,


    ) {}


    }

    View full-size slide

  79. namespace Laracon\Inventory\Contracts;


    use Laracon\Inventory\Contracts\DataTransferObjects\ProductDto;


    interface ProductService


    {


    /**


    * Get product by product id.


    */


    public function getProductById(int $productId): ProductDto;


    }

    View full-size slide

  80. 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 full-size slide

  81. 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 full-size slide

  82. 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 full-size slide

  83. 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 full-size slide

  84. 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 full-size slide

  85. 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 full-size slide

  86. 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 full-size slide

  87. 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 full-size 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) {


    // ...


    $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 full-size 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) {


    // ...


    $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 full-size slide

  90. namespace Laracon\Payment\Contracts;


    interface PaymentService


    {


    /**


    * Process payment for the given amount.


    */


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


    }

    View full-size slide

  91. 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 full-size slide

  92. 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 full-size slide

  93. 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 full-size slide

  94. 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 full-size slide

  95. 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 full-size slide

  96. 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 full-size slide

  97. 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 full-size 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 full-size slide

  99. 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 full-size slide

  100. 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 full-size slide

  101. 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 full-size slide

  102. 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 full-size slide

  103. 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 full-size slide

  104. 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 full-size slide

  105. 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 full-size slide

  106. namespace Laracon\Shipping\Domain\Listeners;


    use Laracon\Order\Contracts\Events\OrderFulfilled;


    class NotifyWarehouse


    {


    public function handle(OrderFulfilled $event)


    {


    // Notify warehouse system


    }


    }

    View full-size slide

  107. 5FTUJOH.PEVMFT

    View full-size slide










  108. ./src/**/Tests/Unit








    ./src/**/Tests/Feature














    ./src








    ./src/**/Tests/*





















    View full-size slide










  109. ./src/**/Tests/Unit








    ./src/**/Tests/Feature














    ./src








    ./src/**/Tests/*





















    View full-size slide










  110. ./src/**/Tests/Unit








    ./src/**/Tests/Feature














    ./src








    ./src/**/Tests/*





















    View full-size slide

  111. 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 full-size slide

  112. 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 full-size slide

  113. 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 full-size slide

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


    Items
    Products
    product_id


    id


    View full-size slide

  115. 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 full-size slide

  116. // ...


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


    ->assertCreated()


    ->json('data');


    assertDatabaseHas('orders', [


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


    ]);


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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  120. 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 full-size slide

  121. 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 full-size slide

  122. 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 full-size slide

  123. 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 full-size slide

  124. 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 full-size slide

  125. 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 full-size 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 full-size 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 full-size slide

  128. 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 full-size slide

  129. 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 full-size slide

  130. 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 full-size slide

  131. 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 full-size slide

  132. 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 full-size slide

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

    View full-size slide

  134. DB::table('products')


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


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

    View full-size slide

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

    View full-size slide

  136. ϦϯΫू
    •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 full-size slide

  137. THANK YOU
    @avosalmon

    View full-size slide