andrebhas/laravel-brick

Build modular monolith Laravel applications with a solid foundation. Each module is a 'brick' that can be stacked wisely. Includes an optional internal bridge with async support, circuit breaker, and Pest testing.

Maintainers

Package info

github.com/andrebhas/laravel-brick

pkg:composer/andrebhas/laravel-brick

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-03-09 01:42 UTC

This package is auto-updated.

Last update: 2026-03-11 15:26:53 UTC


README

Build modular Laravel applications with a solid foundation. Each module is an independent, self-contained unit (a "brick") that can be composed into robust, scalable applications using Domain-Driven Design principles.

"Each module is a brick. Stack them wisely."

PHP Laravel License Tests

โœจ Features

Feature Description
๐Ÿ— Modular Core Create isolated modules with a standard DDD structure
๐ŸŒ‰ Internal Bridge Optional typed, validated cross-module communication
โšก Async Calls Dispatch bridge calls to queues, poll results by job ID
๐Ÿ›ก Circuit Breaker Protect against cascading failures (closed/open/half-open)
๐Ÿ”— Middleware Pipeline Logging, circuit breaker, caching per bridge call
๐Ÿงช Pest Testing Auto-generate test scaffolding with every module
๐Ÿ“ฆ Module Dependencies Modules declare dependencies; enable/disable with checks
โš™๏ธ Per-Module Config Each module has its own Config/config.php
๐ŸŽจ Asset Publishing Copy or symlink module assets to public/bricks/
๐Ÿš€ Octane Compatible No static state; fully safe for Laravel Octane

๐Ÿ“‹ Requirements

  • PHP 8.1 or higher
  • Laravel 9.x, 10.x, or 11.x

๐Ÿ“ฆ Installation

composer require andrebhas/laravel-brick

The package uses Laravel's auto-discovery. The service provider is registered automatically.

Publish Configuration

php artisan vendor:publish --tag=brick-config

Publish Stubs (optional, to customise scaffolding)

php artisan vendor:publish --tag=brick-stubs

๐Ÿ—‚ Module Structure

Layer-Driven Architecture (Default)

After running php artisan brick:make, each module follows this structure:

bricks/
โ””โ”€โ”€ Hotel/
    โ”œโ”€โ”€ module.json                  # Module manifest
    โ”œโ”€โ”€ Actions/
    โ”‚   โ””โ”€โ”€ CreateHotelAction.php
    โ”œโ”€โ”€ Models/
    โ”‚   โ””โ”€โ”€ Hotel.php
    โ”œโ”€โ”€ Repositories/
    โ”‚   โ”œโ”€โ”€ HotelRepositoryInterface.php
    โ”‚   โ””โ”€โ”€ EloquentHotelRepository.php
    โ”œโ”€โ”€ Services/
    โ”‚   โ””โ”€โ”€ HotelService.php
    โ”œโ”€โ”€ Bridge/                      # Optional: inter-module communication
    โ”‚   โ””โ”€โ”€ HotelBridge.php
    โ”œโ”€โ”€ Http/
    โ”‚   โ”œโ”€โ”€ Controllers/
    โ”‚   โ””โ”€โ”€ Requests/
    โ”œโ”€โ”€ Database/
    โ”‚   โ”œโ”€โ”€ Migrations/
    โ”‚   โ””โ”€โ”€ Seeders/
    โ”œโ”€โ”€ Routes/
    โ”‚   โ”œโ”€โ”€ api.php
    โ”‚   โ””โ”€โ”€ web.php
    โ”œโ”€โ”€ Resources/
    โ”‚   โ”œโ”€โ”€ views/
    โ”‚   โ”œโ”€โ”€ lang/
    โ”‚   โ””โ”€โ”€ assets/
    โ”œโ”€โ”€ Config/
    โ”‚   โ””โ”€โ”€ config.php
    โ”œโ”€โ”€ Providers/
    โ”‚   โ””โ”€โ”€ HotelServiceProvider.php
    โ”œโ”€โ”€ tests/
    โ”‚   โ”œโ”€โ”€ Pest.php
    โ”‚   โ”œโ”€โ”€ Feature/
    โ”‚   โ””โ”€โ”€ Unit/
    โ””โ”€โ”€ README.md

Feature-Driven Architecture

If you prefer to organizer your module by business features rather than technical layers, you can use the --features flag (or answer yes during the interactive prompt). This replaces the Actions directory with a Features directory:

bricks/
โ””โ”€โ”€ Hotel/
    โ”œโ”€โ”€ Features/
    โ”‚   โ””โ”€โ”€ CreateHotel/                     # A cohesive, isolated feature
    โ”‚       โ”œโ”€โ”€ CreateHotelAction.php        # Core logic
    โ”‚       โ”œโ”€โ”€ CreateHotelRequest.php       # Specific validation
    โ”‚       โ”œโ”€โ”€ CreateHotelResponse.php      # Formatting Output
    โ”‚       โ””โ”€โ”€ CreateHotelFeatureTest.php   # Feature tests lives here
    โ”œโ”€โ”€ Models/
    โ”œโ”€โ”€ Http/
    โ””โ”€โ”€ ... (other standard directories)

๐Ÿš€ Quick Start

1. Create a Module

php artisan brick:make

Interactive flow:

Nama modul:
 > Hotel

Deskripsi modul (opsional):
 > Modul manajemen hotel

Versi modul [1.0.0]:
 >

Buat jembatan (bridge)? (yes/no) [no]:
 > yes

Buat rute API? (yes/no) [no]:
 > yes

Buat rute web? (yes/no) [no]:
 > no

Buat tes (Pest)? (yes/no) [yes]:
 > yes

Gunakan struktur Feature-Driven? (yes/no) [no]:
 > yes

Membuat modul...
โœ“ Module [Hotel] created successfully

Or non-interactively:

php artisan brick:make Hotel --with-bridge --with-api --with-tests --features

2. Register the Module's Namespace

Add to composer.json:

{
    "autoload": {
        "psr-4": {
            "Bricks\\Hotel\\": "bricks/Hotel/"
        }
    }
}

Then regenerate autoload:

composer dump-autoload

3. Enable the Module

php artisan brick:enable Hotel

4. Run Migrations

php artisan brick:migrate Hotel

๐Ÿ“„ Module Manifest (module.json)

{
    "name": "Hotel",
    "description": "Modul manajemen hotel",
    "version": "1.0.0",
    "namespace": "Bricks\\Hotel",
    "active": true,
    "order": 0,
    "dependencies": ["Auth"],
    "bridge": {
        "class": "Bricks\\Hotel\\Bridge\\HotelBridge"
    },
    "author": {
        "name": "Andre Bhaskoro"
    }
}

๐ŸŒ‰ Bridge: Cross-Module Communication

1. Implement the Bridge

namespace Bricks\Hotel\Bridge;

use AndreBhas\Brick\Brick\Bridge\Contracts\Bridgeable;

class HotelBridge implements Bridgeable
{
    public static function getBridgeName(): string
    {
        return 'Hotel';
    }

    public static function getExposedMethods(): array
    {
        return ['getById', 'checkAvailability'];
    }

    public function getById(int $id): array
    {
        // Fetch hotel data...
        return ['id' => $id, 'name' => 'Grand Hotel'];
    }

    public function checkAvailability(int $hotelId, string $date): bool
    {
        return true;
    }
}

2. Synchronous Call (from another module)

use AndreBhas\Brick\Brick\Bridge\InternalGateway;

$gateway = app(InternalGateway::class);

// Goes through middleware pipeline (logging, circuit breaker, etc.)
$hotel = $gateway->call('Hotel', 'getById', 42);

3. Asynchronous Call

// Dispatch to queue, get a job ID
$jobId = $gateway->callAsync('Hotel', 'getById', 42);

// Later, poll for the result
$result = $gateway->getAsyncResult($jobId);
// ['status' => 'success', 'result' => [...], 'completed_at' => '...']
// or
// ['status' => 'pending', 'queued_at' => '...']
// or
// ['status' => 'failed', 'error' => '...']

4. Generate Bridge for Existing Module

php artisan brick:make-bridge Hotel

๐Ÿ›ก Circuit Breaker

The circuit breaker prevents cascading failures when a module's bridge is unreliable.

States

  • Closed ๐ŸŸข โ€” Normal operation; calls pass through
  • Open ๐Ÿ”ด โ€” Failures exceeded threshold; calls rejected immediately
  • Half-Open ๐ŸŸก โ€” Testing recovery with limited trial calls

Configuration (config/brick.php)

'circuit_breaker' => [
    'default' => [
        'threshold'              => 5,  // failures before opening
        'timeout'                => 30, // seconds before half-open
        'half_open_max_attempts' => 3,
    ],
],

Status Command

php artisan brick:bridge:status
+--------+------------+---------------+
| Bridge | State      | Failure Count |
+--------+------------+---------------+
| Hotel  | โ— CLOSED   | 0             |
| Auth   | โ— OPEN     | 7             |
+--------+------------+---------------+

Manual Reset

php artisan brick:bridge:status --reset=Hotel

๐Ÿงช Testing

Package Tests

composer test

Module Tests (Pest)

Each generated module includes its own tests directory with Pest setup:

# Run a specific module's tests
./vendor/bin/pest bricks/Hotel/tests/

Testing Helpers

BridgeFake โ€“ Mock bridge calls in tests

use AndreBhas\Brick\Brick\Testing\BridgeFake;
use AndreBhas\Brick\Brick\Bridge\InternalGateway;

$fake = new BridgeFake(
    container: app(),
    cache: app('cache')->store(),
    queue: app('queue'),
);

// Register a fake response
$fake->shouldReceive('Hotel', 'getById', fn ($id) => ['id' => $id, 'name' => 'Test Hotel']);

// Swap in the container
app()->instance(InternalGateway::class, $fake);

// ... run your code ...

// Assert calls
$fake->assertCalled('Hotel', 'getById', 1);
$fake->assertNotCalled('Hotel', 'delete');
$fake->assertNothingCalled(); // if no calls expected

CircuitBreakerFake โ€“ Simulate circuit states

use AndreBhas\Brick\Brick\Testing\CircuitBreakerFake;
use AndreBhas\Brick\Brick\Bridge\CircuitBreaker;

$fake = new CircuitBreakerFake();
$fake->forceOpen('Hotel');    // simulate open circuit
$fake->forceHalfOpen('Payment');
$fake->forceClose('Auth');

app()->instance(CircuitBreaker::class, $fake);

Generate Test File

# Feature test
php artisan brick:make-test Hotel HotelAvailability --type=feature

# Unit test
php artisan brick:make-test Hotel HotelPricing --type=unit

๐ŸŽฎ Artisan Commands Reference

Command Description
brick:make Create a new module interactively
brick:make-feature {module} {feature} Scaffold a full feature (Action, Request, etc)
brick:make-action {module} {name} Generate an Action class
brick:make-request {module} {name} Generate a FormRequest class
brick:make-response {module} {name} Generate a Response/Resource class
brick:make-job {module} {name} Generate a specific Job
brick:make-event {module} {name} Generate an Event
brick:make-listener {module} {name} Generate a Listener
brick:make-bridge {module} Generate bridge for a module
brick:make-test {module} {name} Generate a Pest test file
brick:bridge:status Show circuit breaker status for all bridges
brick:enable {module} Enable a module (checks dependencies)
brick:disable {module} Disable a module (checks dependents)
brick:publish-assets {module?} Publish module assets to public/
brick:migrate {module} Run module migrations
brick:rollback {module} Rollback module migrations
brick:refresh {module} Refresh (rollback + re-run) module migrations
brick:seed {module} Run module seeders

Command Options

# Enable with --force to skip dependency check
php artisan brick:enable Hotel --force

# Disable with --force to skip dependents check
php artisan brick:disable Hotel --force

# Publish assets as symlinks
php artisan brick:publish-assets Hotel --symlink

# Run specific number of rollback steps
php artisan brick:rollback Hotel --step=2

# Refresh and seed
php artisan brick:refresh Hotel --seed

# Run specific seeder class
php artisan brick:seed Hotel --class="Bricks\\Hotel\\Database\\Seeders\\HotelDemoSeeder"

โš™๏ธ Configuration Reference

// config/brick.php

return [
    // Root directory for all modules
    'modules_path' => base_path('bricks'),

    'bridge' => [
        'enabled' => env('BRICK_BRIDGE_ENABLED', true),

        // Middleware applied to every synchronous bridge call
        'middlewares' => [
            \AndreBhas\Brick\Brick\Bridge\Middleware\LoggingMiddleware::class,
            \AndreBhas\Brick\Brick\Bridge\Middleware\CircuitBreakerMiddleware::class,
            // \AndreBhas\Brick\Brick\Bridge\Middleware\CacheMiddleware::class,
        ],

        'circuit_breaker' => [
            'default' => [
                'threshold' => 5,
                'timeout'   => 30,
                'half_open_max_attempts' => 3,
            ],
        ],

        'queue'       => env('BRICK_BRIDGE_QUEUE', 'default'),
        'cache_store' => env('BRICK_BRIDGE_CACHE', null),
        'cache_ttl'   => 3600,
    ],

    'assets' => [
        'publish_method' => env('BRICK_ASSETS_METHOD', 'copy'), // 'copy' or 'symlink'
        'public_path'    => public_path('bricks'),
    ],

    'dependencies' => [
        'auto_enable' => env('BRICK_AUTO_ENABLE_DEPENDENCIES', false),
    ],
];

Environment Variables

Variable Default Description
BRICK_BRIDGE_ENABLED true Enable/disable the bridge globally
BRICK_BRIDGE_QUEUE default Queue for async bridge calls
BRICK_BRIDGE_CACHE null Cache store for circuit breaker & async results
BRICK_ASSETS_METHOD copy Asset publishing method (copy or symlink)
BRICK_AUTO_ENABLE_DEPENDENCIES false Auto-enable missing dependencies

๐Ÿ”ง Adding Custom Middleware

Create a middleware class:

namespace App\Brick\Middleware;

use Closure;

class RateLimitMiddleware
{
    public function handle(Closure $next, string $bridge, string $method, array $args): mixed
    {
        // Rate limiting logic...
        return $next($bridge, $method, $args);
    }
}

Register in config/brick.php:

'middlewares' => [
    \AndreBhas\Brick\Brick\Bridge\Middleware\LoggingMiddleware::class,
    \AndreBhas\Brick\Brick\Bridge\Middleware\CircuitBreakerMiddleware::class,
    \App\Brick\Middleware\RateLimitMiddleware::class, // <-- add here
],

๐Ÿงฉ Module Dependencies

Declare dependencies in module.json:

{
    "name": "Booking",
    "dependencies": ["Hotel", "Auth"]
}

When enabling:

php artisan brick:enable Booking
# Error: Cannot enable [Booking]. Missing dependencies: Hotel (inactive).
# Use --force to override.

php artisan brick:enable Hotel
php artisan brick:enable Booking  # Now works

โœ… Compatibility Notes

Package Notes
Spatie Media Library Use HasMedia trait directly in module models
Laravel Socialite Place OAuth controllers in Auth module's Http/Controllers/
Laravel Passport Register Passport in Auth module's service provider
Laravel Telescope Automatically captures all bridge calls via logging middleware
Laravel Debugbar No conflicts; bridge calls appear in the timeline
Laravel Octane Fully safe โ€“ no static state anywhere in the package

๐Ÿ“ Package Structure

laravel-brick/
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ BrickServiceProvider.php          # Auto-discovery entry point
โ”‚   โ””โ”€โ”€ Brick/
โ”‚       โ”œโ”€โ”€ Bridge/
โ”‚       โ”‚   โ”œโ”€โ”€ Contracts/Bridgeable.php  # Interface for bridge classes
โ”‚       โ”‚   โ”œโ”€โ”€ CircuitBreaker.php        # State machine (closed/open/half-open)
โ”‚       โ”‚   โ”œโ”€โ”€ InternalGateway.php       # Main call dispatcher
โ”‚       โ”‚   โ”œโ”€โ”€ Jobs/BridgeJob.php        # Queued async bridge job
โ”‚       โ”‚   โ””โ”€โ”€ Middleware/
โ”‚       โ”‚       โ”œโ”€โ”€ LoggingMiddleware.php
โ”‚       โ”‚       โ”œโ”€โ”€ CircuitBreakerMiddleware.php
โ”‚       โ”‚       โ””โ”€โ”€ CacheMiddleware.php
โ”‚       โ”œโ”€โ”€ Commands/                      # 11 Artisan commands
โ”‚       โ”œโ”€โ”€ Providers/
โ”‚       โ”‚   โ”œโ”€โ”€ BrickServiceProvider.php
โ”‚       โ”‚   โ””โ”€โ”€ BridgeServiceProvider.php
โ”‚       โ”œโ”€โ”€ Support/
โ”‚       โ”‚   โ”œโ”€โ”€ Module.php                # Module value object
โ”‚       โ”‚   โ””โ”€โ”€ ModuleManager.php         # Module discovery & state management
โ”‚       โ”œโ”€โ”€ Stubs/                        # Templates used by generators
โ”‚       โ”‚   โ”œโ”€โ”€ module/
โ”‚       โ”‚   โ””โ”€โ”€ bridge/
โ”‚       โ””โ”€โ”€ Testing/
โ”‚           โ”œโ”€โ”€ BridgeFake.php            # Test double for InternalGateway
โ”‚           โ””โ”€โ”€ CircuitBreakerFake.php    # Test double for CircuitBreaker
โ”œโ”€โ”€ config/brick.php
โ”œโ”€โ”€ tests/
โ”œโ”€โ”€ composer.json
โ”œโ”€โ”€ phpunit.xml
โ””โ”€โ”€ README.md

๐Ÿค Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/my-feature
  3. Write tests for your changes
  4. Run the test suite: composer test
  5. Submit a pull request

๐Ÿ“œ License

The MIT License (MIT). See LICENSE for details.

"Each module is a brick. Stack them wisely." โ€” andrebhas/laravel-brick