Status: Accepted
Date: 2026-04-18
Референс использовал RestException.badRequest(...) на всё подряд — один класс, message-строка, никакой типизации. Каррlser делает pattern matching по message-string (брёх), handler не может отличить "бизнес-правило" от "Feign 503". В новой версии нужна:
Sealed abstract class DomainException в core/domain/error/ со вложенной (nested) иерархией:
NotFound, ValidationError, DocumentGenerationError (final).Integration): Unreachable, Timeout, ServerError (transient) + Rejected, Unauthorized, ContractMismatch (permanent).ExternalSystem enum для type-safe источника (AGENDA, COMMON_EVENTS, ECM, ...).isTransient() — абстрактный метод на Integration, единая точка для retry-политик.Всё в одном файле (DomainException.java) как nested classes: подтипы тонкие (data + one abstract method), идиоматика Java для ADT (аналог Kotlin/Scala sealed). 12 файлов для 12 тонких классов — ceremony без пользы.
Positive:
GlobalExceptionHandler — exhaustive switch. Добавление нового подтипа → compile error → разработчик обязан добавить case.ContractMismatch отдельно от Rejected: баг интеграции (алерт для dev-команды) ≠ отказ внешнего сервиса (info-уровень).retryOnException(e -> e instanceof Integration i && i.isTransient()) — одна строка, работает для всех 3 transient-подтипов.Negative:
Throwable.getMessage() + if/else: nothing to say. Это референс.