今日已更新 412 条资讯 | 累计 19972 条内容
关于我们

标签:#hexagonal

找到 2 篇相关文章

AI 资讯

A Domain Logger Port: Decoupling From PSR-3 Without Losing Context

Book: Decoupled PHP — Clean and Hexagonal Architecture for Applications That Outlive the Framework Also by me: Thinking in Go (2-book series) — Complete Guide to Go Programming + Hexagonal Architecture in Go My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools Me: xgabriel.com | GitHub You open a use case that places an order. Near the top of the constructor, alongside the repositories and the payment gateway, sits a Psr\Log\LoggerInterface . The method body calls $this->logger->info(...) three times. It looks harmless. It is the most common way framework concerns leak back into a domain you spent weeks keeping clean. PSR-3 is a fine standard. Monolog is the default implementation in most PHP projects, and it earns that spot. The problem is not the library. The problem is where you point it. When LoggerInterface is a constructor argument in your application layer, your use case now depends on a package whose surface area you do not control, whose log levels you may not want, and whose context conventions are someone else's. The dependency arrow points the wrong way. What PSR-3 drags in Psr\Log\LoggerInterface is eight level methods plus a generic log() . The level taxonomy comes from RFC 5424 syslog: emergency , alert , critical , error , warning , notice , info , debug . That is a system-administration vocabulary. Your domain does not speak it. When a use case calls $this->logger->warning('payment retry') , you have to ask: is a retry a warning or a notice ? The answer is an infrastructure judgment call wearing a domain costume. The method signature also accepts an arbitrary array $context and a string|Stringable $message with {placeholder} interpolation. None of that is something your application code should be deciding. <?php declare ( strict_types = 1 ); namespace App\Application\Order ; use Psr\Log\LoggerInterface ; final readonly class PlaceOrder { public function __construct ( private OrderRepository $ord

2026-06-14 原文 →
AI 资讯

Retries and Circuit Breakers Belong in the Adapter, Not Your Use Case

Book: Decoupled PHP — Clean and Hexagonal Architecture for Applications That Outlive the Framework Also by me: Thinking in Go (2-book series) — Complete Guide to Go Programming + Hexagonal Architecture in Go My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools Me: xgabriel.com | GitHub You open a use case that places an order. It loads a customer, builds the order, charges a payment gateway, saves, publishes an event. Clean. Then a sprint ago someone added a retry loop around the charge call, because the gateway flaps under load. Now the use case has a for ($i = 0; $i < 3; $i++) , a usleep() , and a comment that says // gateway is flaky on Mondays . The business rule is buried under retry plumbing. A reader who wants to know what placing an order means has to skip past sleep timers and exception counters to find it. Worse, the next person who adds a second outbound call copies the loop. Soon every call to the network has its own hand-rolled retry, each with a slightly different backoff, none of them tested. The use case learned about transient failure. It should never have. Transient failure is not a business rule A use case answers one question: what does the application do when this thing happens? Place an order. Cancel a subscription. Issue a refund. Those are decisions the business cares about. "The payment gateway returned a 503 and we should try again in 200ms" is not a business decision. It is a property of the network between your process and theirs. The domain does not know the gateway is HTTP. It does not know there is a network at all. It asked a port to charge a customer, and the port either succeeds or raises a domain exception. Here is the port, stated in domain language: <?php declare ( strict_types = 1 ); namespace App\Application\Port ; use App\Domain\Customer\CustomerId ; use App\Domain\Shared\Money ; interface PaymentGateway { public function charge ( CustomerId $customerId , Money $amount , s

2026-06-14 原文 →