Architektura modularna w Laravel – uporządkuj duży projekt

calendar_today2025-10-07 folder Laravel /  Programowanie

Im większa aplikacja, tym większy chaos. Laravel świetnie sprawdza się przy małych i średnich projektach, ale wraz ze wzrostem liczby modeli, kontrolerów i zależności — zaczyna brakować porządku.

Bez struktury pojawia się bałagan: trudno znaleźć pliki, logika miesza się z infrastrukturą, a każdy kolejny feature psuje więcej niż poprawia. To prosta droga do technicznego długu.

Ten wpis pokaże, jak wprowadzić modularną architekturę w Laravel, aby duży projekt pozostał skalowalny, zrozumiały i łatwy w utrzymaniu. Dla zespołów, które chcą pisać czysty kod i rozwijać system bez frustracji.


Dlaczego monolity w Laravel szybko stają się problemem?

Laravel oferuje domyślną strukturę app/Http, app/Models, app/Services itd., która świetnie działa na starcie. Ale co, jeśli mamy 30 modeli, 50 kontrolerów i kilkanaście serwisów, które się wzajemnie wywołują?

W typowym dużym projekcie można zauważyć:

  • kontrolery z +500 liniami kodu
  • logikę biznesową ukrytą w kontrolerach i modelach
  • modele mające zbyt wiele odpowiedzialności (Active Record + logika)
  • services folder stający się śmietnikiem
  • trudności z testowaniem i refaktoryzacją

Taka struktura szybko zamienia się w "god class hell" i utrudnia wdrażanie nowych programistów. Każdy dotyka wszystkiego, przez co rośnie ryzyko regresji.


Modularna architektura w Laravel – różne podejścia

Rozwiązaniem jest modularna architektura – rozbijanie aplikacji na niezależne części o jasno określonych granicach. Laravel daje swobodę, by wdrożyć różne podejścia.

1. Modular Monolith (app/Modules)

Najprostszy sposób: tworzymy katalog app/Modules i tam trzymamy moduły funkcjonalne.

Struktura:

app/
└── Modules/
    ├── Users/
    │   ├── Http/
    │   ├── Models/
    │   ├── Services/
    │   └── Providers/
    └── Orders/

Każdy moduł to mini-aplikacja. Korzyści: separacja domeny, łatwa nawigacja, mniejsze pliki.

2. Package-based (Laravel Packages)

Moduły jako pełnoprawne pakiety Composer, ładowane lokalnie. Pozwala wydzielać nawet do osobnych repozytoriów.

Struktura:

packages/
└── Flexmind/
    └── Users/
        ├── src/
        ├── routes/
        └── composer.json

Dzięki temu możemy publikować pakiety lub ponownie wykorzystać w innych projektach.

3. Domain-Driven Design (DDD)

Podział na domeny biznesowe (Domain/Users, Domain/Invoices) oraz warstwy infrastrukturalne (App, Http, Database).

Struktura:

src/
├── Domain/
│   └── Users/
│       ├── Entities/
│       ├── Services/
│       └── Events/
├── App/
│   └── Users/
│       └── Controllers/

DDD zmusza do myślenia w kategoriach biznesowych, nie technicznych.

4. LUCID Architecture

Nowoczesne podejście bazujące na inspiracji z mobilnych frameworków. Rozdziela czysto domenową logikę od infrastruktury.

Główne warstwy:

  • Features – scenariusze aplikacyjne (np. CreateUserFeature)
  • Jobs – jednostki logiki
  • Services/Models – dane i logika
  • Policies, Listeners, Controllers – infrastruktura

Struktura:

app/
├── Domains/
├── Features/
├── Jobs/
└── Data/

Autoloading i konfiguracja modułów

Każdy moduł (lub pakiet) musi być poprawnie załadowany przez Composer. W composer.json dodajemy PSR-4 autoloading:

"autoload": {
  "psr-4": {
    "App\\": "app/",
    "Modules\\": "app/Modules/"
  }
}

Po zmianach:

composer dump-autoload

Automatyczne ładowanie:

RouteServiceProvider w module:

public function boot()
{
    $this->loadRoutesFrom(__DIR__.'/../Routes/web.php');
    $this->loadMigrationsFrom(__DIR__.'/../Database/Migrations');
    $this->loadViewsFrom(__DIR__.'/../Resources/views', 'users');
}

Rejestracja providera w AppServiceProvider lub automatycznie w pakiecie.


Komunikacja między modułami

Każdy moduł powinien być jak najbardziej niezależny. Komunikacja może odbywać się przez:

1. Interfejsy + Container

// Interface
interface UserNotifierInterface {
    public function notify(User $user): void;
}

// Binding
$this->app->bind(UserNotifierInterface::class, SmsNotifier::class);

// Usage
app(UserNotifierInterface::class)->notify($user);

2. Events

// Dispatch
event(new UserRegistered($user));

// Listener in another module
class SendWelcomeEmail {
    public function handle(UserRegistered $event) {
        // ...
    }
}

Events pomagają uniknąć twardych zależności i zachować luźne powiązania.


Przykład praktyczny: Moduł Użytkownicy

Struktura modułu Users:

app/
└── Modules/
    └── Users/
        ├── Http/
        │   └── Controllers/
        ├── Models/
        ├── Services/
        ├── Routes/
        ├── Database/
        │   └── Migrations/
        ├── Providers/
        │   └── UsersServiceProvider.php

Service Provider:

namespace Modules\Users\Providers;

use Illuminate\Support\ServiceProvider;

class UsersServiceProvider extends ServiceProvider
{
    public function boot()
    {
        $this->loadRoutesFrom(__DIR__.'/../Routes/web.php');
        $this->loadMigrationsFrom(__DIR__.'/../Database/Migrations');
    }
}

Kontroler:

namespace Modules\Users\Http\Controllers;

use App\Http\Controllers\Controller;
use Modules\Users\Models\User;

class UserController extends Controller
{
    public function index()
    {
        return view('users::index', ['users' => User::all()]);
    }
}

Najczęstsze błędy i pułapki

  1. Cykliczne zależności – np. moduł A zależy od B, a B od A. Unikaj, izoluj kontrakty.
  2. Duplikacja logiki – brak wspólnych serwisów czy helperów.
  3. Brak właściciela modułu – nie wiadomo, kto odpowiada za dany fragment.
  4. Brak dokumentacji architektury – dodaj README.md do każdego modułu.
  5. Zła konwencja nazewnictwa – ustandaryzuj nazwy klas i folderów.
  6. "Shared" foldery – często stają się śmietnikiem.

Podsumowanie i dobre praktyki

  • ✅ Dziel aplikację na niezależne moduły funkcyjne
  • ✅ Każdy moduł powinien mieć swój Service Provider
  • ✅ Korzystaj z PSR-4 i Composer autoload
  • ✅ Używaj Events i Contracts do komunikacji między modułami
  • ✅ Dokumentuj moduły (README, diagramy, odpowiedzialność)
  • ✅ Stosuj testy jednostkowe per moduł
  • ✅ Przeglądaj kod w kontekście modułu, nie tylko globalnie
  • ✅ Używaj narzędzi: phpstan, laravel-pint, composer scripts

Skontaktuj się z nami

keyboard_arrow_up Zadzwoń