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