andrebhas/laravel-brick
最新稳定版本:v1.0.0
Composer 安装命令:
composer require 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.
关键字:
README 文档
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."
✨ 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:
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature - Write tests for your changes
- Run the test suite:
composer test - Submit a pull request
📜 License
The MIT License (MIT). See LICENSE for details.
"Each module is a brick. Stack them wisely." — andrebhas/laravel-brick
统计信息
- 总下载量: 0
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 8
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-03-06