Architecture
...читать Eric Evans’s Domain-Driven Design or Martin Fowler’s Patterns of Enterprise Application Architecture
- Архитектуры по "форме" приложения
Это структурные стили — описывают, как приложение разделено на части и как они взаимодействуют.
| Стиль | Суть | Пример |
|---|---|---|
| Monolithic | Всё приложение — один большой блок (код, логика, UI, база). | Классическое веб-приложение на Django или Laravel. |
| Layered architecture (многослойная) | Разделение на слои: UI → Service → Domain → Data. | Типичная корпоративная Java-архитектура. |
| Hexagonal Architecture (Ports and Adapters) | Бизнес-логика (ядро) отделена от внешних систем (БД, API, UI) через порты и адаптеры. | Чистая архитектура в стиле DDD, популярна в Rust и Go. |
| MVC (Model–View–Controller) | Разделение на модель (данные), представление (UI) и контроллер (логика). | Классика для UI и веб-фреймворков (Rails, ASP.NET MVC). |
Эти стили можно комбинировать: например, может быть Hexagonal Monolith, который внутри реализует MVC.
- Архитектуры по распределению
Описывают взаимодействие между узлами в распределённой системе.
| Стиль | Суть | Пример |
|---|---|---|
| Client–Server | Клиент обращается к серверу, который отвечает. | Веб-браузер ↔️ сервер. |
| Microservices | Много независимых сервисов, каждый выполняет свою задачу. | Netflix, Amazon, микросервисы на Kubernetes. |
| Peer-to-Peer (P2P) | Все узлы равноправны, нет центрального сервера. | BitTorrent, блокчейн. |
- Client–Server — базовый шаблон,
- Microservices — его развитие (много серверов),
- P2P — противоположная модель (нет центрального узла).
- Архитектуры по взаимодействию (коммуникации)
Описывают как компоненты обмениваются данными.
| Стиль | Суть | Пример |
|---|---|---|
| Event-Driven Architecture (EDA) | Компоненты реагируют на события (асинхронно). | Kafka, RabbitMQ. |
| Publish–Subscribe (Pub/Sub) | Отправитель публикует события, подписчики слушают. | MQTT, Redis Pub/Sub. |
| CQRS (Command Query Responsibility Segregation) | Разделение операций чтения и записи данных. | Отдельные модели/БД для команд и запросов. |
| Event Sourcing | Вместо хранения состояния — сохраняются все события, из которых оно восстанавливается. | История заказов или транзакций. |
- CQRS и Event Sourcing часто идут в паре
- EDA и Pub/Sub — это механизмы коммуникации (Pub/Sub — частный случай EDA)
- Архитектуры по проектированию домена
Предметно-ориентированное проектирование (DDD)
(из книги "Орош Гергели - Разработчик ПО (Библиотека программиста)")
Предметно-ориентированное проектирование (DDD) предполагает, что в самом начале работы вы создадите модель предметной области бизнеса, чтобы понять, как она функционирует. Например, процесс создания системы платежей стоит начать с изучения видов платежей, бизнес-правил, а также контекста. Термин «предметно-ориентированное проектирование» был введен Эриком Эвансом (Eric Evans) в его книге «Domain-Driven Design: Tackling Complexity in the Heart of Software». К основным элементам DDD относятся:
-
Единый глоссарий. Первый шаг — убедиться, что все участники проекта говорят на одном языке. В DDD это называется «единообразный язык» (ubiquitous language). Чтобы разработать общий глоссарий, обсудите с экспертами предметной области все термины и обозначения, которые будут в дальнейшем использоваться. Это может показаться простой задачей, но инженеры-программисты и специалисты по соблюдению требований к платежам могут по-разному трактовать даже такие, казалось бы, очевидные термины, как «совершение платежа».
-
Контекст. Разделите сложные области на более мелкие части, которые в DDD называются «ограниченными контекстами» (bounded contexts). Каждый из этих контекстов имеет свой собственный глоссарий. Например, при проектировании платежной системы могут быть следующие контексты: регистрация, входящие платежи, исходящие платежи и расчет. Каждый контекст можно моделировать независимо и разбивать на более мелкие составляющие.
-
Сущности. Сущность — это объект, обладающий своей индивидуальностью и жизненным циклом. Многие объекты, названные именами существительными: части системы, люди и места — часто представляют собой сущности. Например, бухгалтерская проводка будет сущностью в системе платежей.
-
Объект-значения. Описывают сущности и не могут быть изменены. Например, валюта бухгалтерской проводки является объект-значением.
-
Агрегаты. Группы сущностей, рассматриваемые как единое целое.
-
События области — это события, о которых должно быть известно другим частям области, чтобы те могли на них реагировать. События помогают яснее обозначить триггеры и реакции на них. Например, когда в систему поступает платеж, баланс счета увеличивается на сумму платежа. Однако если ввести такое событие, как PaymentMadeEvent («событие совершения платежа»), эта ранее неявная логика становится явной: теперь «Счет» (Account) реагирует на PaymentMadeEvent, а не мониторит поступающие объекты Payment.
Главное преимущество применения принципов DDD в разработке ПО заключается в том, что они вынуждают инженеров глубже погружаться в бизнес-контекст — приходится общаться с бизнес-экспертами и разбираться, как устроен их мир. А благодаря общему глоссарию (см. выше) создаваемое вами ПО будет больше соответствовать «реальному миру».
Преимущества подходов DDD:
-
Меньше недопониманий между инженерами и бизнесом. Многие проекты задерживаются из-за того, что инженеры создают нечто, отличное от того, что ожидает бизнес. DDD предполагает взаимодействие с бизнес-стейкхолдерами с самого начала работы над проектом, что значительно снижает вероятность недопониманий.
-
Легче разобраться в сложностях предметной области. Бизнес-правила могут быть на удивление сложными, и DDD позволяет быстрее разобраться в них с помощью ограниченных контекстов.
-
Более читабельный код. Благодаря четко определенному общему глоссарию код становится читабельнее. Названия классов и переменных согласованы, и поэтому легко понять, для чего они предназначены. Да и в целом код будет более чистым.
-
Лучшая сопровождаемость. Достигается благодаря понятному коду и единому глоссарию.
-
Удобство расширения и масштабирования. При необходимости добавить новые бизнес-сценарии расширение можно сначала интегрировать в существующую модель предметной области. Расширение логики делает реализацию изменений на уровне кода проще. Масштабирование кода путем добавления большого количества новых бизнес-сценариев становится легче, и результат оказывается более аккуратным.
Узнать больше о применении DDD можно в книгах:
- «Learning Domain-Driven Design»1 Влада Хононова (Vlad Khononov) (2021);
- «Domain-Driven Design» Эрика Эванса (Eric Evans) (2003).
Они определяют как организовать бизнес-логику.
| Стиль | Суть | Пример |
|---|---|---|
| DDD (Domain-Driven Design) | Сосредоточено на бизнес-модели и предметной области, а не на технологии. | Используется с Hexagonal и CQRS. |
DDD часто комбинируется с Hexagonal, CQRS, Event Sourcing.
Итого: визуальная группировка
| Категория | Примеры |
|---|---|
| Структура приложения | Monolithic, Layered, Hexagonal, MVC |
| Распределённость | Client-Server, Microservices, Peer-to-Peer |
| Коммуникация | Event-Driven, Pub/Sub, CQRS, Event Sourcing |
| Модель предметной области | DDD |
Пример сочетания в реальной системе заказов:
- Архитектура: Hexagonal + DDD
- Взаимодействие между сервисами: Event-Driven (Kafka)
- Каждый сервис: CQRS + Event Sourcing
- Разделение сервисов: Microservices
- UI: MVC во фронтенде
- Общая форма деплоя: Client–Server
Architectural Styles
Monolithic
Монолитная архитектура (Monolithic)
- Суть: Вся система разрабатывается, развертывается и работает как единое целое. Компоненты тесно связаны и выполняются в одном процессе.
- Эксперимент: Сравнить классический многослойный монолит (Presentation-Business-Data layers) с модульным монолитом (Modular Monolith), где модули имеют строгие границы.
Messaging
Puplish-Subscribe
TODO: добавить больше информации, пример, пояснение
Event-Driven
Событийно-ориентированная архитектура (Event-Driven Architecture, EDA)
- Суть: Компоненты системы общаются путем асинхронной рассылки и обработки событий. Они слабо связаны и не знают друг о друге.
- Эксперимент: Спроектировать одну и ту же систему, используя EDA и более традиционный запросно-ответный стиль (например, REST), и сравнить производительность, отказоустойчивость и сложность.
TODO: добавить больше информации, пример, пояснение
Distributed
Client-Server
TODO: добавить больше информации, пример, пояснение
Peer-to-Peer
TODO: добавить больше информации, пример, пояснение
Архитектура "peer-to-peer" (P2P) представляет собой распределенную архитектурную модель, в которой каждый участник сети (пир) может одновременно выполнять роль как клиента, так и сервера. В такой сети нет центрального управляющего узла или сервера. Вместо этого каждый узел взаимодействует непосредственно с другими узлами, обмениваясь ресурсами, информацией или услугами.
Основные характеристики архитектуры P2P включают:
Децентрализация: Отсутствие единого центрального сервера, который координирует все действия в сети. Все узлы равноправны и могут взаимодействовать между собой напрямую.
Равноправность (Peerness): Все участники сети называются "пирами". Каждый пир имеет равные права и обязанности.
Самоорганизация: Узлы сети самостоятельно принимают решения и координируют свою работу с другими узлами, обеспечивая эффективность и стабильность сети.
Распределенная база данных: Данные часто распределены между разными узлами сети, и каждый узел может быть как потребителем, так и поставщиком данных.
Открытость: Сеть может быть открытой, что означает, что новые узлы могут присоединяться или покидать сеть в любое время без существенных изменений в архитектуре.
Примеры использования архитектуры P2P включают сети для обмена файлами (например, BitTorrent), блокчейн-технологии (например, Bitcoin), VoIP-телефонии (например, Skype) и децентрализованных приложений (DApps).
Архитектура P2P обеспечивает устойчивость к отказам, высокую масштабируемость и снижение зависимости от централизованных серверов, что делает ее привлекательной для различных приложений и сценариев.
Architectural Pattern Model View Controller(MVC)
TODO: добавить больше информации, пример, пояснение
Architectural Pattern Microservices
Микросервисная архитектура (Microservices)
- Суть: Система разбивается на небольшие, слабо связанные сервисы, каждый из которых отвечает за свою бизнес-возможность. Сервисы развертываются независимо и общаются по сети.
- Эксперимент: Сравнить разные способы коммуникации (синхронный REST vs асинхронный messaging) или разные стратегии декомпозиции (по бизнес-возможностям vs по поддоменам DDD).
TODO: добавить больше информации, пример, пояснение
Architectural Pattern Event Sourcing
TODO: добавить больше информации, пример, пояснение
Architectural Pattern CQRS
CQRS (Command Query Responsibility Segregation) — это шаблон проектирования, предлагающий разделение команд (изменяющих данные) и запросов (читающих данные) в приложении.
Принцип Command-Query Separation (CQS) разделение команд-запросов «Функции не должны вызывать абстрактные побочные эффекты... только команды (процедуры) могут вызывать побочные эффекты». - Бертран Мейер: Объектно-ориентированное создание программного обеспечения.
CQS - "Метод должен либо изменять состояние объекта (команда), либо возвращать данные (запрос), но не делать оба действия одновременно."
CQS и CQRS — это разные, хотя и связанные концепции. CQS действует на уровне методов/обьектов для поддержания частоты кода и не нуждается в синхронизации данных так как все в одном объекте, а CQRS на архитектурном уровне всей системы для масштабируемости и требует явной синхронизации данных. CQS является основой для понимания CQRS, но они решают разные задачи на разных уровнях абстракции.
Архитектурный паттерн CQRS разделения на разные модели:
#![allow(unused)] fn main() { // CQRS - разные модели для команд и запросов // МОДЕЛЬ КОМАНД (для записи) struct BankAccountWrite { balance: f64, } impl BankAccountWrite { fn new() -> Self { Self { balance: 0.0 } } fn deposit(&mut self, amount: f64) { self.balance += amount; // Здесь может быть сложная бизнес-логика } fn withdraw(&mut self, amount: f64) -> Result<(), String> { if amount > self.balance { return Err("Insufficient funds".to_string()); } self.balance -= amount; Ok(()) } } // МОДЕЛЬ ЗАПРОСОВ (для чтения) struct BankAccountRead { account_id: u64, balance: f64, last_transactions: Vec<Transaction>, } impl BankAccountRead { fn get_balance(&self) -> f64 { self.balance } fn get_transaction_history(&self) -> &[Transaction] { &self.last_transactions } fn get_account_details(&self) -> AccountDetails { // Комплексные данные для UI } } // Синхронизация между моделями (через события) struct AccountEventPublisher { // Отправка событий о изменениях } impl AccountEventPublisher { fn publish_deposit_event(&self, account_id: u64, amount: f64) { // Отправка в систему сообщений } } }
CQRS — это стиль архитектуры, в котором операции чтения отделены от операций записи.
Подход сформулировал Грег Янг на основе принципа CQS, предложенного Бертраном Мейером.
Чаще всего (но не всегда) CQRS реализуется в ограниченных контекстах (bounded context) приложений, проектируемых на основе DDD.
Одна из естественных причин развития CQRS — не симметричное распределение нагрузки и сложности бизнес-логики на read и write подсистем.
Большинство бизнес-правил и сложных проверок находится во write — подсистеме.
Мспользование с паттерном Command дает увеличение производительности за счет хранения истории состояния.
Фиксируйте все изменения состояния приложения как последовательность событий.
CQRS подходит для сложных доменов, которые также выигрывают от дизайна DDD.
Наиболее очевидная вещь, которую мы получили, используя Event Sourcing, - это то, что теперь у нас есть журнал всех изменений.
В системе хранилища вместо отклонения запроса пользователя, поскольку система больше не может обрабатывать запросы, она принимает событие и обрабатывает его позднее.
CQRS хорошо подходит для моделей программирования на основе событий.
Обычно CQRS разделяется на отдельные сервисы, взаимодействующие с Event Collaboration
Это позволяет этим службам легко использовать преимущества Event Sourcing
Query - Для извлечения данных приложения следует использовать Query. «Запрос данных» всегда действует без сохранения состояния и не может каким-либо образом изменять состояние приложения.
Каждая обработка Command обрабатывается внутри одного UnitOfWork для обеспечить атомарность примененных изменений.
Обработка нескольких Commands в последовательный способ (если требуется) может быть сделан с помощью Saga.
Выданная Command может быть проверена и перехвачена/обработана другими Агрегирование через CommandValidators и CommandInterceptor.
Пример: CommandHandler обрабатывает команды (создание, обновление, удаление сущности) и возвращает соответствующие события. GetEntityQueryHandler обрабатывает запрос для получения данных о сущности. EntityRepository представляет репозиторий для хранения сущностей.
#![allow(dead_code)] use std::rc::Rc; use std::cell::RefCell; use std::collections::HashMap; // Команда для создания новой сущности #[derive(Debug)] struct CreateEntityCommand { id: u64, name: String, } // Команда для обновления сущности #[derive(Debug)] struct UpdateEntityCommand { id: u64, name: String, } // Команда для удаления сущности #[derive(Debug)] struct DeleteEntityCommand { id: u64, } // Событие, сигнализирующее об успешном создании сущности #[derive(Debug)] struct EntityCreatedEvent { id: u64, name: String, } // Событие, сигнализирующее об успешном обновлении сущности #[derive(Debug)] struct EntityUpdatedEvent { id: u64, name: String, } // Событие, сигнализирующее об успешном удалении сущности #[derive(Debug)] struct EntityDeletedEvent { id: u64, } // Запрос для получения данных о сущности struct GetEntityQuery { id: u64, } // Результат запроса для данных о сущности #[derive(Debug)] struct GetEntityQueryResult { id: u64, name: String, } // Обработчик команд и событий trait Handler<TCommand, TEvent> { fn handle(&mut self, command: TCommand) -> Vec<TEvent>; } // Обработчик запросов trait QueryHandler<TQuery, TResult> { fn handle(&self, query: TQuery) -> TResult; } // Репозиторий для хранения сущностей struct EntityRepository { entities: HashMap<u64, String>, } impl EntityRepository { fn new() -> Self { EntityRepository { entities: HashMap::new(), } } fn create(&mut self, id: u64, name: String) { self.entities.insert(id, name); } fn update(&mut self, id: u64, name: String) { if let Some(entity) = self.entities.get_mut(&id) { *entity = name; } } fn delete(&mut self, id: u64) { self.entities.remove(&id); } fn get(&self, id: u64) -> Option<&String> { self.entities.get(&id) } } // Обработчик команд для создания, обновления и удаления сущностей struct CommandHandler { entity_repository: Rc<RefCell<EntityRepository>>, } impl Handler<CreateEntityCommand, EntityCreatedEvent> for CommandHandler { fn handle(&mut self, command: CreateEntityCommand) -> Vec<EntityCreatedEvent> { self.entity_repository.borrow_mut().create(command.id, command.name.clone()); vec![EntityCreatedEvent { id: command.id, name: command.name, }] } } impl Handler<UpdateEntityCommand, EntityUpdatedEvent> for CommandHandler { fn handle(&mut self, command: UpdateEntityCommand) -> Vec<EntityUpdatedEvent> { self.entity_repository.borrow_mut().update(command.id, command.name.clone()); vec![EntityUpdatedEvent { id: command.id, name: command.name, }] } } impl Handler<DeleteEntityCommand, EntityDeletedEvent> for CommandHandler { fn handle(&mut self, command: DeleteEntityCommand) -> Vec<EntityDeletedEvent> { self.entity_repository.borrow_mut().delete(command.id); vec![EntityDeletedEvent { id: command.id }] } } // Обработчик запроса для получения данных о сущности struct GetEntityQueryHandler { entity_repository: Rc<RefCell<EntityRepository>>//&'a EntityRepository, } impl QueryHandler<GetEntityQuery, Option<GetEntityQueryResult>> for GetEntityQueryHandler { fn handle(&self, query: GetEntityQuery) -> Option<GetEntityQueryResult> { if let Some(name) = self.entity_repository.borrow().get(query.id) { Some(GetEntityQueryResult { id: query.id, name: name.clone(), }) } else { None } } } fn main() { let entity_repository: Rc<RefCell<EntityRepository>> = Rc::new(RefCell::new(EntityRepository::new())); let mut command_handler = CommandHandler { entity_repository: Rc::clone(&entity_repository), }; let query_handler = GetEntityQueryHandler { entity_repository: Rc::clone(&entity_repository), }; // Создание новой сущности let create_command = CreateEntityCommand { id: 1, name: "Entity1".to_string(), }; let create_events = command_handler.handle(create_command); println!("Create Events: {:?}", create_events); // Обновление сущности let update_command = UpdateEntityCommand { id: 1, name: "UpdatedEntity1".to_string(), }; let update_events = command_handler.handle(update_command); println!("Update Events: {:?}", update_events); // Удаление сущности let delete_command = DeleteEntityCommand { id: 1 }; let delete_events = command_handler.handle(delete_command); println!("Delete Events: {:?}", delete_events); // Запрос данных о сущности let get_query = GetEntityQuery { id: 1 }; let result = query_handler.handle(get_query); println!("Query Result: {:?}", result); }
- CQRS
- DomainEvent martinfowler.com
- Patterns of Enterprise Application Architecture
- CQRS implementation
- CQRS implementation
- Command and Query Responsibility Segregation (CQRS)
Подход Domain-Driven Design (DDD)
TODO: добавить больше информации, пример, пояснение
DDD означает "Domain-Driven Design" (Проектирование с учетом предметной области) и представляет собой методологию и набор принципов, разработанных Эриком Эвансом.
DDD ориентировано на решение сложных задач в области проектирования программного обеспечения, особенно там, где ключевой упор делается на моделирование предметной области.
...
Вот некоторые ключевые концепции и принципы DDD:
Предметная область (Domain): Это область, которую охватывает ваше приложение или система. DDD предлагает сосредотачиваться на понимании и моделировании предметной области, чтобы лучше адаптировать приложение к бизнес-потребностям.
Модель (Model): В DDD модель предметной области становится центральным элементом. Модель представляет собой набор абстракций, описывающих ключевые концепции и сущности в предметной области. Она должна быть языком, понятным и близким к бизнес-понятиям.
Ограниченные контексты (Bounded Contexts): DDD предлагает разделять большие системы на ограниченные контексты, где каждый контекст имеет свою уникальную модель предметной области и границы, определенные для конкретных потребностей внутри этого контекста.
Агрегаты и сущности (Aggregates and Entities): Агрегаты представляют собой группу связанных сущностей, образующих единое целое. Сущности - это объекты с идентичностью, которые могут изменять свое состояние.
Сервисы (Services): В DDD сервисы представляют собой операции или функциональность, которые не принадлежат конкретной сущности или агрегату, но являются частью предметной области.
Фабрики и репозитории (Factories and Repositories): Фабрики отвечают за создание сложных объектов, а репозитории - за сохранение и получение объектов из хранилища.
DDD предоставляет набор инструментов и подходов, которые помогают разработчикам и бизнес-аналитикам совместно работать над сложными системами, обеспечивая лучшее соответствие кода предметной области требованиям бизнеса. Это особенно полезно в случаях, когда моделирование бизнес-логики является ключевым аспектом разработки.
Layered architecture
TODO: добавить больше информации, пример, пояснение
Слоенная архитектура (Layered Architecture) - это структурный подход к организации кода, в котором приложение разделяется на логические слои (или уровни), каждый из которых выполняет определенные функции. Каждый слой зависит только от слоев, находящихся ниже, и предоставляет интерфейсы для взаимодействия с вышележащими слоями. Это помогает создать модульную и легко поддерживаемую структуру приложения.
...
Ошибки применения Layered Architecture
Что такое Common Closure Principle? Ну, CCP утверждает:
Классы внутри компонента должны быть сгруппированы вместе на основе того же типа изменений, к которым они восприимчивы. Когда изменение влияет на компонент, оно должно влиять на все классы внутри этого компонента и ни на какие другие компоненты.
Следует отметить, что компоненты — это классы, которые связаны с вариантом использования. Поэтому любые классы, которые могут быть затронуты изменениями в варианте использования, должны быть сгруппированы вместе. Но когда вы перемещаете эти классы в отдельные папки, проекты или пакеты, они больше не группируются вместе, и таким образом вы нарушаете CCP. Разделения кода не по его связности, а по уровню функциональности и всем тем печальным аспектам, которые с этим связаны. То, что должно быть простой, понятной кодовой базой, превращается в разбросанную кодовую базу с классами повсюду. В результате любой, кто сталкивается с такими кодовыми базами, слышит утверждения, что это пример Чистой Архитектуры, и приходит к выводу, что во всех гадостях виновата философия.
Ваша кодовая база должна быть организована по функциям, а не по слоям. Приложение растет горизонтально, а не вертикально. То есть вы постоянно добавляете больше функций в приложение, но почти никогда не добавляете больше слоев. Вам нужно спланировать это расширение, чтобы по мере роста вашего кода было не только легче находить классы, связанные с компонентом, но и вы могли легко разделить кодовую базу на отдельные пакеты и службы по мере ее роста. Вы должны упростить поиск швов в вашем коде, где код может быть разделен. Папки — очевидный способ создания этих швов.
Обычно выделяют следующие основные слои в слоенной архитектуре:
Представление (Presentation Layer):
Этот слой отвечает за отображение данных пользователю и обработку пользовательского ввода. Включает в себя пользовательский интерфейс, отображение данных и обработку событий. Зависит от слоя бизнес-логики.
Бизнес-логика (Business Logic Layer или Service Layer):
Здесь содержится основная бизнес-логика приложения. Обрабатывает запросы от представления и координирует выполнение бизнес-правил и задач. Не зависит напрямую от конкретной реализации базы данных или представления.
Слой данных (Data Layer):
Этот слой управляет доступом к данным и их хранением. Включает в себя работу с базой данных, файлами, внешними API и т.д. Зависит от бизнес-логики. Преимущества слоенной архитектуры включают:
Модульность:
Каждый слой предоставляет четко определенный интерфейс, что облегчает замену или модификацию отдельных компонентов без воздействия на другие части приложения.
Понятность и управляемость:
Разделение приложения на логические слои делает код более читаемым и понятным. Разработчики могут сосредотачиваться на конкретной функциональности без необходимости вникать в детали других слоев.
Повторное использование:
Компоненты внутри каждого слоя могут быть повторно использованы в различных частях приложения или даже в разных приложениях.
Тестирование:
Каждый слой может быть легко протестирован независимо от других слоев.
Изменение технологий:
Замена или обновление технологий в одном слое не должно существенно влиять на другие слои.
Напоминаю, что слоенная архитектура предоставляет общую концепцию, и конкретная реализация может варьироваться в зависимости от конкретных требований и характеристик проекта.
Hexagonal Architecture (Шестиугольная архитектура)
TODO: добавить больше информации, пример, пояснение
Hexagonal Architecture (Шестиугольная архитектура), также известная как Ports and Adapters (Порты и Адаптеры), это паттерн архитектуры, предложенный Алистером Кокберном.
Он призван обеспечить легкость тестирования, гибкость и отделение бизнес-логики от деталей инфраструктуры.
Hexagonal Architecture поддерживает принципы чистой архитектуры (SOLID) и способствует созданию гибких, тестируемых и легко поддерживаемых приложений.
...
Основные идеи Hexagonal Architecture:
Ядро (Core):
Это основа приложения, содержащая бизнес-логику и правила предметной области. В ядре не зависят от деталей реализации, таких как базы данных, фреймворки или пользовательский интерфейс.
Порты (Ports):
Представляют собой интерфейсы или абстракции, которые определяют, как ядро взаимодействует с внешним миром. Это могут быть интерфейсы для работы с базой данных, веб-службы, пользовательские интерфейсы и т.д.
Адаптеры (Adapters):
Это конкретные реализации портов, которые подключают ядро к конкретным технологиям или внешним системам. Они "адаптируют" внешний мир к интерфейсам, определенным в портах.
Преимущества Hexagonal Architecture:
Тестируемость:
Ядро, не зависящее от инфраструктурных деталей, легко тестируется, так как тесты могут сфокусироваться на бизнес-логике, отделенной от внешних зависимостей.
Гибкость:
Подход позволяет легко менять внешние компоненты (адаптеры) без изменения ядра приложения.
Разделение ответственностей:
Бизнес-логика отделена от технических деталей, что упрощает понимание и обслуживание кода.
Hexagonal Architecture и слоенная архитектура (Layered Architecture) представляют собой два различных подхода к организации кода, но оба направлены на достижение подобных целей: улучшение тестируемости, гибкости и отделение бизнес-логики от деталей инфраструктуры.
Вот основные различия между ними:
Организация компонентов:
Hexagonal Architecture: Организована вокруг идеи "портов" (ports) и "адаптеров" (adapters). Ядро (бизнес-логика) зависит от портов, представляющих интерфейсы для взаимодействия с внешним миром. Адаптеры реализуют эти порты и подключают ядро к конкретным технологиям или внешним системам.
Слоенная архитектура: Организована вокруг идеи разделения кода на логические слои (presentation layer, business logic layer, data access layer и т.д.), где каждый слой имеет определенные обязанности. Каждый слой может зависеть только от слоев, находящихся ниже.
Фокус на внешние зависимости:
Hexagonal Architecture: Акцент делается на отделении ядра от внешних зависимостей. Бизнес-логика предоставляет порты, и адаптеры реализуют эти порты для взаимодействия с инфраструктурой.
Слоенная архитектура: Слои организованы так, чтобы каждый слой знал только о слоях, находящихся ниже. Например, бизнес-логика может вызывать слой доступа к данным, но не наоборот.
Структура кода:
Hexagonal Architecture: Архитектура предполагает, что бизнес-логика находится в центре и зависит только от интерфейсов (портов). Адаптеры реализуются вне ядра, что способствует легкости тестирования и замене внешних компонентов.
Слоенная архитектура: Организована слоями, каждый из которых предоставляет определенные функциональности. Взаимодействие между слоями происходит в строго определенной последовательности.
Обмен информацией:
Hexagonal Architecture: Обмен информацией между внешним миром и ядром происходит через порты и адаптеры, что способствует легкости изменения внешних зависимостей.
Слоенная архитектура: Обмен информацией обычно происходит внутри слоев, и внешние зависимости передаются через слои.
Оба подхода ценятся за свои преимущества в отношении тестируемости, гибкости и четкого разделения ответственностей. Выбор между Hexagonal Architecture и слоенной архитектурой может зависеть от конкретных требований проекта, предпочтений разработчиков и характера приложения.
PoSA Архитектура программного обеспечения, ориентированная на шаблоны
TODO: добавить больше информации, пример, пояснение
...