Башня Байткода
69 subscribers
26 photos
2 files
27 links
Авторские заметки об архитектуре ПО на основе реальной практики.

github: https://github.com/PHPTCloud
Download Telegram
#arch #cqs #cqrs #note
Command Query Responsibility Segregation (CQRS)

❓ Что такое CQS (Разделение команд и запросов / Command-Query separation)

Первым упоминанием CQS считается книга B. Meyer Object Oriented Software Construction. Основная идея CQS заключается в том, что в объекте методы могут быть двух типов:

1️⃣ Команды (Commands) - методы, которые изменяют состояние объекта, не возвращая значение;

2️⃣ Запросы (Queries) - методы, которые возвращают результат, не изменяя состояние объекта.


❓ Что такое CQRS (Разделение ответственности на Команды и Запросы / Command Query Responsibility Segregation)

CQRS означает разделение ответственности за команду и запрос - это шаблон, который разделяет операции чтения и обновления для хранилища данных. Внедрив в приложение CQRS, можно максимально увеличить его производительность, масштабируемость и защиту.

Преимущества CQRS включают:

1️⃣ Независимое масштабирование. CQRS позволяет раздельно масштабировать рабочие нагрузки чтения и записи, снижая риск конфликтов блокировки.

2️⃣ Оптимизация схем данных. Для процессов чтения можно применить схему, оптимизированную для запросов, а для процессов записи — другую схему, оптимизированную для обновлений.

3️⃣ Четкое разделение зон ответственности. Разделение процессов чтения и записи позволяет получить более гибкие и простые в обслуживании модули. Бо́льшая часть сложной бизнес-логики переместится в модель записи. Это в некоторой степени упростит модель чтения.

4️⃣ Более простые запросы. Сохраняя в базе данных для чтения материализованное представление данных, вы предотвратите использование приложением сложных соединений в запросах.

https://cqrs.nu
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥6👍4
#exp #gex #arch
Command/Query bus на практике


Когда мы принимаем на вход какие-либо данные от пользователей, мы обязательно валидируем входящие данные и формируем ответ в нужном формате. Обычная схема в таком случае это Controller => Use Case => Domain Logic => Responder.

Controller - принимает и валидирует данные;
Use Case - агрегирует и использует доменную логику (Domain Logic);
Responder - формирует ответ в нужном формате.

Обычно этого хватает на старте, но с развитием системы могут появиться проблемы: разработчик забыл написать валидацию, нарушение конвенций или их отсутствие, некачественная валидация и т.д.

Решением подобных проблем являются внедрение правил CQRS и организация слоя Command/Query bus.

❓ Что такое Command/Query bus?

CQB - это изолированный слой системы, который несет ответственность за все входящие данные и формирует выходящие данные.

Для CQB можно выделить следующие задачи:

1️⃣ формирует команду/запрос;

2️⃣ валидирует команду/запрос;

3️⃣ передает её в обработчик команды/запроса;

4️⃣ обработчик команды/запроса формирует ответ*.

* В случае команд (запись данных) можно отдать стандартный ответ из контролера

CQB унифицирует работу с входными/выходными данными и предотвращает передачу некорректных данных в слой бизнес логики.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥5👍4
#exp #arch #gex
Выводы о Гексагональной архитектуре


✅ Преимущества

1. Строго декларируются зоны ответственности, которые инкапсулируются в слоях приложения;
2. Бизнес-логика отделяется от логики системы, что упрощает миграцию кода;
3. Строго определяется направление зависимостей в соответствии с DIP;
4. Исчезает проблема зависимости от конкретных технических и технологических решений;
5. Бизнес-логика и системная логика отделяются от модулей представления;
6. Появляются правила объединения модулей в компоненты, которые можно переиспользовать в других системах;
7. Появляются правила CQRS, позволяющие увеличить производительность системы.


🚫 Недостатки

1. Очень высокий порог входа в концепции паттерна;
2. Увеличение количества слоев может затруднить понимание работы системы;
3. Большое количество кодовой базы.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5🔥3
#gex #navi
Первая цепочка постов о Гексагональной архитектуре подошла к концу. Надеюсь, вы получили много полезой информации о том, как работать с рассмотренным паттерном. Чтобы глубже понять Гексагоналку, нам потребуется больше времени, а пока продолжим погружаться в другие интересные темы проектирования архитектуры.

Навигация по постам

1. Что такое Гексагональная архитектура?
2.
Что такое слои приложения, и зачем они нужны?
3.
Слой доменов Часть 1, Часть 2
4.
Порты и Адаптеры
5.
Принцип инверсии зависимостей в Гексагональной архитектуре
6.
Слой приложения
7.
Инфраструктурный слой
8.
Инфраструктурный слой. Что в него выделять?
9.
Слой представления
10.
Разделение на компоненты и слайсы (Feature slicing)
11.
Command/Query bus на практике
👍5🔥4
#arch #solid #note
Принцип единственной ответственности — The Single Responsibility Principle или SRP

Принцип единственной ответственности — The Single Responsibility Principle или SRP — один из пяти основных принципов объектно‑ориентированного программирования и проектирования, сформулированных Робертом Мартином.

Принцип декларирует, что каждый объект должен иметь одну обязанность и эта обязанность должна быть полностью инкапсулирована в класс, а все его сервисы должны быть направлены исключительно на обеспечение этой обязанности.

Следование принципу заключается обычно в декомпозиции сложных классов, которые делают сразу много вещей, на простые, отвественность которых очень специализирована. Но также и объединении в отдельный класс однотипной функциональности, которая может оказаться распределённой по многим классам, может рассматриваться как следование этому принципу.

Проектирование классов с направленностью на обеспечение единственной обязанности упрощает дальнейшие модификации и сопровождение, так как проще разобраться в одном блоке функциональности, нежели распутывать сложные взаимосвязи между различными функциональными блоками. Также при модификации логики в одном месте приложения снижаются риски возникновения проблем в других «неожиданных» его местах.

Следование SRP весьма полезная практика с точки зрения повторного использования кода. Сложные объекты с комплексными зависимостями обычно очень сложно использовать повторно, особенно если нужна только часть реализованного в них функционала. А небольшие классы с чётко очерченным функционалом, напротив, проще использовать повторно, так как они не избыточные и редко тянут за собой существенный объём зависимостей.
👍6🔥1
#arch #solid #note
Принцип открытости/закрытости — The Open Closed Principle или OCP

Принцип декларирует, что программные сущности (классы, модули, функции и т. п.) должны быть открыты для расширения, но закрыты для изменения. Это означает, что эти сущности могут менять свое поведение без изменения их исходного кода.

В этом контексте открытость для расширения — это возможность добавить для класса, модуля или функции новое поведение, если необходимость в этом возникнет, а закрытость для изменений — это запрет на изменение исходного кода программных сущностей. На первый взгляд, это звучит сложно и противоречиво. Но если разобраться, то принцип вполне логичен.

Следование принципу OCP заключается в том, что программное обеспечение изменяется не через изменение существующего кода, а через добавление нового кода. То есть созданный изначально код остаётся «нетронутым» и стабильным, а новая функциональность внедряется либо через наследование реализации, либо через использование абстрактных интерфейсов и полиморфизм.

Принцип открытости/закрытости Мейера основывается на идее, что разработанная изначально реализация класса в дальнейшем не модифицируется (разве что исправляются ошибки), а любые изменения производятся через создание нового класса, который обычно наследуется от изначального. Согласно определению Мейера реализация интерфейса может быть унаследована и переиспользована, но интерфейс может и измениться в новой реализации.
👍5🔥3
#arch #solid #note
Принцип подстановки Барбары Лисков — The Liskov Substitution Principle или LSP

Принцип в формулировке Роберта Мартина декларирует, что функции, которые используют базовый тип, должны иметь возможность использовать подтипы базового типа не зная об этом. Оригинальное определение Барбары Лисков более формальное и заметно сложнее для восприятия: «В том случае, если q(x) — свойство, верное по отношению к объектам х некого типа T, то свойство q(y) тоже будет верным относительно ряда объектов y, которые относятся к типу S, при этом S — подтип некого типа T».

Следование принципу LSP заключается в том, что при построении иерархий наследования создаваемые наследники должны корректно реализовывать поведение базового типа. То есть если базовый тип реализует определённое поведение, то это поведение должно быть корректно реализовано и для всех его наследников.

LSP перекликается с контрактным программированием, определяя точные, формальные и верифицируемые описания интерфейсов. И интерфейсы, реализумые наследниками, должны соответствовать контракту интерфейсов базового класса.

Наследник класса дополняет, но не заменяет поведение базового класса. То есть в любом месте программы замена базового класса на класс‑наследник не должна вызывать проблем. Если по каким‑то причинам так не получается, то вероятнее всего имеет место либо некорректная реализация, либо неверно выбранная абстракция для наследования.

Соблюдение принципа подстановки Барбары Лисков позволяет гарантировать, что любой созданный нами подкласс будет без проблем использоваться ранее реализованными модулями, которые работали с надклассом. А это существенно упрощает расширение функциональных возможностей системы.

Но LSP, как и любой другой принцип, не является догмой. И иногда следование этому принципу при построении архитектуры может приводить к более ресурсоёмкой реализации, нежели работа с нарушением этого принципа. Но как и с любыми другими правилами — надо осознавать возможные последствия нарушения.
👍4🔥3
#arch #solid #note
Принцип разделения интерфейса — The Interface Segregation Principle или ISP

Принцип в формулировке Роберта Мартина декларирует, что клиенты не должны зависеть от методов, которые они не используют. То есть если какой‑то метод интерфейса не используется клиентом, то изменения этого метода не должны приводить к необходимости внесения изменений в клиентский код.

Следование принципу ISP заключается в создании интерфейсов, которые достаточно специфичны и требуют только необходимый минимум реализаций методов. Избыточные интерфейсы, напротив, могут требовать от реализующего класса создание большого количества методов, причём даже таких, которые не имеют смысла в контексте класса.

В чём‑то принцип разделения интерфейса перекликается с принципом единственной ответственности — интерфейсы не должны быть избыточно «толстыми», если вдруг в приложении формируется слишком объёмный интерфейс, то есть высокая вероятность, что так происходит из‑за того, что в этом интерфейсе слишком много разных ответственностей, а значит логичнее всего провести декомпозицию сложного интерфейса на несколько простых.

Принцип разделения интерфейса снижает сложность поддержки и развития приложения. Чем проще и минималистичнее используемый интерфейс, тем менее ресурсоёмкой является его реализация в новых классах, тем меньше причин его модифицировать, но и в случае модификации она будет значительно проще.
👍4🔥3
#arch #note #solid
Принцип инверсии зависимостей — The Dependency Inversion Principle или DIP

Принцип декларирует, что модули верхних уровней не должны зависеть от модулей нижних уровней, а оба типа модулей должны зависеть от абстракций; сами абстракции не должны зависеть от деталей, а вот детали должны зависеть от абстракций.

Следование принципу инверсии зависимостей «заставляет» реализовывать высокоуровневые компоненты без встраивания зависимостей от конкретных низкоуровневых классов, что, например, сильно упрощает замену используемых зависимостей как по бизнес‑требованиям, так и для целей тестирования. При этом зависимость формируется не от конкретной реализации, а от абстракции — реализуемого зависимостью интерфейса.

Например, мы реализуем хранение документов в веб‑приложении. На первый взгляд, кажется логичным добавить зависимость от модулей работы с файловой системой непосредственно в класс, отвечающий за высокоуровневую работу с этими документами. Но в перспективе такая зависимость может создать проблемы — например, нам потребуется хранить данные не только на диске, но и в облаке. Если зависимость внедрена от реализации, то мы столкнёмся с необходимостью её переработки. Если же зависимость выведена на уровень абстракции (интерфейса), то нам будет достаточно реализовать функционал работы с облаком, соответствующий ранее созданному интерфейсу работы с файлами.

Принцип инверсии зависимостей часто упрощает следованию принципу подстановки Барбары Лисков. Выделение абстракций и встраивание зависимостей от них снижает вероятность того, что в новом классе мы не полностью реализуем контракт базового класса, который мы расширяем в рамках нового.
👍4🔥2
#arch #note #solid #solid4mens
Принцип Стабильных Зависимостей - The Stable Dependencies Principle или SDP

«Не получится строить стабильное на нестабильном»

Стабильным компонентом называется компонент, который редко меняется в силу своей зрелости, и по этой же причине многие модули системы зацеплены на этом компоненте, а он в свою очередь почти не зацеплен с кем-то еще. Чаще всего такие компоненты находятся в ядре вашей системы и разработаны очень давно, вокруг них строятся другие приложения, использующие их в своих целях.

Зацепление — мера того, насколько взаимозависимы разные подпрограммы или модули.

Сильное зацепление рассматривается как серьёзный недостаток, поскольку затрудняет понимание логики модулей, их модификацию, автономное тестирование, а также переиспользование по отдельности. Слабое зацепление, напротив, является признаком хорошо структурированной и хорошо спроектированной системы.
👍4🔥3🤯3
#arch #solid #sold4mens #note
Принцип стабильных абстракций - The Stable Abstraction Principle или SAP

Стабильные пакеты абстрактны. То есть абстрактность должна возрастать со стабильностью. Стабильность здесь — то, как часто нам приходится менять части пакета: классы, интерфейсы, или что-либо ещё. Абстрактные пакеты должны быть стабильны, чтобы безболезненно на них завязываться.

А вот гибкие пакеты, наоборот, конкретны — они должны быть нестабильны, чтобы мы их легко меняли. Все наши приложения, наши конечные пакеты — это на самом деле очень нестабильные составляющие, которые зависят от множества всего и ломаются чаще, чем пакеты, от которых они зависят.

❓ Можно ли измерить абстрактность?

Да. Абстрактность — это число абстрактных классов и интерфейсов в пакете, деленное на общее число классов и интерфейсов в этом самом пакете.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4🔥3🤯3
#arch #solid #solid4mens #note
Принцип ациклических зависимостей - The Acyclic Dependencies Principle или ADP

Если модули или компоненты системы можно отобразить в виде графа, то в таком графе запрещается иметь циклические зависимости между модулями или компонентами. Принцип ациклических зависимостей гласит, что в таком графе должны быть только однонаправленные зависимости между частями системы.

Представьте, что вы используете базу данных, которая зависит от модуля журналирования для записи времени запуска базы данных. Но модулю журналирования нужна база данных, чтобы узнать, куда записывать время запуска.

Другой пример: вы написали "Модуль Б", который зависит от "Модуля А" и использует функции "Модуля В". "Модуль В" в свою очередь зависит от "Модуля А". Если вы или ваши коллеги внесут изменения в "Модуль Б", то эти изменения затронут "Модуль В", а он затронет код "Модуля А", который затронет код "Модуля Б"... и начнется типичная дилемма курицы и яйца. 🤯

Следование принципу ADP решает эту дилемму и другие проблемы:

1️⃣ Сложность тестирования и обслуживания компонентов системы;

2️⃣ Циклические ссылки на классы создают высокую степень связи; оба класса должны перекомпилироваться каждый раз, когда любой из них изменяется;

3️⃣ Объекты с очень большим количеством циклических ссылок часто являются объектами-богами. Даже если это не так, они имеют тенденцию приводить к спагетти-коду.

* на постере пример системы с циклическими зависимостями.
Please open Telegram to view this post
VIEW IN TELEGRAM
🤯5👍2
#exp #arch #grasp
Проектирование пакетов/компонентов системы

На ряду с принципами SOLID, Роберт Мартин описал также 6 других:

1️⃣ Принцип эквивалентности повторного использования и выпуска (Reuse-Release Equivalency Principle или REP);

2️⃣ Общий принцип закрытия (Common Closure Principle или CCP);

3️⃣ Общий принцип повторного использования (Common Reuse Principle или CRP);

4️⃣ Принцип ациклических зависимостей (Acyclic Dependencies Principle или ADP);

5️⃣ Принцип стабильных зависимостей (Stable Dependencies Principle или SDP);

6️⃣ Принцип стабильной абстракции (Stable Abstraction Principle или SAP).

Возможно, вы уже слышали рекомендацию, в которой говорится, что мы должны стремиться к достижению low coupling (низкой связанности) и high cohesion (высокого сцепления) при работе над кодовой базой.
Мне не нравится термин "сцепление", поэтому я буду использовать слово "сплоченность".

❗️ Coupling (связанность) представляет собой степень взаимосвязи между блоками. Другими словами, это количество соединений между двумя или более блоками. Чем меньше число, тем ниже coupling. Блок (юнит) здесь необязательно является классом. Это может быть метод, класс, группа классов или даже модуль.

Понятие Cohesion (сплоченность) мы разберем чуть позже, когда узнаем о принципах REP, CCP, CRP. Для удобства эти принципы разделяют на следующие группы:

1️⃣ Принципы связности пакетов/компонентов - REP, CCP, CRP - снижают степень связности (coupling) пакетов и компонентов;

2️⃣ Принципы сплоченности пакетов/компонентов - SAP, SDP, ADP - увеличивают степень сплоченности (cohesion).

* На постере идеальный пример сплоченности модулей и сцепления компонентов.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4👍3
#arch #note #solid4mens #solid
Принцип эквивалентности повторного использования и выпуска - The Reuse-Release Equivalency Principle или REP


❗️ Принцип гласит, что единица повторного использования не может быть меньше единицы выпуска. Все, что предназначено для повторного использования, должно управляться какой-то системой учета выпусков. Не может быть так, чтобы разработчик просто написал какой-то класс и объявил его повторно используемым. О возможности повторного использования можно говорить только тогда, когда внедрена некая система учета выпусков и обеспечены гарантии извещения, надежности и поддержки, в которых так нуждаются потенциальные пользователи.

❗️ Эффективное повторное использование требует отслеживания релизов в системе контроля изменений

Автору библиотеки необходимо идентифицировать выпуски по каким-либо номерам или названиям. Это позволяет пользователям библиотеки идентифицировать разные версии. Это требует использования какой-то системы отслеживания релизов.

❗️ Единица повторного использования равна единице выпуска.

Можно было бы использовать класс в качестве единицы повторного использования и выпуска, однако в типичном приложении так много классов, что системе отслеживания выпусков было бы обременительно отслеживать их все. Требуется более крупномасштабная организация, и пакет хорошо соответствует этой потребности.

Пакет — это эффективная единица повторного использования и выпуска.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥5👍3
#arch #note #solid4mens #solid
Общий принцип закрытия - The Common Closure Principle или CCP

❗️ Общий принцип закрытия гласит, что классы, которые изменяются по одним и тем же причинам и в одно и то же время, должны быть собраны в компоненты, а классы, которые изменяются в разное время и по разным причинам, должны быть разделены на разные компоненты.

Этот принцип представляет собой переформулировку принципа единой ответственности (SRP), рассмотренного в SOLID , путем применения его к компонентам. Точно так же, как SRP утверждает, что у класса не должно быть более одной причины для изменения, CCP утверждает, что у компонента не должно быть более одной причины причины для изменения.

❗️ Когда необходимо внести изменения в код, идеально, чтобы эти изменения происходили только в одном компоненте, а не в нескольких.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥5👍3
#arch #note #solid4mens #solid
Общий принцип повторного использования - The Common Reuse Principle или CRP

❗️ Принцип совместного повторного использования (CRP) помогает определить, какие классы и модули должны быть включены в компонент. Он говорит, что классы и модули, которые используются вместе, должны быть включены в один компонент.

Также CRP указывает, какие классы не должны быть объединены. Когда один компонент использует другой, между ними возникает зависимость. Даже если использующему компоненту нужен только один класс из используемого, это не уменьшает зависимость. Поэтому, когда происходит изменение в используемом компоненте, обычно требуются соответствующие изменения в использующем компоненте.

Иногда, даже если в использующем компоненте ничего не меняется, его все равно нужно перекомпилировать, тестировать и развертывать заново.

❗️ Компонент не должен зависеть от того, что ему не требуется.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥5👍3
#arch #solid #note #solid4mens
Диаграмма противоречий для определения связности компонентов

Три принципа связности компонентов вступают в противоречие друг с другом:

1️⃣ Принципы эквивалентности повторного использования (REP) и согласованного изменения (CCP) являются включительными: оба стремятся сделать компоненты как можно крупнее.

2️⃣ Принцип повторного использования (CRP) — исключительный, стремящийся сделать компоненты как можно мельче. Задача хорошего архитектора — разрешить это противоречие.

Диаграмма противоречий, показывающая, как три принципа связности влияют друг на друга. Ребра на диаграмме описывают цену нарушения принципа на противоположной вершине.

❗️ Архитектор, уделяющий внимание только принципам REP и CRP, обнаружит, что простое изменение вовлекает слишком большое количество компонентов. С другой стороны, архитектор, уделяющий особое внимание принципам CCP и REP, вынужден будет выпускать слишком много ненужных версий.

❗️ Хороший архитектор найдет в этом треугольнике противоречий золотую середину, отвечающую текущим нуждам разработчиков, а также подумает об изменениях, которые могут произойти в будущем. Например, на ранних этапах разработки проекта принцип CCP намного важнее, чем REP, удобство разработки важнее удобства повторного использования.

Вообще говоря, в начале разработки наибольшую важность имеет правая сторона треугольника, когда единственной жертвой является повторное использование. Но по мере развития и интеграции в другие проекты фокус начинает смещаться влево. То есть организация компонентов в проекте может изменяться с течением времени. Это больше связано с тем, как проект разрабатывается и используется, нежели с тем, что фактически этот проект делает.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4🔥2🤯2
#arch #book
Чистая архитектура. Искусство разработки программного обеспечения

Автор: Роберт Мартин
Год выпуска: 2017-2018
Страниц: 352
👍4🔥3
Башня Байткода pinned «#gex #navi Первая цепочка постов о Гексагональной архитектуре подошла к концу. Надеюсь, вы получили много полезой информации о том, как работать с рассмотренным паттерном. Чтобы глубже понять Гексагоналку, нам потребуется больше времени, а пока продолжим погружаться…»
#exp #arch #case
Хочу поделиться кейсом задачи, которую недавно решил в Github Booster. Цель - продемонстрировать, как можно развивать отдельные модули, как можно мыслить, чтобы дать импульс такому развитию.

📌 Задача

1️⃣ Использовать два кейса - syncSubscribers и syncSubscriptions для подписки на пользователей, которые подписались на целевого пользователя в Github, но на которых я не подписался в ответ.

2️⃣ Использовать два кейса - syncSubscribers и syncSubscriptions для отписки от пользователей, на которых подписался целевой пользователь, но которые не подписались на меня в ответ.

Процесс развития модулей

▶️ Уровень 1

Нарушен принцип SRP - класс SyncManager может изменяться более чем по одной причине. Декомпозируем.

▶️Уровень 2

Можно так и оставить, но мне бы хотелось абстрагироваться от каждого кейса, поэтому через Manager классы я буду вызывать Handler классы. Это поможет мне в будущем заменить один Handler на другой так, чтобы те, кто использует Manager класс, не знали об этом измении и продолжали свою работу.

▶️ Уровень 3

Все, что хотели, разделили и абстрагировали. И тут возникла циклическая зависимость, которая порождается методами subscribersBalancing и subscriptionsBalancing. SubscribersManager класс зависит от SubscriptionsManager и наоборот.

▶️ Уровень 4

Для решения проблемы циклических зависимостей делаю следующее:

Cоздаю интерфейсы SubscribersSyncManagerInterface и SubscriptionsSyncManagerInterface, чтобы Manager классы завязывались на них (выделены зеленым).

Эти интерфейсы в моем случае ведут к еще одним Manager классам, которые нужны для отдельной логики.

Если не нужно делать дополнительные действия, можно привязать эти интерфейсы к Handler классам, и проблема циклических зависимостей будет решена.

Плюсы и минусы

➕ Соблюдается принцп SRP;
➕ Есть Handler классы, которые удобно покрывать тестами;
➕ Через Dependency Injection можно переопределять детали реализации интерфейсов.

❌ Много классов;
❌ Структура проекта без конкретных правил может стать чрезмерно запутанной.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4🔥4🤔1