SOLID v kódu

Pět principů objektového návrhu a konkrétní místa v MiniShopu, kde je uvidíte.

Na této stránce

V jedné větě: SOLID je pět pravidel, díky kterým jde kód měnit bez strachu – nová funkce znamená spíš přidat třídu než přepisovat existující.

Písmeno Princip Jednoduše
S Single Responsibility Třída má jeden důvod ke změně.
O Open/Closed Otevřené pro rozšíření, uzavřené pro úpravy.
L Liskov Substitution Implementaci rozhraní jde zaměnit za jinou, aniž by se program rozbil.
I Interface Segregation Malá rozhraní na míru, ne jedno obří.
D Dependency Inversion Business logika závisí na abstrakcích, ne na databázi a síti.

S – Single Responsibility

Kde: OrderService a handlery v EventHandlers/.

Původně (krok 1) OrderService při zrušení objednávky sám vracel zboží na sklad a posílal oznámení. Měl tak tři důvody ke změně. Od kroku 3 jen změní objednávku a uloží ji; vrácení zboží řeší sága a oznámení samostatné handlery (SendCancellationNoticeHandler…). Každý dělá jednu věc.

Přirovnání: Číšník objednávku přijme a předá do kuchyně. Nevaří, neumývá nádobí a neúčtuje zásoby.

O – Open/Closed

Kde: IDiscountPolicy a doménové události.

  • Nová sleva = nová třída implementující IDiscountPolicy a jeden řádek registrace. OrderService si vezme všechna pravidla a použije nejvýhodnější – sám se nemění.
  • Nová reakce na objednávku (např. věrnostní body) = nový handler IDomainEventHandler<OrderConfirmed>. Order ani OrderService se o něm nedozví.
  • IdempotentDomainEventHandler přidává ochranu proti duplicitám jako dekorátor – obalí handler, aniž by se handler měnil.

L – Liskov Substitution

Kde: IIntegrationBus a IOrderNotifier.

  • RabbitMqIntegrationBus a InProcessIntegrationBus jsou zaměnitelné. Testy běží celou ságu v procesu, produkce přes RabbitMQ – handlery nepoznají rozdíl.
  • Oznámení zákazníkovi: původní LoggingOrderNotifier (jen log) nahradil v účtech kroku 5 EmailOrderNotifier – handlery ani OrderService se nezměnily ani o řádek. Stejně tak IEmailSender: SMTP, nebo jen log, když server není nastavený.

I – Interface Segregation

Kde: rozhraní modulů v Application/Ordering a Application/Inventory.

Místo jednoho obecného „repozitáře všeho“ má každý modul úzká rozhraní: IOrderRepository, ICatalogProductRepository, IOrderingUnitOfWork, IOrderingMessagePublisher… Handler skladu dostane IInventoryUnitOfWork – nemůže omylem uložit do databáze objednávek, protože k ní nemá rozhraní.

D – Dependency Inversion

Kde: konstruktory služeb a vrstvy projektu.

public sealed class OrderService(
    IOrderRepository orders,               // ne OrderRepository / EF Core
    ICatalogProductRepository catalog,
    IOrderingUnitOfWork unitOfWork,        // ne OrderingDbContext
    IEnumerable<IDiscountPolicy> discountPolicies,
    TimeProvider timeProvider)             // ne DateTime.UtcNow

Application definuje, co potřebuje; Infrastructure to dodá. I čas je abstrakce (TimeProvider) – testy ho umí posunout (FixedTimeProvider.Advance) a vyzkoušet třeba timeout ságy bez čekání.

Kdy SOLID nepřehánět

Každé rozhraní je další soubor a další skok při čtení kódu. MiniShop má rozhraní tam, kde se implementace skutečně mění (testy vs. produkce, RabbitMQ vs. paměť) nebo kde chrání hranici modulu.

Na internetu