Module Lifecycle

AutoCom dispatches events at key points during module loading and tenant-level module management. These events allow you to hook into the module lifecycle for logging, monitoring, or custom behavior.

Lifecycle Overview

Application Boot
       │
       ▼
┌──────────────────┐
│ ModuleLoading    │ ◄── Event dispatched
└────────┬─────────┘
         │
    Load Module
         │
         ▼
    ┌────┴────┐
    │         │
Success    Failure
    │         │
    ▼         ▼
┌─────────┐  ┌──────────────┐
│ Loaded  │  │ LoadFailed   │ ◄── Events
└─────────┘  └──────────────┘

For tenant-level module management:

Tenant enables module     Tenant disables module
        │                         │
        ▼                         ▼
┌───────────────┐         ┌────────────────┐
│ ModuleEnabled │         │ ModuleDisabled │
└───────────────┘         └────────────────┘

Available Events

ModuleLoading

Dispatched before a module's service providers are registered.

namespace App\Core\Events;

class ModuleLoading
{
    public function __construct(
        public readonly string $moduleName,
        public readonly array $config
    ) {}
}

Use cases:

  • Pre-load logging
  • Configuration validation
  • Performance monitoring (start timer)

ModuleLoaded

Dispatched after a module successfully loads.

namespace App\Core\Events;

class ModuleLoaded
{
    public function __construct(
        public readonly string $moduleName,
        public readonly array $config
    ) {}
}

Use cases:

  • Success logging
  • Performance monitoring (end timer)
  • Cache warming
  • Feature flag initialization

ModuleLoadFailed

Dispatched when a module fails to load.

namespace App\Core\Events;

class ModuleLoadFailed
{
    public function __construct(
        public readonly string $moduleName,
        public readonly Throwable $exception
    ) {}
}

Use cases:

  • Error logging and alerting
  • Fallback behavior
  • Admin notifications
  • Health check reporting

ModuleEnabled

Dispatched when a tenant enables a module.

namespace App\Core\Events;

class ModuleEnabled
{
    public function __construct(
        public readonly string $moduleAlias,
        public readonly ?string $tenantId = null,
        public readonly array $settings = []
    ) {}
}

Use cases:

  • Tenant activity logging
  • Initial data seeding
  • Welcome emails
  • Analytics tracking

ModuleDisabled

Dispatched when a tenant disables a module.

namespace App\Core\Events;

class ModuleDisabled
{
    public function __construct(
        public readonly string $moduleAlias,
        public readonly ?string $tenantId = null
    ) {}
}

Use cases:

  • Cleanup tasks
  • Data export reminders
  • Analytics tracking
  • Resource deallocation

Listening to Events

Creating a Listener

namespace App\Listeners;

use App\Core\Events\ModuleLoaded;
use Illuminate\Support\Facades\Log;

class LogModuleLoaded
{
    public function handle(ModuleLoaded $event): void
    {
        Log::info("Module loaded: {$event->moduleName}", [
            'version' => $event->config['version'] ?? 'unknown',
            'type' => $event->config['type'] ?? 'standard',
        ]);
    }
}

Registering Listeners

In your EventServiceProvider:

namespace App\Providers;

use App\Core\Events\ModuleLoading;
use App\Core\Events\ModuleLoaded;
use App\Core\Events\ModuleLoadFailed;
use App\Core\Events\ModuleEnabled;
use App\Core\Events\ModuleDisabled;
use App\Listeners\LogModuleLoaded;
use App\Listeners\HandleModuleFailure;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

class EventServiceProvider extends ServiceProvider
{
    protected $listen = [
        ModuleLoaded::class => [
            LogModuleLoaded::class,
        ],
        ModuleLoadFailed::class => [
            HandleModuleFailure::class,
        ],
        ModuleEnabled::class => [
            // Your listeners
        ],
        ModuleDisabled::class => [
            // Your listeners
        ],
    ];
}

Using Closures

For quick listeners in a service provider:

use App\Core\Events\ModuleLoaded;
use Illuminate\Support\Facades\Event;

public function boot(): void
{
    Event::listen(ModuleLoaded::class, function (ModuleLoaded $event) {
        // Handle the event
    });
}

Practical Examples

Performance Monitoring

Track module load times:

namespace App\Listeners;

use App\Core\Events\ModuleLoading;
use App\Core\Events\ModuleLoaded;
use Illuminate\Support\Facades\Cache;

class ModulePerformanceMonitor
{
    public function handleLoading(ModuleLoading $event): void
    {
        Cache::put("module_load_start_{$event->moduleName}", microtime(true), 60);
    }

    public function handleLoaded(ModuleLoaded $event): void
    {
        $start = Cache::pull("module_load_start_{$event->moduleName}");

        if ($start) {
            $duration = (microtime(true) - $start) * 1000;

            Log::debug("Module {$event->moduleName} loaded in {$duration}ms");

            // Report to monitoring service
            if ($duration > 100) {
                Log::warning("Slow module load: {$event->moduleName} took {$duration}ms");
            }
        }
    }
}

Register both handlers:

protected $listen = [
    ModuleLoading::class => [
        [ModulePerformanceMonitor::class, 'handleLoading'],
    ],
    ModuleLoaded::class => [
        [ModulePerformanceMonitor::class, 'handleLoaded'],
    ],
];

Error Alerting

Send alerts when modules fail:

namespace App\Listeners;

use App\Core\Events\ModuleLoadFailed;
use App\Notifications\ModuleFailedNotification;
use Illuminate\Support\Facades\Notification;

class AlertOnModuleFailure
{
    public function handle(ModuleLoadFailed $event): void
    {
        // Log the error
        Log::error("Module failed to load: {$event->moduleName}", [
            'error' => $event->exception->getMessage(),
            'trace' => $event->exception->getTraceAsString(),
        ]);

        // Alert admins for core modules
        $config = $this->getModuleConfig($event->moduleName);

        if ($config['isCore'] ?? false) {
            Notification::route('slack', config('services.slack.webhook'))
                ->notify(new ModuleFailedNotification($event->moduleName, $event->exception));
        }
    }
}

Tenant Module Analytics

Track module usage across tenants:

namespace App\Listeners;

use App\Core\Events\ModuleEnabled;
use App\Core\Events\ModuleDisabled;
use App\Models\ModuleAnalytics;

class TrackModuleUsage
{
    public function handleEnabled(ModuleEnabled $event): void
    {
        ModuleAnalytics::create([
            'module_alias' => $event->moduleAlias,
            'tenant_id' => $event->tenantId,
            'action' => 'enabled',
            'settings' => $event->settings,
            'created_at' => now(),
        ]);
    }

    public function handleDisabled(ModuleDisabled $event): void
    {
        ModuleAnalytics::create([
            'module_alias' => $event->moduleAlias,
            'tenant_id' => $event->tenantId,
            'action' => 'disabled',
            'created_at' => now(),
        ]);
    }
}

Module-Specific Initialization

Run setup when a module is enabled for a tenant:

namespace Modules\StoreShopify\Listeners;

use App\Core\Events\ModuleEnabled;
use Modules\StoreShopify\Services\ShopifyService;

class InitializeShopifyForTenant
{
    public function handle(ModuleEnabled $event): void
    {
        if ($event->moduleAlias !== 'store-shopify') {
            return;
        }

        // Run Shopify-specific initialization
        app(ShopifyService::class)->initializeForTenant(
            $event->tenantId,
            $event->settings
        );
    }
}

Module Service Provider Hooks

In addition to events, modules can implement lifecycle methods in their service providers:

Register Phase

Called when the module is being registered (before boot):

class MyModuleServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        // Merge config
        $this->mergeConfigFrom(__DIR__ . '/../module.json', 'my-module');

        // Register services (not available yet to other modules)
        $this->app->singleton(MyService::class, fn() => new MyService());
    }
}

Boot Phase

Called after all modules are registered:

class MyModuleServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        // Load routes (after all services are available)
        $this->loadRoutes();

        // Load migrations
        $this->loadMigrationsFrom(__DIR__ . '/database/migrations');

        // Register with other modules
        $this->registerWidgets();
        $this->registerWorkflowNodes();
    }
}

Conditional Registration

Check if other modules are available:

public function boot(): void
{
    // Register widgets only if CoreDashboard is loaded
    if (class_exists(\Modules\CoreDashboard\Registries\WidgetRegistry::class)) {
        $this->registerWidgets();
    }

    // Register workflow nodes only if Workflows is loaded
    if (class_exists(\Modules\Workflows\Registries\WorkflowNodeRegistry::class)) {
        $this->registerWorkflowNodes();
    }
}

Debugging Lifecycle

Enable Debug Logging

Add to your .env:

LOG_LEVEL=debug

Then check logs:

tail -f storage/logs/laravel.log | grep ModuleLoader

Output:

[2024-01-20 10:00:00] local.DEBUG: ModuleLoader: Loading module 'Core'
[2024-01-20 10:00:00] local.DEBUG: ModuleLoader: Registered provider 'Modules\Core\CoreServiceProvider'
[2024-01-20 10:00:00] local.INFO: ModuleLoader: Successfully loaded module 'Core'
[2024-01-20 10:00:01] local.INFO: ModuleLoader: Loading complete {"loaded":5,"errors":0}

Check Load Order

php artisan module:list --graph

Next Steps