Przejdź do treści
Biblioteka skilli
Programowanie · Planowanie

hexagonal-architecture

/hexagonal-architecture

Układa aplikację Symfony w architekturze heksagonalnej (porty i adaptery): domena wolna od frameworka, interfejsy portów w domenie i implementacje adapterów w infrastrukturze.

Repozytorium
symfony-skills ↗
Licencja
MIT
Ostatnia zmiana repozytorium
25.07.2026

Ocena i bezpieczeństwo

100/ 100Klasa: dobry
Format25 / 25
Frontmatter z nazwą i opisem, poprawna nazwa, opis do 1024 znaków, nazwa zgodna z folderem.
Odkrywalność25 / 25
Opis mówi, kiedy użyć skilla, ma sensowną długość i jest w 3. osobie.
Treść35 / 35
Co najmniej 150 słów i do 500 linii, nagłówki, listy kroków, przykłady albo kod.
Higiena15 / 15
Bez zaślepek typu TODO, bez ścieżek Windows, z licencją.
Ryzykobrak sygnałów w skanie
Pliki w paczce
1
Skrypty
0
Komentarze HTML
0
Adresy URL w SKILL.md
0
Przestarzałość

Brak sygnałów. Repozytorium zmienione 25.07.2026, bez odwołań do wycofanych modeli i API.

Licencja

MIT, otwarta: można używać i zmieniać, z zachowaniem informacji o autorze.

Co robi i kiedy

Układa aplikację Symfony w architekturze heksagonalnej (porty i adaptery): domena wolna od frameworka, interfejsy portów w domenie i implementacje adapterów w infrastrukturze.

Wyzwalacze z opisu

keep the domain pure
Oryginalny opis (EN)

Use when structuring a Symfony app with ports & adapters / clean architecture — keeping the domain framework-free, defining interfaces (ports) in the domain and implementations (adapters) in infrastructure. Use when the task mentions hexagonal, ports/adapters, or "keep the domain pure".

Jak działa

  1. The dependency rule
  2. Suggested package layout
  3. Ports live in the domain
  4. Keeping the domain framework-free
  5. Application layer = use cases
  6. Driving adapters
  7. Gotchas
Sekcje z nagłówków SKILL.md, w oryginale · 163 linii, 727 słów
Podgląd SKILL.md
---
name: hexagonal-architecture
description: >
  Use when structuring a Symfony app with ports & adapters / clean architecture —
  keeping the domain framework-free, defining interfaces (ports) in the domain and
  implementations (adapters) in infrastructure. Use when the task mentions hexagonal,
  ports/adapters, or "keep the domain pure".
---

# Hexagonal Architecture (Ports & Adapters)

> **When to reach for this.** Hexagonal is for complex, long-lived domains where isolating business logic from the framework pays off — rich invariants, multiple delivery mechanisms, logic you expect to outlive the current stack. For typical CRUD and feature work (the ~80% of most apps), use [`layered-architecture`](../layered-architecture) instead; strict ports/adapters there is just overhead. Apply this to the gnarly 20% that earns it, not as a blanket rule.

## The dependency rule

```
        ┌─────────────────────────────────────────┐
        │            Infrastructure                 │  ← Symfony, Doctrine, HTTP, Messenger
        │   (driving + driven adapters)             │
        │   ┌───────────────────────────────────┐   │
        │   │         Application               │   │  ← use cases / handlers
        │   │   ┌───────────────────────────┐   │   │
        │   │   │         Domain            │   │   │  ← entities, value objects, ports
        │   │   │   (no framework imports)  │   │   │
        │   │   └───────────────────────────┘   │   │
        │   └───────────────────────────────────┘   │
        └─────────────────────────────────────────┘
```

**Source code dependencies point inward.** The domain knows nothing about Symfony, Doctrine, or HTTP. Outer layers depend on inner-layer **interfaces**, never the reverse.

## Suggested package layout

```
src/
├── Domain/                     # pure PHP — no Symfony/Doctrine "use" statements
│   ├── Order/
│   │   ├── Order.php           # aggregate root (plain object, no #[ORM\...])
│   │   ├── OrderId.php         # value object
│   │   ├── OrderStatus.php     # enum
│   │   └── OrderRepository.php  # PORT (interface)
├── Application/                # use cases — orchestrates domain via ports
│   └── Order/
│       ├── CreateOrderHandler.php
│       └── CreateOrderCommand.php
└── Infrastructure/             # adapters — implement ports using Symfony/Doctrine
    ├── Persistence/Doctrine/
    │   ├── DoctrineOrderRepository.php   # ADAPTER implements Domain\Order\OrderRepository
    │   └── Mapping/Order.orm.xml         # ORM mapping kept OUT of the domain class
    └── Http/
        └── CreateOrderController.php     # driving adapter
```

## Ports live in the domain

A **port** is an interface the domain owns and the infrastructure implements.

```php
// ✅ src/Domain/Order/OrderRepository.php — no framework imports
namespace App\Domain\Order;

interface OrderRepository
{
    public function save(Order $order): void;

    public function ofId(OrderId $id): ?Order;
}
```

```php
// ✅ src/Infrastructure/Persistence/Doctrine/DoctrineOrderRepository.php
namespace App\Infrastructure\Persistence\Doctrine;

use App\Domain\Order\Order;
use App\Domain\Order\OrderId;
use App\Domain\Order\OrderRepository;
use Doctrine\ORM\EntityManagerInterface;

final class DoctrineOrderRepository implements OrderRepository
{
    public function __construct(private readonly EntityManagerInterface $em) {}

    public function save(Order $order): void
    {
        $this->em->persist($order);
        $this->em->flush();
    }

    public function ofId(OrderId $id): ?Order
    {
        return $this->em->find(Order::class, $id->toString());
    }
}
```

Wire the interface to the implementation in `config/services.yaml`:

```yaml
services:
    App\Domain\Order\OrderRepository: '@App\Infrastructure\Persistence\Doctrine\DoctrineOrderRepository'
```

## Keeping the domain framework-free

The hard part in Symfony is that Doctrine attribute mapping (`#[ORM\Entity]`) couples the entity to the ORM. Two acceptable strategies:

1. **Pragmatic (default):** allow Doctrine attributes on the aggregate. You lose "zero framework imports" but keep one class. Fine for most teams.
2. **Strict:** keep the domain class pure and map it with **XML mapping** in `Infrastructure/Persistence/Doctrine/Mapping/`. The domain never imports Doctrine.

```php
// ✅ STRICT — pure aggregate, mapped externally via XML
namespace App\Domain\Order;

final class Order
{
    /** @var Collection<int, OrderLine>|OrderLine[] */
    private array $lines = [];

    private function __construct(
        private readonly OrderId $id,
        private OrderStatus $status,
    ) {}

    public static function place(OrderId $id): self
    {
        return new self($id, OrderStatus::Pending);   // invariant: new orders start Pending
    }
}
```

State the chosen strategy in the project README so the agent stays consistent.

## Application layer = use cases

One handler per use case. It depends only on **ports**, never on adapters.

```php
// ✅ src/Application/Order/CreateOrderHandler.php
namespace App\Application\Order;

use App\Domain\Order\Order;
use App\Domain\Order\OrderId;
use App\Domain\Order\OrderRepository;

final readonly class CreateOrderHandler
{
    public function __construct(private OrderRepository $orders) {}

    public function __invoke(CreateOrderCommand $command): OrderId
    {
        $order = Order::place(OrderId::generate());
        $this->orders->save($order);

        return $order->id();
    }
}
```

## Driving adapters

Controllers, console commands, and Messenger handlers are **driving adapters**. They translate a delivery mechanism into an application call and translate the result back out. They contain no business logic.

## Gotchas

- Agent puts `EntityManagerInterface` into the application/domain layer — depend on a repository **port** instead.
- Agent imports `Symfony\…` or `Doctrine\…` in `src/Domain/` — the domain must stay framework-free (or use the agreed pragmatic exception for ORM attributes only).
- Agent makes the application layer depend on the concrete `DoctrineOrderRepository` — depend on the interface; wire it in `services.yaml`.
- Agent returns a Symfony `Response` from a use-case handler — return a domain value (ID, DTO); the controller builds the HTTP response.
- Agent puts validation/HTTP concerns in the domain — those belong in adapters.
- Agent generates `#[ORM\Entity]` on a "pure" domain class when the project chose the strict XML-mapping strategy — check the README's stated strategy first.

Wersje

RepozytoriumWynikLicencja
Ta strona · fatonh/symfony-skills100MIT
affaan-m/ECC100MIT
rrezartprebreza/spring-boot-skills100MIT
rrezartprebreza/spring-boot-skills100MIT
zernie/vigiles100MIT
bdfinst/agentic-dev-team100MIT
FlameMida/spec-dev95MIT
packmindhub/packmind95Apache-2.0
citypaul/.dotfiles95MIT

Migawka bazy z 28.10.2026 · wersja paczki c51a9169fe9a (skrót SHA-256 całej paczki)

Zgłoś problem z tym skillem: szkodliwa treść, błędny opis albo prośba autora o usunięcie.

Podobne skille

  • 100707-technologies-hexagonal-architecture
    Doradza w architekturze heksagonalnej dla projektów Java: granice portów i adapterów, niezależność jądra aplikacji i kierunek zależności.
  • 100hexagonal-pattern-guide
    Prowadzi przez izolowanie rdzenia aplikacji od wejścia i wyjścia za interfejsami w architekturze heksagonalnej, czystej i cebulowej: adaptery pierwotne i wtórne, odwrócenie zależności, composition root i podmiana adaptera.
  • 100clean-architecture
    Dostarcza wzorce Clean Architecture, DDD i architektury heksagonalnej dla aplikacji NestJS i TypeScript, od warstwy domeny po porty i adaptery.
  • 100ddd-architecture-hexagonal
    Prowadzi przez architekturę heksagonalną (porty i adaptery) Alistaira Cockburna: rdzeń domeny, porty, adaptery pierwotne i wtórne oraz wstrzykiwanie zależności w Java i Spring Boot.
  • 100hexagonal
    Opisuje architekturę heksagonalną (porty i adaptery), Onion Architecture i ich związek z Clean Architecture, dla logiki domenowej niezależnej od technologii i dobrze testowalnej.
  • 100clean-architecture
    Dostarcza wzorce Clean Architecture, architektury heksagonalnej i DDD dla aplikacji w Javie 21+ ze Spring Boot 3.5+.

Uwagi i zgłoszenia

Wiadomość trafi do Bartosza Sokolińskiego. Adres e-mail jest potrzebny tylko do odpowiedzi. Szczegóły w polityce prywatności.