Builder

laravel-module-builder

Builds reusable domain modules - pure business logic without routes/views

Overview

The laravel-module-builder agent creates self-contained domain modules. Unlike features (which include routes, views, and controllers), modules are pure business logic packages with contracts, services, DTOs, and tests that can be shared across applications or extracted into packages.

Responsibilities

  • Contract Definition - Interfaces for dependency inversion
  • Service Classes - Business logic implementation
  • DTOs - Data transfer objects for type safety
  • Value Objects - Immutable domain objects
  • Events - Domain events for decoupling
  • Tests - Unit tests for the module

What It Creates

Component Location Purpose
Contracts app/Modules/{Name}/Contracts/ Interface definitions
Services app/Modules/{Name}/Services/ Business logic
DTOs app/Modules/{Name}/DTOs/ Data transfer objects
Events app/Modules/{Name}/Events/ Domain events
Tests tests/Unit/Modules/{Name}/ Unit tests

Module Structure Example

app/Modules/Payment/
├── Contracts/
│   ├── PaymentGatewayInterface.php
│   └── PaymentResultInterface.php
├── Services/
│   └── PaymentService.php
├── DTOs/
│   ├── ChargeRequest.php
│   └── PaymentResult.php
├── Exceptions/
│   ├── PaymentFailedException.php
│   └── InvalidCardException.php
├── Events/
│   ├── PaymentSucceeded.php
│   └── PaymentFailed.php
└── PaymentServiceProvider.php

tests/Unit/Modules/Payment/
├── PaymentServiceTest.php
└── ChargeRequestTest.php

Generated Contract Example

<?php

namespace App\Modules\Payment\Contracts;

use App\Modules\Payment\DTOs\ChargeRequest;
use App\Modules\Payment\DTOs\PaymentResult;

interface PaymentGatewayInterface
{
    /**
     * Charge a payment method.
     *
     * @throws \App\Modules\Payment\Exceptions\PaymentFailedException
     */
    public function charge(ChargeRequest $request): PaymentResult;

    /**
     * Refund a previous charge.
     */
    public function refund(string $transactionId, int $amountInCents): PaymentResult;

    /**
     * Check if the gateway is available.
     */
    public function isAvailable(): bool;
}

Generated DTO Example

<?php

namespace App\Modules\Payment\DTOs;

use Illuminate\Contracts\Support\Arrayable;

readonly class ChargeRequest implements Arrayable
{
    public function __construct(
        public int $amountInCents,
        public string $currency,
        public string $paymentMethodId,
        public ?string $description = null,
        public array $metadata = [],
    ) {}

    public static function fromArray(array $data): self
    {
        return new self(
            amountInCents: $data['amount_in_cents'],
            currency: $data['currency'] ?? 'USD',
            paymentMethodId: $data['payment_method_id'],
            description: $data['description'] ?? null,
            metadata: $data['metadata'] ?? [],
        );
    }

    public function toArray(): array
    {
        return [
            'amount_in_cents' => $this->amountInCents,
            'currency' => $this->currency,
            'payment_method_id' => $this->paymentMethodId,
            'description' => $this->description,
            'metadata' => $this->metadata,
        ];
    }
}

Service Provider

<?php

namespace App\Modules\Payment;

use Illuminate\Support\ServiceProvider;
use App\Modules\Payment\Contracts\PaymentGatewayInterface;
use App\Modules\Payment\Services\StripeGateway;

class PaymentServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->bind(
            PaymentGatewayInterface::class,
            StripeGateway::class
        );
    }

    public function boot(): void
    {
        // Publish config if needed
        // $this->publishes([...]);
    }
}

When to Use Modules vs Features

Use Module When Use Feature When
Business logic is reusable Need CRUD with UI
May extract to package later App-specific feature
No routes/views needed Need controllers and views
Integration with external APIs User-facing functionality

Invoked By Commands

Called By

Guardrails

The module builder follows strict rules:

  • ALWAYS define contracts (interfaces) first
  • ALWAYS use DTOs for data transfer
  • ALWAYS bind implementations in ServiceProvider
  • NEVER add routes or controllers to modules
  • NEVER couple to specific implementations

See Also