Od architektury warstwowej do Clean Architecture
Proszę zrozumieć, dlaczego zależności powinny być skierowane do wnętrza.
Od architektury warstwowej do Clean Architecture to bezpłatna lekcja PHP Academy na CoddyKit. To lekcja 1 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej PHP Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs PHP Academy zawiera 4 lekcji w sumie.
Dlaczego Clean Architecture
Znają już Państwo klasyczny, trójwarstwowy stos PHP: Controller → Service → Repository → Database. Działa on, ale logika biznesowa staje się sprzężona z Eloquent, Doctrine, żądaniem HTTP i cyklem życia frameworka. Clean Architecture odwraca kierunek zależności, dzięki czemu Państwa domena nie wie nic o infrastrukturze. Korzyści to testowalne przypadki użycia, wymienne adaptery i baza kodu, która przetrwa aktualizacje frameworka.
Reguła zależności
Jedyna reguła Clean Architecture brzmi: zależności w kodzie źródłowym wskazują wyłącznie do wewnątrz. Wewnętrzne kręgi (encje, przypadki użycia) nigdy nie mogą odwoływać się do zewnętrznych kręgów (kontrolerów, ORM-ów, frameworków). W czasie działania przepływ sterowania może przebiegać na zewnątrz za pośrednictwem interfejsów, ale w czasie kompilacji/importu nic z warstwy wewnętrznej nie importuje niczego z warstwy zewnętrznej.
- Encje: reguły biznesowe organizacji
- Przypadki użycia: reguły aplikacji
- Adaptery: kontrolery, prezenterzy, bramki
- Frameworki i sterowniki: DB, HTTP, sieć
Przykład sprzężonej architektury warstwowej
Oto typ serwisu dostarczanego przez większość aplikacji PHP. Zwróćcie Państwo uwagę, że logika domenowa jest splątana z Eloquent i odpowiedzią HTTP. Nie można przetestować jednostkowo reguły rabatu bez bazy danych i frameworka.
<?php
class OrderService
{
public function place(Request $request)
{
$user = User::find($request->user_id); // Eloquent
$total = 0;
foreach ($request->items as $i) {
$total += Product::find($i['id'])->price * $i['qty'];
}
if ($user->is_vip) {
$total *= 0.9; // business rule trapped in infra code
}
Order::create(['user_id' => $user->id, 'total' => $total]);
return response()->json(['total' => $total]);
}
}Encje: domena niezależna od frameworka
Encja koduje reguły obowiązujące w całym przedsiębiorstwie i nie zależy od niczego. Czysty PHP, bez adnotacji i bez klasy bazowej z ORM-u. Można ją w pełni konstruować w teście.
<?php
final class Money
{
public function __construct(public readonly int $cents) {
if ($cents < 0) throw new InvalidArgumentException('negative money');
}
public function multiply(float $factor): self {
return new self((int) round($this->cents * $factor));
}
}
final class Order
{
/** @param array<int,int> $lineCents */
public function __construct(private array $lineCents, private bool $vip) {}
public function total(): Money {
$sum = array_sum($this->lineCents);
$money = new Money($sum);
return $this->vip ? $money->multiply(0.9) : $money;
}
}
echo (new Order([1000, 2000], true))->total()->cents, PHP_EOL; // 2700Przypadki użycia zarządzają przebiegiem
Przypadek użycia (interaktor) orkiestruje encje i komunikuje się ze światem zewnętrznym wyłącznie za pośrednictwem interfejsów (portów). Otrzymuje DTO żądania i zwraca DTO odpowiedzi — nigdy obiekt HTTP.
<?php
interface OrderRepository {
public function save(Order $order): void;
}
final class PlaceOrder
{
public function __construct(private OrderRepository $orders) {}
public function execute(array $lineCents, bool $vip): int {
$order = new Order($lineCents, $vip);
$this->orders->save($order);
return $order->total()->cents;
}
}Granica jest interfejsem
Przypadek użycia deklaruje potrzebny mu interfejs OrderRepository. Interfejs znajduje się w kręgu wewnętrznym, a konkretna implementacja Eloquent/Doctrine znajduje się na zewnątrz i zależy od środka. Jest to zasada odwrócenia zależności zastosowana na granicy architektonicznej.
Kierunek zależności w kodzie źródłowym: EloquentOrderRepository → OrderRepository (interfejs), nigdy odwrotnie.
<?php
// Lives in infrastructure layer, points INWARD to the domain interface
final class EloquentOrderRepository implements OrderRepository
{
public function save(Order $order): void {
OrderModel::create(['total' => $order->total()->cents]);
}
}Testowanie bez infrastruktury
Ponieważ przypadek użycia zależy od interfejsu, testy wstrzykują fake. Bez bazy danych i uruchamiania frameworka — błyskawiczne testy jednostkowe, które sprawdzają czyste zachowanie biznesowe.
<?php
final class InMemoryOrders implements OrderRepository {
public array $saved = [];
public function save(Order $o): void { $this->saved[] = $o; }
}
$repo = new InMemoryOrders();
$useCase = new PlaceOrder($repo);
$total = $useCase->execute([1000, 2000], true);
assert($total === 2700);
assert(count($repo->saved) === 1);
echo "PASS total=$total saved=" . count($repo->saved) . PHP_EOL;Kontrolery stają się cienkimi adapterami
Kontroler jest teraz adapterem: tłumaczy HTTP na wywołanie przypadku użycia, a wynik na HTTP. Nie zawiera żadnych reguł biznesowych. Można zastąpić REST interfejsem CLI albo procesem obsługującym kolejkę, a przypadek użycia pozostanie nietknięty.
<?php
final class OrderController
{
public function __construct(private PlaceOrder $placeOrder) {}
public function store(Request $request): JsonResponse {
$total = $this->placeOrder->execute(
lineCents: $request->input('lineCents'),
vip: (bool) $request->input('vip'),
);
return new JsonResponse(['total' => $total], 201);
}
}Architektura wyrażająca domenę
Struktura katalogów powinna wyrażać domenę, a nie framework. Należy unikać katalogów Controllers/ i Models/ na najwyższym poziomie. Organizacja według zdolności pozwala nowej osobie szybko zrozumieć, czym zajmuje się aplikacja.
src/Ordering/Domain/— encje, obiekty wartościsrc/Ordering/Application/— przypadki użycia, interfejsy portówsrc/Ordering/Infrastructure/— repozytoria Eloquent, kontrolery HTTP
Każdy bounded context jest katalogiem najwyższego poziomu, a framework pozostaje na obrzeżach.
Wymuszanie reguły zależności
Dyscyplina bez narzędzi słabnie. Należy używać deptrac lub phparkitect w CI, aby kompilacja kończyła się błędem, gdy Domain importuje Infrastructure. Reguła staje się gwarancją na etapie kompilacji, a nie nadzieją pokładaną w przeglądzie kodu.
# deptrac.yaml
deptrac:
layers:
- name: Domain
collectors: [{ type: directory, value: src/.*/Domain/.* }]
- name: Application
collectors: [{ type: directory, value: src/.*/Application/.* }]
- name: Infrastructure
collectors: [{ type: directory, value: src/.*/Infrastructure/.* }]
ruleset:
Domain: [] # Domain may depend on nothing
Application: [Domain]
Infrastructure: [Application, Domain]Przekraczanie granic za pomocą DTO
Aby encje nie wyciekały na zewnątrz, dane przekraczające granicę są przesyłane jako prosty DTO, a nie jako encja lub model ORM. Przypadek użycia zwraca płaską strukturę, którą adapter może zserializować, dzięki czemu obiekt domenowy nigdy nie opuszcza rdzenia, a warstwa zewnętrzna nie uzyskuje dostępu do wewnętrznego stanu.
<?php
final class OrderSummary // boundary DTO, no behavior, no domain types
{
public function __construct(
public readonly string $orderId,
public readonly int $totalCents,
) {}
}
final class PlaceOrderV2 {
public function __construct(private OrderRepository $orders) {}
public function execute(array $lineCents, bool $vip): OrderSummary {
$order = new Order($lineCents, $vip);
$this->orders->save($order);
return new OrderSummary('ord_1', $order->total()->cents);
}
}Szybki test
Jaki kierunek zależności jest dozwolony zgodnie z Regułą zależności?
Podsumowanie
Przeszli Państwo od sprzężonego stosu warstwowego do Clean Architecture:
- Reguła zależności: zależności w kodzie źródłowym wskazują wyłącznie do wewnątrz.
- Encje zawierają reguły biznesowe organizacji w PHP niezależnym od frameworka.
- Przypadki użycia orkiestrują działanie za pośrednictwem interfejsów portów, zwracając DTO, a nie HTTP.
- Kontrolery i repozytoria ORM to zewnętrzne adaptery, które zależą od środka (DIP).
- Struktura powinna wyrażać domenę, a narzędzia takie jak deptrac wymuszają tę regułę w CI.
Często zadawane pytania
Czy lekcja „Od architektury warstwowej do Clean Architecture” jest bezpłatna?
Tak — pełny tekst „Od architektury warstwowej do Clean Architecture” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu PHP Academy, przejdź na CoddyKit PRO. Kurs PHP Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Od architektury warstwowej do Clean Architecture”?
Proszę zrozumieć, dlaczego zależności powinny być skierowane do wnętrza. Ćwiczysz PHP Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć PHP Academy?
Nie wymagamy żadnego doświadczenia. PHP Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 1 z 4.
Ile czasu zajmuje lekcja „Od architektury warstwowej do Clean Architecture”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji PHP Academy?
Tak. Każda lekcja PHP Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Od architektury warstwowej do Clean Architecture
- Wyjaśnienie portów i adapterów
- Przypadki użycia i usługi aplikacyjne
- Odwrócenie zależności w praktyce