Entwickler, der außergewöhnliche CRM- und Laravel-Lösungen liefert

Als erfahrener Entwickler spezialisiere ich mich auf Laravel- und Vue.js-Entwicklung, die Implementierung von Vtiger CRM sowie auf vielfältige WordPress-Projekte. Meine Arbeit zeichnet sich durch kreative, dynamische und benutzerzentrierte Weblösungen aus, die individuell an die Bedürfnisse meiner Kunden angepasst werden.

Die Kopplung zwischen Modulen ist eine der größten Herausforderungen in komplexen Laravel-Projekten. In diesem Artikel zeige ich, wie Sie mithilfe des Porto-Architekturpatterns Domänen klar trennen und über Facades und Dependency Provider kommunizieren lassen – ohne direkte Abhängigkeiten.

Domain-Entities in Laravel strukturieren: So reduzieren Sie Kopplungen mit dem Porto-Pattern

Die Kopplung zwischen Modulen ist eine der größten Herausforderungen in komplexen Laravel-Projekten. In diesem Artikel zeige ich, wie Sie mithilfe des Porto-Architekturpatterns Domänen klar trennen und über Facades und Dependency Provider kommunizieren lassen – ohne direkte Abhängigkeiten.

Warum Porto?

Das Porto-Pattern (inspiriert von Domain-Driven Design) bietet:

  • Klare Schichten: Business-Logik in Actions, Datenzugriff in Repositories.
  • Wiederverwendbarkeit: Module sind unabhängig voneinander einsetzbar.
  • Testbarkeit: Einfache Isolation durch Mocking von Schnittstellen.

Projektstruktur

src/  
├── Domains/  
│   ├── Category/  
│   │   ├── Actions/          # Geschäftslogik (z. B. Kategorie erstellen)  
│   │   ├── Repositories/     # Datenbank-Interaktionen  
│   │   ├── Providers/        # Dependency Injection  
│   │   └── Facades/          # Öffentliche Schnittstelle für andere Module  
│   └── Product/              # Analog für Produkte  
└── Parents/                  # Gemeinsame Basisklassen  

Beispiel: Kategorie- und Produktmodule

1. Repository (Datenzugriff)

// src/Domains/Category/Repositories/Eloquent/CategoryRepository.php  
namespace Domains\Category\Repositories\Eloquent;  

use Domains\Category\Models\Category;  

class CategoryRepository implements CategoryRepositoryInterface {  
    public function getBySlug(string $slug): Category {  
        return Category::where('slug', $slug)->firstOrFail();  
    }  
}  

2. Action (Business-Logik)

// src/Domains/Category/Actions/GetCategoryBySlug.php  
namespace Domains\Category\Actions;  

use Domains\Category\Repositories\CategoryRepositoryInterface;  

class GetCategoryBySlug {  
    public function __construct(  
        private CategoryRepositoryInterface $repository  
    ) {}  

    public function execute(string $slug): Category {  
        return $this->repository->getBySlug($slug);  
    }  
}  

3. Facade (Modul-Schnittstelle)

// src/Domains/Category/Facades/CategoryFacade.php  
namespace Domains\Category\Facades;  

use Domains\Category\Actions\GetCategoryBySlug;  

class CategoryFacade {  
    public function getBySlug(string $slug): Category {  
        return GetCategoryBySlug::execute($slug);  
    }  
}  

Modulübergreifende Kommunikation

Problem: Produkt-Modul benötigt Kategorie-Daten

Schlechte Lösung: Direkter Aufruf der Kategorie-Action → enge Kopplung.
Bessere Lösung:

1. Dependency Provider erstellen

// src/Domains/Product/Providers/ProductDependencyProvider.php  
namespace Domains\Product\Providers;  

use Domains\Category\Facades\CategoryFacade;  

class ProductDependencyProvider {  
    public function getCategoryFacade(): CategoryFacade {  
        return app(CategoryFacade::class);  
    }  
}  

2. Facade im Produkt-Modul nutzen

// src/Domains/Product/Actions/GetProductDetails.php  
namespace Domains\Product\Actions;  

use Domains\Product\Providers\ProductDependencyProvider;  

class GetProductDetails {  
    public function __construct(  
        private ProductDependencyProvider $dependencies  
    ) {}  

    public function execute(int $productId): array {  
        $category = $this->dependencies->getCategoryFacade()->getBySlug('electronics');  
        // Business-Logik mit Kategorie-Daten  
    }  
}  

Vorteile dieses Ansatzes

  • Geringe Kopplung: Module kennen sich nicht direkt.
  • Einfaches Mocking: Im Test ersetzen Sie die Facade durch ein Fake-Objekt.
  • Austauschbarkeit: Wechseln Sie die Kategorie-Implementierung, ohne Produkt-Code anzufassen.

Best Practices

  1. Interfaces definieren:
   // src/Domains/Category/Contracts/CategoryRepositoryInterface.php  
   interface CategoryRepositoryInterface {  
       public function getBySlug(string $slug): Category;  
   }  
  1. Service-Provider binden:
   // src/Domains/Category/Providers/CategoryServiceProvider.php  
   public function register(): void {  
       $this->app->bind(  
           CategoryRepositoryInterface::class,  
           CategoryRepository::class  
       );  
   }  
  1. Nur öffentliche Methoden exponieren: Facades sollten keine Geschäftslogik enthalten.

Wann lohnt sich dieser Aufwand?

SzenarioEmpfehlung
Kleine ProjekteÜbertrieben – einfache MVC-Struktur reicht.
Mittlere/große AppsUnverzichtbar – Wartbarkeit zahlt sich aus.
MicroservicesIdeal – klare Modulgrenzen.

👉 Beispielcode auf GitHub


Fazit

Indem Sie Domänen über Facades und Dependency Provider entkoppeln, machen Sie Ihr Laravel-Projekt:

  • Testbarer: Isolation von Modulen vereinfacht Unit-Tests.
  • Wartungsfreundlicher: Änderungen in einem Modul breiten sich nicht aus.
  • Erweiterbarer: Neue Features integrieren sich nahtlos.

Weiterführend:

Haben Sie Fragen? Stellen Sie sie in den Kommentaren! 🚀


„Software-Architektur ist wie Städteplanung – je klarer die Bezirke (Module) abgegrenzt sind, desto reibungsloser fließt der Verkehr (Daten).“