[{"data":1,"prerenderedAt":1087},["ShallowReactive",2],{"post-\u002Fblog\u002Fclean-architecture":3,"surround-\u002Fblog\u002Fclean-architecture":1081},{"id":4,"title":5,"body":6,"category":1064,"cover":1065,"date":1066,"description":1067,"draft":1068,"extension":1069,"meta":1070,"navigation":1071,"path":1072,"seo":1073,"stem":1074,"tags":1075,"updated":1079,"__hash__":1080},"blog\u002Fblog\u002Fclean-architecture.md","Универсальное руководство по Clean Architecture",{"type":7,"value":8,"toc":1037},"minimark",[9,14,22,28,31,35,63,65,69,72,83,85,90,97,102,145,150,215,217,221,228,232,303,307,359,361,365,372,376,443,447,490,492,496,499,503,541,545,581,583,587,592,598,602,650,652,656,660,663,690,694,697,700,725,730,853,858,906,936,940,943,945,949,953,978,982,1002,1004,1008,1011],[10,11,13],"h2",{"id":12},"введение","Введение",[15,16,17,21],"p",{},[18,19,20],"strong",{},"Clean Architecture"," (Чистая Архитектура) — это архитектурный подход, предложенный Робертом Мартином (Uncle Bob), который объединяет идеи Hexagonal Architecture (Порты и Адаптеры), Onion Architecture и Screaming Architecture.",[15,23,24,27],{},[18,25,26],{},"Главная цель:"," создание системы, которая независима от фреймворков, баз данных, UI и внешних сервисов. Бизнес-логика изолирована и является ядром системы.",[29,30],"hr",{},[10,32,34],{"id":33},"фундаментальные-принципы","📐 Фундаментальные принципы",[36,37,38,45,51,57],"ol",{},[39,40,41,44],"li",{},[18,42,43],{},"Правило зависимостей (The Dependency Rule):"," Исходный код зависимостей может указывать только внутрь (к центру). Внутренние слои ничего не знают о внешних.",[39,46,47,50],{},[18,48,49],{},"Независимость от фреймворков:"," Фреймворки — это просто инструменты. Они не должны диктовать архитектуру.",[39,52,53,56],{},[18,54,55],{},"Тестируемость:"," Бизнес-логика может быть протестирована в изоляции, без базы данных, UI или внешних API.",[39,58,59,62],{},[18,60,61],{},"Независимость от UI и БД:"," Интерфейс пользователя и способ хранения данных — это внешние детали, которые можно заменить без изменения ядра.",[29,64],{},[10,66,68],{"id":67},"слои-архитектуры-и-структура-директорий","🧅 Слои Архитектуры и Структура Директорий",[15,70,71],{},"Архитектура состоит из четырех основных слоев. Рассмотрим их от центра к периферии.",[73,74,80],"pre",{"className":75,"code":77,"language":78,"meta":79},[76],"language-text","src\u002F\n │\n ├── Domain\u002F                              # 🧠 ЯДРО (Чистый PHP, ЗАПРЕЩЕНЫ зависимости от фреймворков)\n │   ├── DTO\u002F                             # Доменные DTO (передача данных между слоями)\n │   │   └── UserDTO.php                  # Пример: UserDTO (readonly)\n │   │\n │   ├── Entity\u002F                          # Бизнес-сущности с поведением (Rich Domain Model)\n │   │   ├── User.php                     # Пример: User с методами changeStatus(), block()\n │   │   └── Order.php                    # Не содержат аннотаций ORM!\n │   │\n │   ├── ValueObject\u002F                     # Неизменяемые объекты-значения (Readonly-классы)\n │   │   ├── Email.php                    # Пример: Email, Money, Address\n │   │   └── OrderStatus.php              # Описывают данные, сравниваются по значению\n │   │\n │   ├── Repository\u002F                      # ТОЛЬКО интерфейсы (Contracts) для доступа к данным\n │   │   ├── UserRepositoryInterface.php  # Пример: UserRepositoryInterface\n │   │   └── OrderRepositoryInterface.php # Напр.: save(), findById(), findAll()\n │   │\n │   ├── Service\u002F                         # Интерфейсы для внешних сервисов (если нужно в Domain)\n │   │   └── EmailSenderInterface.php     # Пример: EmailSenderInterface, PaymentGatewayInterface\n │   │\n │   ├── Event\u002F                           # Доменные события (Domain Events)\n │   │   └── UserCreatedEvent.php         # Пример: UserCreatedEvent, OrderPlacedEvent\n │   │\n │   └── Exception\u002F                       # Исключения бизнес-правил\n │       └── UserAlreadyExistsException.php # Пример: OutdatedDataException, InvalidOrderException\n │\n ├── Application\u002F                         # ⚙️ СЦЕНАРИИ ИСПОЛЬЗОВАНИЯ (Оркестрация, зависит от Domain)\n │   ├── Port\u002F                            # Общие интерфейсы Application-слоя (DeeplinkGeneratorInterface)\n │   ├── Common\u002F                          # Общие утилиты (Pagination, DateTime)\n │   │\n │   ├── Voting\u002F                          # Модуль по доменной области (bounded context)\n │   │   ├── Command\u002F                     # DTO команд (readonly)\n │   │   ├── CommandHandler\u002F              # Синхронные обработчики команд (__invoke)\n │   │   ├── Message\u002F                     # Асинхронные сообщения для Messenger\n │   │   ├── MessageHandler\u002F              # Consumer'ы очередей (#[AsMessageHandler])\n │   │   └── DTO\u002F                         # Выходные DTO модуля\n │   │\n │   ├── Season\u002F                          # Аналогичная структура для других модулей\n │   │   ├── Command\u002F\n │   │   ├── CommandHandler\u002F\n │   │   ├── Query\u002F\n │   │   ├── QueryHandler\u002F\n │   │   └── DTO\u002F\n │   │\n │   └── Bot\u002F\n │       ├── Command\u002F                     # (если есть CQRS-команды)\n │       ├── CommandHandler\u002F\n │       ├── Message\u002F\n │       ├── MessageHandler\u002F\n │       └── DTO\u002F\n │\n │   # ⚠️ Не используйте папки UseCase\u002F, Service\u002F для CQRS-обработчиков.\n │   # CommandHandler\u002F + QueryHandler\u002F — стандарт проекта.\n │\n ├── Infrastructure\u002F                      # 🔌 ДЕТАЛИ РЕАЛИЗАЦИИ (Здесь живет фреймворк, БД, API)\n │   ├── Persistence\u002F                     # Работа с базой данных\n │   │   ├── Model\u002F                       # ORM-сущности (Doctrine Models, Eloquent Models)\n │   │   │   ├── UserModel.php            # Пример: UserModel (содержит #[ORM\\Entity], #[ORM\\Column])\n │   │   │   └── OrderModel.php           # Не содержат бизнес-логики!\n │   │   │\n │   │   ├── Mapper\u002F                      # Мапперы между Domain Entity и Infrastructure Model\n │   │   │   ├── UserMapper.php           # Пример: UserMapper (toDomain(), toModel())\n │   │   │   └── OrderMapper.php          # Конвертирует Domain Entity ↔ ORM Model\n │   │   │\n │   │   └── Repository\u002F                  # Реализации интерфейсов из Domain\\Repository\n │   │       ├── DoctrineUserRepository.php  # Пример: DoctrineUserRepository (реализует UserRepositoryInterface)\n │   │       └── EloquentOrderRepository.php # Использует EntityManager, QueryBuilder\n │   │\n │   ├── Integration\u002F                     # Интеграция с внешними API и сервисами\n │   │   ├── Api\u002F                         # HTTP-клиенты для внешних API\n │   │   │   ├── SapClient.php            # Пример: SapClient, StripeClient, SlackClient\n │   │   │   └── StripeClient.php         # Использует Guzzle, Symfony HttpClient\n │   │   │\n │   │   └── Gateway\u002F                     # Реализации интерфейсов из Domain\\Service\n │   │       ├── SmtpEmailSender.php      # Пример: SmtpEmailSender (реализует EmailSenderInterface)\n │   │       └── StripePaymentGateway.php # Использует внешние SDK\n │   │\n │   ├── Messaging\u002F                       # Интеграция с очередями сообщений\n │   │   ├── Kafka\u002F                       # Apache Kafka \u002F Redpanda\n │   │   │   ├── Dto\u002F                     # DTO для парсинга JSON из Kafka\n │   │   │   │   └── EmployeeBatchDto.php # Пример: EmployeeBatchDto, OrderEventDto\n │   │   │   ├── Message\u002F                 # Обёртки для сырых сообщений\n │   │   │   │   └── KafkaRawMessage.php  # Пример: KafkaRawMessage\n │   │   │   ├── Serializer\u002F              # Кастомные сериализаторы\n │   │   │   │   └── KafkaRawMessageSerializer.php # Для Symfony Messenger\n │   │   │   └── Handler\u002F                 # Обработчики сообщений из Kafka\n │   │   │       └── EmployeeMessageHandler.php  # Парсят JSON, создают команды, диспатчат в MessageBus\n │   │   │\n │   │   └── RabbitMQ\u002F                    # RabbitMQ (если используется)\n │   │       └── Handler\u002F\n │   │           └── OrderEventHandler.php\n │   │\n │   ├── AutoMapper\u002F                      # Конфигурация автоматического маппинга объектов\n │   │   └── AutoMapperConfig.php         # Пример: AutoMapperConfig (для AutoMapperBundle)\n │   │\n │   ├── Serializer\u002F                      # Настройки сериализации\u002Fдесериализации\n │   │   └── SerializerConfig.php         # Пример: SerializerConfig (для Symfony Serializer)\n │   │\n │   ├── Validation\u002F                      # Валидаторы и правила валидации\n │   │   ├── Validator.php                # Пример: Validator (обёртка над Symfony Validator)\n │   │   └── Rules\u002F                       # Кастомные правила валидации\n │   │       └── UniqueEmailRule.php\n │   │\n │   ├── DI\u002F                              # Конфигурация контейнера внедрения зависимостей\n │   │   └── services.php                 # Пример: services.php (для Symfony DI, Slim)\n │   │\n │   ├── Console\u002F                         # Системные консольные команды (зависят от фреймворка)\n │   │   └── Command\u002F                     # Кастомные команды (если нужны)\n │   │       └── SyncDataCommand.php      # Пример: SyncDataCommand, ClearCacheCommand\n │   │\n │   └── EventListener\u002F                   # Обработчики событий фреймворка\n │       └── KernelRequestListener.php    # Пример: KernelRequestListener, ExceptionListener\n │\n └── Presentation\u002F                        # 🖥️ ТОЧКИ ВХОДА (HTTP, CLI. Зависит от Application)\n     ├── Http\u002F                            # Взаимодействие по HTTP\n     │   ├── Controller\u002F                  # HTTP контроллеры (REST, GraphQL)\n     │   │   ├── UserController.php       # Пример: UserController, OrderController\n     │   │   └── OrderController.php      # Принимают запросы, вызывают CommandHandlers\n     │   │\n     │   ├── Request\u002F                     # HTTP Request DTO (если нужны)\n     │   │   └── CreateUserRequest.php    # Пример: CreateUserRequest, UpdateOrderRequest\n     │   │\n     │   ├── Response\u002F                    # HTTP Response DTO\n     │   │   └── UserResponse.php         # Пример: UserResponse, OrderResponse\n     │   │\n     │   └── Middleware\u002F                  # Middleware (авторизация, логирование, CORS)\n     │       └── AuthMiddleware.php       # Пример: AuthMiddleware, CorsMiddleware\n     │\n     └── Console\u002F                         # Пользовательские CLI-команды (бизнес-ориентированные)\n         └── Command\u002F                     # Пример: ProcessOrdersCommand, GenerateReportCommand\n             └── ProcessOrdersCommand.php # Вызывают Application CommandHandlers\n","text","",[81,82,77],"code",{"__ignoreMap":79},[29,84],{},[86,87,89],"h3",{"id":88},"_1-domain-layer-ядро-сущности","🧠 1. Domain Layer (Ядро \u002F Сущности)",[15,91,92,93,96],{},"Самый внутренний слой. Содержит ",[18,94,95],{},"корпоративные бизнес-правила"," (Enterprise Business Rules). Это сердце приложения.",[15,98,99],{},[18,100,101],{},"Состав:",[103,104,105,111,117,123,133,139],"ul",{},[39,106,107,110],{},[81,108,109],{},"Entity\u002F"," — Бизнес-сущности с поведением (Rich Domain Model). Содержат состояние и методы его изменения.",[39,112,113,116],{},[81,114,115],{},"ValueObject\u002F"," — Объекты-значения (Value Objects). Не имеют идентификатора, неизменяемы (immutable), сравниваются по значению.",[39,118,119,122],{},[81,120,121],{},"Repository\u002F"," — ТОЛЬКО интерфейсы (контракты) для доступа к данным.",[39,124,125,128,129,132],{},[81,126,127],{},"Service\u002F"," или ",[81,130,131],{},"Gateway\u002F"," — Интерфейсы для взаимодействия с внешними системами (если их нельзя отнести к Application).",[39,134,135,138],{},[81,136,137],{},"Event\u002F"," — Доменные события (Domain Events).",[39,140,141,144],{},[81,142,143],{},"Exception\u002F"," — Исключения, отражающие нарушение бизнес-правил.",[15,146,147],{},[18,148,149],{},"📋 Правила:",[151,152,153,167],"table",{},[154,155,156],"thead",{},[157,158,159,164],"tr",{},[160,161,163],"th",{"align":162},"left","✅ РАЗРЕШЕНО",[160,165,166],{"align":162},"❌ ЗАПРЕЩЕНО",[168,169,170,179,187,199,207],"tbody",{},[157,171,172,176],{},[173,174,175],"td",{"align":162},"Чистый PHP (без зависимостей от фреймворков)",[173,177,178],{"align":162},"Использовать аннотации ORM (Doctrine, Eloquent)",[157,180,181,184],{},[173,182,183],{"align":162},"Создание интерфейсов (контрактов)",[173,185,186],{"align":162},"Зависеть от HTTP, CLI, Базы Данных",[157,188,189,196],{},[173,190,191,192,195],{"align":162},"Использование независимых библиотек (напр., ",[81,193,194],{},"ramsey\u002Fuuid",")",[173,197,198],{"align":162},"Знать о существовании фреймворка (Symfony, Laravel)",[157,200,201,204],{},[173,202,203],{"align":162},"Реализация бизнес-логики и инвариантов",[173,205,206],{"align":162},"Выполнять SQL-запросы или HTTP-вызовы",[157,208,209,212],{},[173,210,211],{"align":162},"Фабричные методы для создания сущностей",[173,213,214],{"align":162},"Бизнес-логика в конструкторах",[29,216],{},[86,218,220],{"id":219},"️-2-application-layer-сценарии-использования","⚙️ 2. Application Layer (Сценарии использования)",[15,222,223,224,227],{},"Содержит ",[18,225,226],{},"бизнес-логику уровня приложения"," (Application Business Rules). Отвечает за как использовать Domain для выполнения конкретных сценариев (Use Cases).",[15,229,230],{},[18,231,101],{},[103,233,234,251,261,270,279,292,298],{},[39,235,236,237,240,241,240,244,240,247,250],{},"Модули по доменным областям (",[81,238,239],{},"Voting\u002F",", ",[81,242,243],{},"Season\u002F",[81,245,246],{},"Statistics\u002F",[81,248,249],{},"Bot\u002F",") — каждый содержит свои Command, Query, Handler, DTO",[39,252,253,256,257,260],{},[81,254,255],{},"Command\u002F"," + ",[81,258,259],{},"CommandHandler\u002F"," — синхронные use case, изменяющие состояние",[39,262,263,256,266,269],{},[81,264,265],{},"Query\u002F",[81,267,268],{},"QueryHandler\u002F"," — операции чтения",[39,271,272,256,275,278],{},[81,273,274],{},"Message\u002F",[81,276,277],{},"MessageHandler\u002F"," — асинхронная обработка через Symfony Messenger (не путать с CQRS Handler!)",[39,280,281,284,285,288,289,195],{},[81,282,283],{},"Port\u002F"," — общие интерфейсы Application-слоя (имена ",[81,286,287],{},"*Interface",", не ",[81,290,291],{},"*Port",[39,293,294,297],{},[81,295,296],{},"DTO\u002F"," — выходные объекты модулей",[39,299,300,302],{},[81,301,143],{}," — исключения уровня приложения (напр., EmployeeNotFoundException)",[15,304,305],{},[18,306,149],{},[151,308,309,317],{},[154,310,311],{},[157,312,313,315],{},[160,314,163],{"align":162},[160,316,166],{"align":162},[168,318,319,327,335,343,351],{},[157,320,321,324],{},[173,322,323],{"align":162},"Координировать работу Domain и Infrastructure",[173,325,326],{"align":162},"Прямая работа с HTTP-запросами\u002Fответами",[157,328,329,332],{},[173,330,331],{"align":162},"Вызывать репозитории и сервисы через интерфейсы",[173,333,334],{"align":162},"Прямые запросы к БД (SQL, DQL)",[157,336,337,340],{},[173,338,339],{"align":162},"Обрабатывать исключения и логировать контекст",[173,341,342],{"align":162},"Содержать бизнес-логику вне UseCase\u002FHandler",[157,344,345,348],{},[173,346,347],{"align":162},"Использовать паттерны (Saga, CQRS, Mediator)",[173,349,350],{"align":162},"Знать о деталях реализации (Doctrine, Kafka)",[157,352,353,356],{},[173,354,355],{"align":162},"Диспатчить команды в Message Bus",[173,357,358],{"align":162},"Работать напрямую с очередями\u002Fброкерами",[29,360],{},[86,362,364],{"id":363},"_3-infrastructure-layer-детали-реализации","🔌 3. Infrastructure Layer (Детали реализации)",[15,366,367,368,371],{},"Самый внешний слой. Содержит ",[18,369,370],{},"всё, что касается технических деталей:"," базы данных, внешние API, очереди, фреймворки. Реализует интерфейсы, заданные в Domain и Application.",[15,373,374],{},[18,375,101],{},[103,377,378,384,390,396,401,410,419,425,431,437],{},[39,379,380,383],{},[81,381,382],{},"Persistence\u002F"," — Работа с БД.",[39,385,386,389],{},[81,387,388],{},"Model\u002F"," — ORM-сущности (Doctrine Models, Eloquent Models).",[39,391,392,395],{},[81,393,394],{},"Mapper\u002F"," — Мапперы между Domain Entity и ORM Model.",[39,397,398,400],{},[81,399,121],{}," — Реализации интерфейсов из Domain\\Repository.",[39,402,403,128,406,409],{},[81,404,405],{},"Integration\u002F",[81,407,408],{},"Api\u002F"," — HTTP-клиенты для внешних API.",[39,411,412,128,415,418],{},[81,413,414],{},"Messaging\u002F",[81,416,417],{},"Kafka\u002F"," — Интеграция с очередями (Kafka, RabbitMQ).",[39,420,421,424],{},[81,422,423],{},"DI\u002F"," — Конфигурация контейнера внедрения зависимостей.",[39,426,427,430],{},[81,428,429],{},"Serializer\u002F"," — Настройки сериализации\u002Fдесериализации.",[39,432,433,436],{},[81,434,435],{},"Validation\u002F"," — Валидаторы и правила валидации.",[39,438,439,442],{},[81,440,441],{},"Console\u002F"," — Системные консольные команды, зависящие от фреймворка.",[15,444,445],{},[18,446,149],{},[151,448,449,457],{},[154,450,451],{},[157,452,453,455],{},[160,454,163],{"align":162},[160,456,166],{"align":162},[168,458,459,467,475,483],{},[157,460,461,464],{},[173,462,463],{"align":162},"Использовать Doctrine, Eloquent, PDO, Redis",[173,465,466],{"align":162},"Содержать бизнес-логику (только техническая)",[157,468,469,472],{},[173,470,471],{"align":162},"Реализовывать интерфейсы из Domain\u002FApplication",[173,473,474],{"align":162},"Вызываться напрямую из Presentation слоя",[157,476,477,480],{},[173,478,479],{"align":162},"Работать с HTTP-клиентами, Kafka, SMTP",[173,481,482],{"align":162},"Зависеть от Application или Presentation слоев",[157,484,485,488],{},[173,486,487],{"align":162},"Конфигурировать фреймворк и DI-контейнер",[173,489],{"align":162},[29,491],{},[86,493,495],{"id":494},"️-4-presentation-layer-точки-входа","🖥️ 4. Presentation Layer (Точки входа)",[15,497,498],{},"Слой драйверов. Отвечает за доставку запросов от пользователя\u002Fвнешней системы в Application и возврат ответа. Должен быть максимально \"тонким\".",[15,500,501],{},[18,502,101],{},[103,504,505,511,517,523,529,535],{},[39,506,507,510],{},[81,508,509],{},"Http\u002FController\u002F"," — HTTP контроллеры (REST, GraphQL).",[39,512,513,516],{},[81,514,515],{},"Http\u002FFactory\u002F"," — маппинг Request DTO → Command\u002FQuery (для сложных случаев).",[39,518,519,522],{},[81,520,521],{},"Http\u002FDTO\u002FRequest\u002F"," — HTTP Request DTO с Symfony Validator constraints.",[39,524,525,528],{},[81,526,527],{},"Console\u002FCommand\u002F"," — CLI-команды (бизнес-ориентированные).",[39,530,531,534],{},[81,532,533],{},"Listener\u002F"," — Обработчики событий фреймворка.",[39,536,537,540],{},[81,538,539],{},"Middleware\u002F"," — Проверки авторизации, валидация запросов.",[15,542,543],{},[18,544,149],{},[151,546,547,555],{},[154,548,549],{},[157,550,551,553],{},[160,552,163],{"align":162},[160,554,166],{"align":162},[168,556,557,565,573],{},[157,558,559,562],{},[173,560,561],{"align":162},"Парсить HTTP-запросы, валидировать входные данные",[173,563,564],{"align":162},"Содержать бизнес-логику или оркестрацию",[157,566,567,570],{},[173,568,569],{"align":162},"Создавать DTO\u002FCommand и передавать в Application",[173,571,572],{"align":162},"Напрямую обращаться к Repository или БД",[157,574,575,578],{},[173,576,577],{"align":162},"Форматировать HTTP-ответы (JSON, XML, HTML)",[173,579,580],{"align":162},"Знать о деталях Infrastructure",[29,582],{},[86,584,586],{"id":585},"поток-зависимостей-и-данных","🔄 Поток зависимостей и данных",[588,589,591],"h4",{"id":590},"граф-зависимостей-кто-от-кого-зависит","Граф зависимостей (Кто от кого зависит)",[73,593,596],{"className":594,"code":595,"language":78,"meta":79},[76]," ┌─────────────────────────────────────────────────┐\n │            Presentation Layer                   │\n │   (Controllers, CLI Commands, API Endpoints)    │\n └──────────────────────┬──────────────────────────┘\n                        │ вызывает\n                        ▼\n ┌─────────────────────────────────────────────────┐\n │            Application Layer                    │\n │   (UseCases, CommandHandlers, DTOs)             │\n └──────────────────────┬──────────────────────────┘\n                        │ использует интерфейсы\n                        ▼\n ┌─────────────────────────────────────────────────┐\n │              Domain Layer                       │\n │   (Entities, ValueObjects, Repository Contracts)│\n └───────────────────────▲─────────────────────────┘\n                         │ реализует\n ┌───────────────────────┴─────────────────────────┐\n │           Infrastructure Layer                  │\n │   (ORM Models, Mappers, Repo Implementations,   │\n │    HTTP Clients, Message Brokers, DI Config)    │\n └─────────────────────────────────────────────────┘\n",[81,597,595],{"__ignoreMap":79},[588,599,601],{"id":600},"поток-данных-на-примере-сохранения-сущности","Поток данных (На примере сохранения сущности)",[36,603,604,618,627,633],{},[39,605,606,609,610,613,614,617],{},[18,607,608],{},"Presentation:"," Принимает JSON, валидирует, создает ",[81,611,612],{},"CreateUserCommand",", вызывает ",[81,615,616],{},"CreateUserHandler",".",[39,619,620,623,624,617],{},[18,621,622],{},"Application:"," CreateUserHandler получает команду. Вызывает ",[81,625,626],{},"UserRepositoryInterface->save($user)",[39,628,629,632],{},[18,630,631],{},"Domain:"," Ничего не знает о том, как данные уйдут в БД. Просто отдает сущность.",[39,634,635,638,639,642,643,646,647,617],{},[18,636,637],{},"Infrastructure:"," ",[81,640,641],{},"UserRepository"," (реализация интерфейса) принимает Domain-сущность, через UserMapper конвертирует её в ",[81,644,645],{},"UserModel"," (ORM), сохраняет через ",[81,648,649],{},"EntityManager",[29,651],{},[10,653,655],{"id":654},"ключевые-паттерны-и-концепции","💡 Ключевые паттерны и концепции",[86,657,659],{"id":658},"_1-разделение-entity-и-model-orm","1. Разделение Entity и Model (ORM)",[15,661,662],{},"Это частая ошибка — использовать ORM-модели как бизнес-сущности.",[103,664,665,678,684],{},[39,666,667,670,671,674,675,617],{},[18,668,669],{},"Domain Entity:"," Богатая модель, содержит бизнес-инварианты (например, ",[81,672,673],{},"User->block()"," проверяет правила блокировки). Не имеет аннотаций ",[81,676,677],{},"#[ORM\\Column]",[39,679,680,683],{},[18,681,682],{},"Infrastructure Model:"," \"Тупая\" модель (Anemic), нужна только для маппинга на таблицу в БД. Содержит только геттеры\u002Fсеттеры и ORM-аннотации.",[39,685,686,689],{},[18,687,688],{},"Mapper:"," Класс, который переводит Domain Entity ↔ Infrastructure Model.",[86,691,693],{"id":692},"_2-порты-и-адаптеры-в-php-ports-and-adapters","2. Порты и Адаптеры в PHP (Ports and Adapters)",[15,695,696],{},"Архитектурный паттерн Ports and Adapters (Hexagonal Architecture) универсален, но PHP-сообщество исторически не использует слово Port в нейминге классов и директорий. Вместо этого применяются более описательные названия, отражающие суть контракта.",[15,698,699],{},"В PHP:",[103,701,702,716],{},[39,703,704,707,708,711,712,715],{},[18,705,706],{},"Порт (Port)"," — это ",[18,709,710],{},"Интерфейс"," (обычно с суффиксом ",[81,713,714],{},"Interface","), описывающий контракт.",[39,717,718,707,721,724],{},[18,719,720],{},"Адаптер (Adapter)",[18,722,723],{},"конкретный класс",", реализующий интерфейс и использующий технологии (Doctrine, Guzzle, Redis).",[15,726,727],{},[18,728,729],{},"Таблица соответствий терминов",[151,731,732,745],{},[154,733,734],{},[157,735,736,739,742],{},[160,737,738],{"align":162},"Академический термин",[160,740,741],{"align":162},"🐘 Идиоматичный PHP-нейминг",[160,743,744],{"align":162},"Где размещается",[168,746,747,764,784,801,818,836],{},[157,748,749,754,759],{},[173,750,751],{"align":162},[81,752,753],{},"UserRepositoryPort",[173,755,756],{"align":162},[81,757,758],{},"UserRepositoryInterface",[173,760,761],{"align":162},[81,762,763],{},"Domain\u002FRepository\u002F",[157,765,766,771,776],{},[173,767,768],{"align":162},[81,769,770],{},"PaymentGatewayPort",[173,772,773],{"align":162},[81,774,775],{},"PaymentGatewayInterface",[173,777,778,128,781],{"align":162},[81,779,780],{},"Domain\u002FGateway\u002F",[81,782,783],{},"Application\u002FService\u002F",[157,785,786,791,796],{},[173,787,788],{"align":162},[81,789,790],{},"EmailSenderPort",[173,792,793],{"align":162},[81,794,795],{},"EmailSenderInterface",[173,797,798],{"align":162},[81,799,800],{},"Domain\u002FService\u002F",[157,802,803,808,813],{},[173,804,805],{"align":162},[81,806,807],{},"ExternalApiClientPort",[173,809,810],{"align":162},[81,811,812],{},"SapClientInterface",[173,814,815],{"align":162},[81,816,817],{},"Infrastructure\u002FIntegration\u002F",[157,819,820,826,831],{},[173,821,822,825],{"align":162},[81,823,824],{},"UserManagementPort"," (вход)",[173,827,828],{"align":162},[81,829,830],{},"CreateUserCommandHandler",[173,832,833],{"align":162},[81,834,835],{},"Application\u002FCommandHandler\u002F",[157,837,838,843,848],{},[173,839,840,825],{"align":162},[81,841,842],{},"HttpApiPort",[173,844,845],{"align":162},[81,846,847],{},"UserController",[173,849,850],{"align":162},[81,851,852],{},"Presentation\u002FHttp\u002F",[15,854,855],{},[18,856,857],{},"Почему в PHP не используют слово \"Port\"?",[36,859,860,873,892],{},[39,861,862,865,866,868,869,872],{},[18,863,864],{},"Слишком абстрактно."," PHP-разработчики предпочитают, чтобы имя класса сразу отвечало на вопрос \"что это делает?\". ",[81,867,812],{}," говорит: \"это интерфейс для общения с SAP\". ",[81,870,871],{},"SapPort"," заставляет думать.",[39,874,875,878,879,240,882,240,885,240,888,891],{},[18,876,877],{},"Влияние фреймворков."," Symfony и Laravel популяризировали термины ",[81,880,881],{},"Repository",[81,883,884],{},"Gateway",[81,886,887],{},"Client",[81,889,890],{},"Handler",". Они прочно вошли в стандарт индустрии.",[39,893,894,897,898,901,902,905],{},[18,895,896],{},"Screaming Architecture."," Архитектура должна \"кричать\" о предметной области, а не о паттернах. Директория ",[81,899,900],{},"Infrastructure\u002FPersistence\u002F"," кричит: \"тут работа с базой!\", а ",[81,903,904],{},"Infrastructure\u002FAdapter\u002F"," — \"тут какой-то абстрактный адаптер\".",[15,907,908,911,912,914,915,918,919,240,921,240,923,240,926,240,929,240,932,240,934,617],{},[18,909,910],{},"⚠️ Важно:"," Избегайте создания директорий ",[81,913,283],{}," и ",[81,916,917],{},"Adapter\u002F",". Используйте семантические имена: ",[81,920,121],{},[81,922,131],{},[81,924,925],{},"Client\u002F",[81,927,928],{},"Sender\u002F",[81,930,931],{},"Publisher\u002F",[81,933,405],{},[81,935,382],{},[86,937,939],{"id":938},"_3-иммутабельность-и-dto","3. Иммутабельность и DTO",[15,941,942],{},"Все объекты, передающие данные между слоями (Commands, DTOs), должны быть неизменяемыми (readonly). Это гарантирует, что данные не будут изменены по пути от Presentation к Application, и упрощает потоковую безопасность.",[29,944],{},[10,946,948],{"id":947},"️-плюсы-и-минусы-подхода","⚖️ Плюсы и Минусы подхода",[86,950,952],{"id":951},"преимущества","✅ Преимущества",[103,954,955,960,966,972],{},[39,956,957,959],{},[18,958,55],{}," Ядро (Domain) покрывается юнит-тестами на 100% без моков БД и фреймворка.",[39,961,962,965],{},[18,963,964],{},"Поддерживаемость:"," Четкое разделение ответственности. Разработчик точно знает, где писать бизнес-логику, а где код для работы с БД.",[39,967,968,971],{},[18,969,970],{},"Гибкость:"," Легко заменить БД (MySQL → PostgreSQL), фреймворк (Symfony → Laravel) или способ доставки (REST → GraphQL), не трогая ядро.",[39,973,974,977],{},[18,975,976],{},"Независимость команд:"," Можно параллельно разрабатывать ядро и инфраструктуру.",[86,979,981],{"id":980},"недостатки-и-компромиссы","❌ Недостатки и Компромиссы",[103,983,984,990,996],{},[39,985,986,989],{},[18,987,988],{},"Boilerplate (много шаблонного кода):"," Создание Entity, Model, Mapper, Repository Interface, Repository Implementation для одной сущности требует времени.",[39,991,992,995],{},[18,993,994],{},"Порог входа:"," Команде нужно строго соблюдать правила. Новички часто пытаются \"срезать путь\" и нарушают границы слоев.",[39,997,998,1001],{},[18,999,1000],{},"Overkill для простых проектов:"," Для простого CRUD-приложения Clean Architecture будет избыточной (лучше использовать Active Record или простую Service-ориентированную архитектуру).",[29,1003],{},[10,1005,1007],{"id":1006},"️-чек-лист-для-code-review","🛡️ Чек-лист для Code Review",[15,1009,1010],{},"При ревью кода в проекте с Clean Architecture задавайте следующие вопросы:",[36,1012,1013,1016,1019,1022,1025,1028,1031,1034],{},[39,1014,1015],{},"Импортирует ли Domain слой что-либо из Symfony\u002FLaravel\u002FDoctrine? (Если да — ошибка)",[39,1017,1018],{},"Есть ли бизнес-логика (условия if\u002Felse, расчеты) в Infrastructure или Presentation слоях? (Если да — перенести в Domain\u002FApplication)",[39,1020,1021],{},"Обращается ли Presentation слой напрямую к Repository или БД? (Если да — перенести в Application UseCase)",[39,1023,1024],{},"Содержит ли Domain Entity аннотации ORM или зависимости от внешних сервисов? (Если да — вынести в Infrastructure)",[39,1026,1027],{},"Являются ли DTO и Command классы readonly?",[39,1029,1030],{},"Используются ли семантические имена для интерфейсов (Repository, Client, Sender), а не абстрактные Port\u002FAdapter?",[39,1032,1033],{},"Реализует ли Infrastructure слой интерфейсы из Domain\u002FApplication через DI-контейнер?",[39,1035,1036],{},"Находится ли бизнес-логика внутри Entity\u002FValueObject\u002FCommandHandler, а не в конструкторах и контроллерах?",{"title":79,"searchDepth":1038,"depth":1038,"links":1039},3,[1040,1042,1043,1054,1059,1063],{"id":12,"depth":1041,"text":13},2,{"id":33,"depth":1041,"text":34},{"id":67,"depth":1041,"text":68,"children":1044},[1045,1046,1047,1048,1049],{"id":88,"depth":1038,"text":89},{"id":219,"depth":1038,"text":220},{"id":363,"depth":1038,"text":364},{"id":494,"depth":1038,"text":495},{"id":585,"depth":1038,"text":586,"children":1050},[1051,1053],{"id":590,"depth":1052,"text":591},4,{"id":600,"depth":1052,"text":601},{"id":654,"depth":1041,"text":655,"children":1055},[1056,1057,1058],{"id":658,"depth":1038,"text":659},{"id":692,"depth":1038,"text":693},{"id":938,"depth":1038,"text":939},{"id":947,"depth":1041,"text":948,"children":1060},[1061,1062],{"id":951,"depth":1038,"text":952},{"id":980,"depth":1038,"text":981},{"id":1006,"depth":1041,"text":1007},"Backend","\u002Fcovers\u002Fclean-architecture.webp","2026-07-22","Главная цель Чистой архитектуры: создание системы, которая независима от фреймворков, баз данных, UI и внешних сервисов. Бизнес-логика изолирована и является ядром системы.",false,"md",{},true,"\u002Fblog\u002Fclean-architecture",{"title":5,"description":1067},"blog\u002Fclean-architecture",[1076,1077,1078],"architecture","pattern","php",null,"3Tl5PCu_W6pu1XBdh2ZiQ86-MKSh9bX59oKlvLOttCU",[1079,1082],{"title":1083,"path":1084,"stem":1085,"description":1086,"children":-1},"CQRS на практике: разделение команд и запросов без лишней сложности","\u002Fblog\u002Fcqrs","blog\u002Fcqrs","Архитектурный паттерн, который разделяет операции над системой.",1792405819914]