DEV Community

Cover image for Modular monolith architecture within Laravel, communication between different modules.
Md Abu Musa
Md Abu Musa

Posted on

Modular monolith architecture within Laravel, communication between different modules.

In a modular monolith architecture within Laravel, communication between different modules can be achieved in several ways, depending on the level of coupling and design preferences. Here are a few common approaches:

1. Direct Method Calls

  • This is the simplest approach where one module directly calls public methods or services from another module. You can use Laravel’s Dependency Injection to load services or repositories from another module.

Example:

   use Modules\Billing\Services\InvoiceService;

   class OrderService {
       protected $invoiceService;

       public function __construct(InvoiceService $invoiceService) {
           $this->invoiceService = $invoiceService;
       }

       public function createOrder($data) {
           // Communicate with another module, e.g., Billing
           $this->invoiceService->createInvoice($data);
       }
   }
Enter fullscreen mode Exit fullscreen mode

In this approach, you are using the service from one module inside another module by injecting it into the service class or controller.

2. Event-Driven Communication

  • An event-driven approach can help decouple modules. One module can dispatch an event, and another module can listen to that event and handle it accordingly.

Example:

  • Event Dispatching (from Module A):

     use App\Events\OrderCreated;
    
     class OrderService {
         public function createOrder($data) {
             // Order creation logic
    
             // Dispatch event
             event(new OrderCreated($data));
         }
     }
    
  • Event Listener (in Module B):

     use App\Events\OrderCreated;
    
     class InvoiceEventListener {
         public function handle(OrderCreated $event) {
             // Create an invoice when an order is created
             InvoiceService::createInvoice($event->data);
         }
     }
    

This makes the modules less dependent on each other and promotes a more decoupled design.

3. Service Providers and Facades

  • You can create a custom Service Provider in each module to register services and bind them to the Laravel Service Container. Facades can be used to provide an easy way to access services across modules.

Example:

  • In Module A, you could have a service provider:

     namespace Modules\Billing\Providers;
    
     use Illuminate\Support\ServiceProvider;
     use Modules\Billing\Services\InvoiceService;
    
     class BillingServiceProvider extends ServiceProvider {
         public function register() {
             $this->app->singleton(InvoiceService::class, function ($app) {
                 return new InvoiceService();
             });
         }
     }
    
  • In Module B, you can use the service by resolving it from the service container:

     $invoiceService = app(InvoiceService::class);
     $invoiceService->createInvoice($data);
    

4. Repository Pattern for Cross-Module Communication

  • If each module contains a repository layer for data access, you can inject the repository of one module into the service of another module.

Example:

   use Modules\Billing\Repositories\InvoiceRepository;

   class OrderService {
       protected $invoiceRepository;

       public function __construct(InvoiceRepository $invoiceRepository) {
           $this->invoiceRepository = $invoiceRepository;
       }

       public function createOrder($data) {
           // Use repository from another module
           $this->invoiceRepository->save($data);
       }
   }
Enter fullscreen mode Exit fullscreen mode

5. Use Laravel Jobs/Queues

  • For decoupled communication, especially for time-consuming tasks, one module can dispatch a job that will be handled by another module in the background.

Example:

   use Modules\Billing\Jobs\CreateInvoiceJob;

   class OrderService {
       public function createOrder($data) {
           // Dispatch job to another module
           CreateInvoiceJob::dispatch($data);
       }
   }
Enter fullscreen mode Exit fullscreen mode

Each of these approaches has different levels of decoupling and complexity, so your choice will depend on the needs of your application and whether you prefer stronger boundaries between modules or simpler integration methods.

Top comments (0)