Pull to refresh

Comments 26

Не понял главного. Как оценивать достаточность указанных характеристик, выходя за границы воздушных замков в повседневную проектную реальность?

примеры и разборы больших архитектур на предмет измерения метрик и проверки соблюдения принципа планирую оформить отдельной статьёй :)
Наброски инструмента проверки архитектур, описанных по C4-модели, пока висят в непринятом PR'е: https://github.com/Byndyusoft/aact/pull/14
Как только стабилизируем инструмент — обязательно напишу и разберём с помощью него какую-нибудь большую реальную архитектуру :)

Пожалуйста, не дестабилизируйте C4.

Как оценивать достаточность указанных характеристик, выходя за границы воздушных замков в повседневную проектную реальность?

Просто заменить слово "связанность" на "количество перекладываний JSON'ов"
/sarcasm

Посмотрите книгу и подкаты Владика Хононова.

Основной принцип проектирования микросервисной архитектуры это атомарность микросервисов, разве нет?

А а целом , в книжке Structured design Edvard Yourdon & Larry L. Constantine - все вышеперечисленные "принципы" очень детально описаны, правда, безотносительно к микросервисной архитектуре.

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

За хорошую книжку, автору спасибо, ну а в чем новизна?

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

Я для себя выделил две эвристики того, как различать( Coupling связанность) и (Cohesion связность).

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

Второе. Связанность - это объединение кода библиотеками и фреймворками. А связность - это объединение бизнес-логикой.

Оба определения не идеальны, но как минимум толкают в нужном направлении.

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

если на более высоком уровне у вас много компонент, а на более низком мало

ну, пересобрать маленькие штуки в большие штуки.

сервис набирания воды из некоего источника, стратегия разгибания руки, фабрика хватателя предметов, мониторилка красной лампочки, все инварианты чайника, все стратегии опускания пакетика в кружку итп группируются в контекст/домен приготовления напитка.

таков путь

скорее всего, вы ошибочно обозвали все эти штуки высоким уровнем, и поэтому не складывается.

вы вправе держать это все отдельно, и не абстрагивать, и не тестировать (хоть на уровне разгибателя руки, хоть на уровне приготовления напитка) - вам возиться. будет тяжело, но если это осознанный выбор - в чем проблема-то?

Это вы по мотивам Владика Хононова написали? Тогда бы и его книгу привели в ссылках.

нет ) к сожалению, не читал. Видимо нужно ознакомиться :)

Вот эту свежую книгу я имел в виду: "Balancing Coupling in Software Design: Universal Design Principles for Architecting Modular Software Systems (Addison-Wesley Signature Series (Vernon))"

Круто! Такая формулировка принципа мне кажется более применимой на практике, чем у Дяди Боба.

PS Мне всегда больше нравился перевод "cohesion", как "согласованность".

Да, cohesion не по переводу а по смыслу это согласованность бизнес логики и ее концентрация чтобы эту согласованность легко проверять, доказывать и изменять.

В статье и по ссылкам используются термины гранулярность и границы, но рассматривается всё в контексте связанности и прочности. При этом вопросу, почему ИТ система является/стала распределённой внимание не очень уделяется, имхо.

К примеру, есть набор сервисов, которые постоянно ходят в базу за разнородными данными, и иногда достаточно активно их обновляют. Это периодически вызывает блокирование разных таблиц, подвисает логика, и как результат - пользователи UI замечают задержки в приложениях. Или начинают превышаться какие то требования по допустимым таймаутам обработки евентов. С точки зрения архитектуры и данных понятно, что надо рефакторить базу, таблицы, взаимодействия, сервисы, и тогда одна БД превратится в несколько, могут появиться кэши и новые сервера. А можно просто заменить диски условно на SSD, и после 10 кратного ускорения ввода вывода снять проблемы на несколько лет, начав или продолжая плавный рефакторинг архитектуры системы - всё равно надо масштабировать, если бизнес растёт, или ещё что. Это эквивалентно расширению границ области, в которой находятся те же и так же связанные данные БД. То есть, в данном случае, производительность ввода вывода является таким же, если не более важным фактором, как и связанность и прочность. В других задачах производительность вычислений может определять границы для группы как то связанных компонент

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

не уверен что до конца верно понял ваш посыл 😅. Что касается гранулярности как метрики архитектуры — да, есть такая метрика, но она лишь как-то характеризует систему: к примеру насколько мелко нарезаны микросервисы. Т.е. ни мелкая, ни крупная гранулярность не являются ошибкой или удачным решением в общем случае, это лишь характеристика (ни хорошая, ни плохая) выбранной стратегии. Подробнее об этом писал в своей статье: https://habr.com/ru/companies/oleg-bunin/articles/699750/

Представьте, что у вас есть набор объектов, например, 10 точек, между которыми могут существовать связи. Если рассматривать каждую точку отдельно, то потенциальное число пар (связей) может быть достаточно большим. Однако, когда вы группируете эти точки в несколько кластеров (скажем, 2 группы по 5 точек), то вы получаете два новых объекта – кластеры. Внутри каждого кластера действительно может быть много связей (например, каждая точка соединена с другими в группе), но связи между кластерами становятся гораздо менее значимыми или их вообще меньше.
Это можно формализовать так: если у вас есть множество элементов, число возможных пар растёт квадратично при рассмотрении всех элементов, но при группировке вы «сжимаете» систему – количество агрегатов существенно меньше, и, следовательно, число внешних связей (между агрегатами) также меньше.

да-да, я ж ровно об этом и говорю — весь вопрос в том, как мы проводим границы. По сравнению с точками, в случае ИТ-проектов дополнительные сложность и ограничения в том, что место проведения границ обычно ещё и имеет свою физическую (например, сетевую) состовляющую или даже административную (разделение ответственности команд).

Важно правильно очертить границы, чтобы к примеру нужный сервис попал в ответственность к правильной команде

@razon

Поясните, пожалуйста - куда и на каком уровне абстракции фронт встраивается в вашей модели?

В моей практике фронтовые приложения/компоненты зачастую работают со множеством (всеми) слабосвязанных между собой компонентов бэка и, кажется, сломают всю стройную картину - если фронт засунуть в один из ограниченных контекстов - он его coupling убьёт, а если в отдельный - у него самого ещё хуже coupling будет.

Фронт работает только с одним микросервисом — со специально выделенным для него BFF (backend for frontend), который уже в свою очередь работает с различными компонентами/микросервисами, но уже внутри периметра. В редких случаях особо сложных фронтендов — делаем несколько BFF, повторяющих структуру компонентизации микрофронтов на UI.

Так же, иногда разделяют BFF для декстопа и для мобильного приложения, но я тут обычно советую так делать только в случае сильно разного API для браузера и мобилки (ещё надо смотреть почему оно разное)

чуть-чуть касался этой темы и темы ограниченных контекстов в своей статье про гранулярность: https://habr.com/ru/companies/oleg-bunin/articles/699750/

@razon

И как вы находите границы микросервисов и ограниченных контекстов?

Условно ко мне пришёл продакт, говорит хочу фичу логистики, она должна делать то-то и то-то - в результате какого процесса у меня появятся артефакты/термины "ПВЗ Customer", "ПВЗ Маппер" и т.д. и "Ограниченный контекст ПВЗ", "Ограниченный контекст Геоданные" и т.д.

тут нет какого-то уникального подхода — всё стандартно как и всегда при DDD-анализе и проектировании микросервисов.
К примеру, у микрософта в гайдлайне по микросервисом это более-менее прилично описано:
https://learn.microsoft.com/en-us/azure/architecture/microservices/model/domain-analysis

абстрактность компонента должна нарастать с его стабильностью

Наверное, так точнее:

Абстрактность должна нарастать чтобы обеспечить его стабильность. Или не должна, если это дорого обходится.

Абстракция всего лишь инструмент для:

  • DRY, сокращения общего кода

  • Упрощение каскадных изменений

И да, абстракция защищает правила (функции) от изменений типов. Такая вот симметрия к ООП, где функции защищают (инкапсулируют) данные от некорректного изменения.

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

Альтернатива - раздельные типы, анти-DRY как в микросервисах. Кажется, непонятные абстракции и сложность их (редких) изменений всех достали, вот и придумали анти-DRY и выкрутили сдуру на максимум, как в микросервисах.

Поэтому тут архитектурный trade-off. Как всегда.

trade-off зависит от возможностей языка и от масштабов системы

Я бы ещё добавил немного про абстракции:

1.  Абстракция Типов:
       Цель: Определить общий контракт (интерфейс, поведение) для разных структур данных.
   
   Преимущества: Позволяет работать с разными типами единообразно (полиморфизм), скрывать реализацию, легко добавлять новые типы.
    *   Где проще: ООП. Встроенные механизмы (интерфейсы, абстрактные классы, наследование) делают создание и расширение иерархий типов естественным.

2.  Абстракция Функций:
       Цель: Выделить общую логику (алгоритм, операцию) в переиспользуемую единицу.
   
   Преимущества: Позволяет работать с разными функциями единообразно (заметили симметрию?), уменьшает дублирование кода (DRY), обобщает операции, улучшает композицию, легко добавлять новые операции/функции.
    *   Где проще: ФП. Функции как объекты первого класса, функции высшего порядка и акцент на композиции делают абстрагирование поведения и алгоритмов естественным.

Итог по парадигмам:

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

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

Я не понял, почему когда абстрагируемся, связи остаются такими же, что и на низких уровнях (HTTP запрос между микросервисами).

Пример: На уровне класса, связи - прямые вызовы. Дальше по абстракции может быть например несколько процессов, которые вызывают друг друга через IPC. Дальше несколько сервисов через общую шину. Дальше несколько систем через gRPC.

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

Sign up to leave a comment.

Articles