Предисловие
Как ты уже понял из названия, говорить я тут собрался о самом, что ни на есть, “высоком” — Чистой архитектуре. А толчком к развитию всей этой истории послужила книга Роберта Мартина “Чистая архитектура”. Если еще не читал, осмелюсь порекомендовать! Автор раскрывает много важных тем, активно делится своим богатым жизненным опытом (из проф. области естественно) и сделанными на его основе выводами, эпизодически вплетает в главы истории о том, как виртуозно говнокодили (ну и не только, конечно же) наши отцы и деды в далёких 60-х, 70-х, 80-х и даже лихих 90-х, как по крупинкам собирали всеми любимые принципы SOLID и их аналоги в мире компонентов, и чему научились за прошедшие полвека. В процессе чтения книги хорошо прослеживается линия развития индустрии разработки ПО, типичные проблемы, с которыми пацанам приходилось сталкиваться, и способы их решения.
Не подумай, пересказ краткого содержания книги не является целью этого поста.
Далее речь пойдёт о идеях, подтолкнувших меня к разработке одного инструмента (степень его полезности определять вам, а я с огромным интересом буду ждать отзывы, предложения и конструктивную критику).
Мне кажется, статья будет полезна всем. Те, кто с книгой уже знаком, смогут освежить в своей памяти некоторые ее моменты, или просто пропустить первую часть и сразу приступить к знакомству с инструментом. Те, кто ранее книгу не читал, возможно, и в первой части найдут для себя что-то новое.
Для удобства статья поделена на 2 части.
В первой я расскажу о ключевых идеях, породивших в моей голове желание всё это дело автоматизировать, программисты мы или кто в конце-то концов? Тут я буду много цитировать автора, а также активно вставлять выжимки и иллюстрации из разных глав книги.
Вторая часть, почти полностью, будет посвящена описанию появившегося инструмента, раскрытию его возможностей и пошаговому руководству.
Если же ты относишь себя к категории людей, принимающих всё слишком близко к сердцу, советую начать чтение с Послесловия.
Содержание
- Часть первая
- Что же такое архитектура ПО?
- Какова цель архитектуры?
- С чего начинаются многие проекты?
- Две ценности программных продуктов
- Зачем придумали SOLID и почему одного его не достаточно?
- Связность компонентов и принципы её определяющие
- Сочетаемость компонентов и принципы её определяющие
- ADP: Acyclic Dependencies Principle
- SDP: Stable Dependencies Principle
- SAP: Stable Abstractions Principle
- Зона боли
- Зона бесполезности
- Как не попасть в зоны исключения?
- Расстояние до главной последовательности
- ADP: Acyclic Dependencies Principle
- Заключение автора (Р. Мартина)
- Послесловие
- Часть вторая
- Вступление
- Краткое содержание первой части
- Инструмент и тестовый проект. Введение в тему.
- Подключаем PHP Clean Architecture
- Создаём и настраиваем конфигурационный файл
- Визуализация для анализа
- 1. Общая информация о системе
- Тот самый A/I график, отображающий рассеянность компонентов
- График удаленности компонентов от главной последовательности
- И график взаимосвязей между компонентами
- 2. Детальная информация о компоненте
- 3. Детальная информация об элементе компонента
- Варианты конфигурации
- Ограничение общего максимально-допустимого расстояния до главной последовательности
- Ограничение максимально-допустимого D для конкретного модуля
- Контроль зависимостей компонента
- Public/Private Class в PHP
- Контроль нарушений принципов ADP и SDP
- 1. Общая информация о системе
- Автоматический контроль в CI/CD
- Послесловие
Часть 1
Что же такое архитектура ПО?
Увы, единого определения автор книги не даёт. Существует большое количество разных формулировок, и все они по-своему верны.
Однако мы можем зайти с другой стороны..
Какова цель архитектуры?
Здесь ответ один:
Цель архитектуры ПО — уменьшить человеческие трудозатраты на создание и сопровождение системы.
Немного вводной..
С чего начинаются многие проекты?
«Мы сможем навести порядок потом, нам бы только выйти на рынок!»
В результате порядок так и не наводится, потому что давление конкуренции на рынке никогда не ослабевает.
«Самая большая ложь, в которую верят многие разработчики, – что грязный код поможет им быстро выйти на рынок, но в действительности он затормозит их движение в долгосрочной перспективе. Разработчики, уверовавшие в эту ложь, проявляют самонадеянность Зайца, полагая, что в будущем смогут перейти от создания беспорядка к наведению порядка, но они допускают простую ошибку. Дело в том, что создание беспорядка всегда оказывается медленнее, чем неуклонное соблюдение чистоты, независимо от выбранного масштаба времени».
Две ценности программных продуктов
- Поведение (нечто срочное, но не всегда важное)
- Архитектура (нечто важное, но не всегда срочное)
Многие зачастую с головой утопают в первой, и полностью пренебрегают второй.
Типа: “Зачем тратить время на основательное продумывание архитектуры, главное ведь чтоб работало правильно!”.
Но они упускают один важный момент: «Если правильно работающая программа не допускает возможности ее изменения, она перестанет работать правильно, когда изменятся требования, и они не смогут заставить ее работать правильно. То есть программа станет бесполезной.
Если же программа работает неправильно, но легко поддается изменению, они смогут заставить работать ее правильно и поддерживать ее работоспособность по мере изменения требований. То есть программа постоянно будет оставаться полезной.»
Мне понравилось такое высказывание: “Если вы думаете, что хорошая архитектура стоит дорого, попробуйте плохую архитектуру.” (Брайан Фут и Джозеф Йодер)
Выходит важно уделять должное внимание чистоте кода и архитектуре в целом с самых первых строк. Но, если с чистотой кода всё относительно понятно (вынюхивай себе всякие плохие запахи в коде, да исправляй, к тому ещё и инструментов полным-полно умные IDE, статические анализаторы и прочая несомненно важная дребедень), то с архитектурой не совсем так (по крайней мере мне).
Как можно контролировать то, что настолько расплывчато, что с трудом поддаётся даже определению единой формулировкой, что имеет весьма разностороннее представление и каждый второй, блин, говнюк это видит и норовит трактовать по своему, если ты не внук Ванги, не выигрывал в шоу “Интуиция” и не отличаешься особой чувствительностью пятой точки? Тут автор книги подкидывает парочку ценных мыслей для размышления… Давай разбираться.
Зачем придумали SOLID и почему одного его не достаточно?
Все мы наверняка знаем о принципах SOLID.
Их цель – создать программные структуры среднего уровня, которые:
- терпимы к изменениям;
- просты и понятны;
- образуют основу для компонентов, которые могут использоваться во многих программных системах.
Но: «Как из хороших кирпичей можно сложить никуда не годную стену, так и из хорошо продуманных компонентов среднего уровня можно создать никуда не годную систему».
Поэтому в мире компонентов есть аналоги принципов SOLID, а также есть высокоуровневые принципы создания архитектур.
Я этих тем коснусь очень поверхностно, подробнее можно ознакомиться в книге. Погнали!
Связность компонентов и принципы её определяющие
«К какому компоненту отнести тот или иной класс? Это важное решение должно приниматься в соответствии с зарекомендовавшими себя принципами разработки программного обеспечения. К сожалению, подобные решения носят особый характер и принимаются почти исключительно исходя из контекста»
REP: Reuse/Release Equivalence Principle – Принцип эквивалентности повторного использования и выпусков.
С точки зрения архитектуры и дизайна этот принцип означает, что классы и модули, составляющие компонент, должны принадлежать связной группе. Компонент не может просто включать случайную смесь классов и модулей; должна быть какая-то тема или цель, общая для всех модулей.
Классы и модули, объединяемые в компонент, должны выпускаться вместе.
CCP: Common Closure Principle – принцип согласованного изменения.
Это принцип SRP (single responsibility) перефразированный для компонентов.
В один компонент должны включаться классы, изменяющиеся по одним и тем же причинам и в одно и то же время. В разные компоненты должны включаться классы, изменяющиеся в разное время и по разным причинам.
CRP: Common Reuse Principle – принцип совместного повторного использования.
Этот принцип имеет схожесть с ISP (interface segregation).
Не вынуждайте пользователей компонента зависеть от того, чего им не требуется.
Принцип указывает, что классы не имеющие тесной связи, не должны включаться в один компонент.
«Возможно, вы уже заметили, что три принципа связности компонентов вступают в противоречие друг с другом. Принципы эквивалентности повторного использования (REP) и согласованного изменения (CCP) являются включительными: оба стремятся сделать компоненты как можно крупнее. Принцип повторного использования (CRP) – исключительный, стремящийся сделать компоненты как можно мельче. Задача хорошего архитектора – разрешить это противоречие».
![image](https://habrastorage.org/webt/to/sk/yl/toskylulwgshxendjozwzjf9sly.png)
«В прошлом мы смотрели на связность проще, чем предполагают принципы эквивалентности повторного использования (REP), согласованного изменения (CCP) и совместного повторного использования (CRP). Когда-то мы думали, что связность – это просто атрибут, что модуль выполняет одну и только одну функцию. Однако три принципа связности компонентов описывают намного более сложное многообразие. Выбирая классы для включения в компоненты, нужно учитывать противодействующие силы, связанные с удобством повторного использования и разработки. Поиск баланса этих сил, исходя из потребностей приложения, – непростая задача. Кроме того, баланс практически всегда постоянно смещается. То есть разбиение, считающееся удачным сегодня, может оказаться неудачным через год. Как следствие, состав компонентов почти наверняка будет изменяться с течением времени и смещением фокуса проекта с удобства разработки к удобству повторного использования».
Сочетаемость компонентов
«Следующие три принципа определяют правила взаимоотношений между компонентами. И снова мы сталкиваемся с противоречиями между удобством разработки и логической организацией. Силы, воздействующие на архитектуру структуры компонентов, носят технический, политический и непостоянный характер».
ADP: Acyclic Dependencies Principle — принцип ацикличности зависимостей.
Циклы в графе зависимостей компонентов недопустимы!
![image](https://habrastorage.org/webt/r0/od/sf/r0odsfch_htpxpurtonelpiy-em.png)
Из какого бы компонента, отображенного на графе, вы не начали движение по стрелкам, указывающим направление зависимости, у вас не получится вернуться в первоначальную точку. Это ациклический ориентированный граф (DAG — Directed Acyclic Graph).
![image](https://habrastorage.org/webt/ib/wg/d8/ibwgd8xnihw0qilpi7i32rvaxkk.png)
На этом изображении у нас появляется зависимость, приводящая к появлению цикла. Образовавшуюся циклическую зависимость всегда можно разорвать.
Способы разрыва циклических зависимостей
1. Применив DIP (принцип инверсии зависимостей)
![image](https://habrastorage.org/webt/oy/dp/8b/oydp8bixvzlwccqadsmdnrqqtdi.png)
2. Введением нового компонента
![image](https://habrastorage.org/webt/2e/51/ty/2e51tyj9bfrfimyiqxgzrqqc6ds.png)
«Второе решение предполагает зависимость структуры компонентов от изменения требований. И действительно, с ростом приложения структура зависимостей компонентов растет и изменяется. Поэтому ее постоянно нужно проверять на предмет появления циклов. Когда образуются циклы, их нужно разрывать тем или иным способом. Иногда для этого приходится создавать новые компоненты, что заставляет разрастаться структуру зависимостей».
Проектирование сверху вниз, а точнее его неосуществимость
«Структура компонентов не может проектироваться сверху вниз. К этому выводу приходят не сразу, как только начинают проектировать систему, но это неизбежно случается с ростом и изменением системы.
Рассматривая крупноблочную диаграмму, такую как структура зависимостей компонентов, мы полагаем, что компоненты должны каким-то образом представлять функции системы. Но в действительности это не является непременным атрибутом диаграмм зависимостей компонентов.
Фактически диаграммы зависимостей компонентов слабо отражают функции приложения. В большей степени они являются отражением удобства сборки и сопровождения приложения. В этом главная причина, почему они не проектируются в начале разработки проекта. В этот период нет программного обеспечения, которое требуется собирать и сопровождать, поэтому нет необходимости составлять карту сборки и сопровождения. Но с появлением все большего числа модулей на ранних стадиях реализации и проектирования возрастает потребность управлять зависимостями… Кроме того, появляется желание максимально ограничить влияние изменений, поэтому мы начинаем обращать внимание на принципы единственной ответственности (SRP) и согласованного изменения (CCP) и объединять классы, которые наверняка будут изменяться вместе.
Одной из главных задач такой структуры зависимостей является изоляция изменчивости. Нам не нужны компоненты, часто изменяющиеся по самым мелким причинам и влияющие на другие компоненты, которые иначе были бы вполне стабильными. Например, косметические изменения в графическом интерфейсе не должны влиять на бизнес-правила. Добавление и изменение отчетов не должно влиять на высокоуровневые политики. Следовательно, граф зависимостей компонентов создается и формируется архитекторами для защиты стабильных и ценных компонентов от влияния изменчивых компонентов.
По мере развития приложения мы начинаем беспокоиться о создании элементов многократного пользования. На этом этапе на состав компонентов начинает влиять принцип совместного повторного использования (CRP). Наконец, с появлением циклов мы начинаем применять принцип ацикличности зависимостей (ADP), в результате начинает изменяться и разрастаться граф зависимостей компонентов.
Попытка спроектировать структуру зависимостей компонентов раньше любых классов, скорее всего, потерпит неудачу. На этом этапе мы почти ничего не знаем о согласовании изменений, не представляем, какие элементы можно использовать многократно и почти наверняка создадим компоненты, образующие циклические зависимости. Поэтому структура зависимостей компонентов должна расти и развиваться вместе с логическим дизайном системы»
Эй, ты еще здесь? Чел, это рил важно, с этим нужно хотя бы поздороваться, чтоб въехать в смысл следующей части, там будет сырой туториал по использованию тулзы.
SDP: Stable Dependencies Principle — принцип устойчивых зависимостей.
Зависимости должны быть направлены в сторону устойчивости!
«Что подразумевается под “устойчивостью”? Представьте себе монету, стоящую на ребре. Является ли такое ее положение устойчивым? Скорее всего, вы ответите “нет”. Однако если оградить ее от вибраций и дуновений ветра, она может оставаться в таком положении сколь угодно долго. То есть устойчивость напрямую не связана с частотой изменений. Монета не изменяется, но едва ли кто-то скажет, что, стоя на ребре, она находится в устойчивом положении.
В толковом словаре говорится, что устойчивость – это “способность сохранять свое состояние при внешних воздействиях”. Устойчивость связана с количеством работы, которую требуется проделать, чтобы изменить состояние. С одной стороны, монета, стоящая на ребре, находится в неустойчивом состоянии, потому что требуется приложить крошечное усилие, чтобы опрокинуть ее. С другой стороны, стол находится в очень устойчивом состоянии, потому что для его опрокидывания требуются намного более существенные усилия.
Какое отношение все это имеет к программному обеспечению? Существует множество факторов, усложняющих изменение компонента, например его размер, сложность и ясность. Но мы оставим в стороне все эти факторы и сосредоточим внимание на кое-чем другом. Есть один верный способ сделать программный компонент сложным для изменения – создать много других компонентов, зависящих от него. Компонент с множеством входящих зависимостей очень устойчив, потому что согласование изменений со всеми зависящими компонентами требует значительных усилий».
![image](https://habrastorage.org/webt/rp/pr/x8/rpprx8mwmw7dhsnchmfpiybhy_q.png)
![image](https://habrastorage.org/webt/sk/1b/gz/sk1bgzciqhgtwbbf32tnhhp6nig.png)
Как же оценить устойчивость компонента? Автор предлагает подсчитать количество его входящих и исходящих зависимостей и на их основе вычислить меру его устойчивости.
- Fan-in (число входов): количество входящих зависимостей. Эта метрика определяет количество классов вне данного компонента, которые зависят от классов внутри компонента.
- Fan-out (число выходов): количество исходящих зависимостей. Эта метрика определяет количество классов внутри данного компонента, зависящих от классов за его пределами.
- I: неустойчивость: I = Fan-out ÷ (Fan-in + Fan-out). Значение этой метрики изменяется в диапазоне [0, 1]. I = 0 соответствует максимальной устойчивости компонента, I = 1 – максимальной неустойчивости.
Так вот, этот принцип говорит что метрика I компонента должна быть меньше метрик I компонентов, которые от него зависят. То есть метрики I должны уменьшаться в направлении зависимости.
Важно отметить, если все компоненты системы будут максимально устойчивыми, такую систему невозможно будет изменять. Следовательно, не все компоненты должны быть устойчивыми.
Дружище, не засыпай, я рядом. Осталось совсем немного!
SAP: Stable Abstractions Principle — принцип устойчивости абстракций. Устойчивость компонента пропорциональна его абстрактности.
«Некоторые части программных систем должны меняться очень редко. Эти части представляют высокоуровневые архитектурные и другие важные решения. Никто не желает, чтобы такие решения были изменчивыми. Поэтому программное обеспечение, инкапсулирующее высокоуровневые правила, должно находиться в устойчивых компонентах (I = 0). Неустойчивые (I = 1) должны содержать только изменчивый код – код, который можно было бы легко и быстро изменить.
Но если высокоуровневые правила поместить в устойчивые компоненты, это усложнит изменение исходного кода, реализующего их. Это может сделать всю архитектуру негибкой. Как компонент с максимальной устойчивостью (I = 0) сделать гибким настолько, чтобы он сохранял устойчивость при изменениях? Ответ заключается в соблюдении принципа открытости/закрытости (OCP). Этот принцип говорит, что можно и нужно создавать классы, достаточно гибкие, чтобы их можно было наследовать (расширять) без изменения. Какие классы соответствуют этому принципу? Абстрактные.
SAP (принцип устойчивости абстракций) устанавливает связь между устойчивостью и абстрактностью. С одной стороны, он говорит, что устойчивый компонент также должен быть абстрактным, чтобы его устойчивость не препятствовала расширению, с другой – он говорит, что неустойчивый компонент должен быть конкретным, потому что неустойчивость позволяет легко изменять его код.
То есть стабильный компонент должен состоять из интерфейсов и абстрактных классов, чтобы его легко было расширять. Устойчивые компоненты, доступные для расширения, обладают достаточной гибкостью, чтобы не накладывать чрезмерные ограничения на архитектуру.
Принципы устойчивости абстракций (SAP) и устойчивых зависимостей (SDP) вместе соответствуют принципу инверсии зависимостей (DIP) для компонентов. Это верно, потому что принцип SDP требует, чтобы зависимости были направлены в сторону устойчивости, а принцип SAP утверждает, что устойчивость подразумевает абстрактность. То есть зависимости должны быть направлены в сторону абстрактности.
Однако принцип DIP сформулирован для классов, и в случае с классами нет никаких полутонов. Класс либо абстрактный, либо нет. Принципы SDP и SAP действуют в отношении компонентов и допускают ситуацию, когда компонент частично абстрактный или частично устойчивый».
Как же измерить абстрактность компонента? Тут тоже всё очень просто.
Значение меры абстрактности компонента определяется отношением количества его интерфейсов и абстрактных классов к общему числу классов.
- Nc: число классов в компоненте.
- Na: число абстрактных классов и интерфейсов в компоненте.
- A: абстрактность. A = Na ÷ Nc.
Значение метрики A изменяется в диапазоне от 0 до 1. 0 означает полное отсутствие абстрактных элементов в компоненте, а 1 означает, что компонент не содержит ничего, кроме абстрактных классов и интерфейсов.
Зависимость между I и A
Если отобразить на графике (Y — абстрактность, X — неустойчивость) “хорошие” компоненты обоих видов: абстрактные устойчивые и конкретные неустойчивые, получим вот что:
![image](https://habrastorage.org/webt/o9/ti/uu/o9tiuuubqfxevrke6rmxoi8xvt4.png)
Но, т.к. компоненты имеют разные степени абстрактности и устойчивости, далеко не все попадут в эти 2 точки (0, 1) или (1, 0), и так как нельзя потребовать, чтобы все компоненты находились в них, мы должны предположить, что на графике A/I имеется некоторое множество точек, определяющих оптимальные позиции для компонентов.
Вывести это множество можно, определив области, где компоненты не должны находиться, – иными словами, определив зоны исключения.
![image](https://habrastorage.org/webt/1d/q0/6u/1dq06uddhrif323kzrg2ymdlgwa.png)
Зона боли
Рассмотрим компоненты, располагающиеся вблизи точки (0, 0).
Они очень устойчивы и конкретны. Такие компоненты нежелательны, т.к. являются слишком жесткими. Их нельзя расширить, потому что они не абстрактные и очень трудно изменить из-за большой устойчивости. Чем более изменяем компонент, попадающий в зону боли, тем больше боли он приносит. Связано это со сложностью его изменения.
Правильно спроектированные компоненты обычно не должны находиться вблизи с точкой (0, 0).
Однако в действительности существуют программные сущности, попадающие в эту зону. К примеру схема базы данных, она максимально конкретна и к ней тянется множество зависимостей. Вот одна из причин, почему изменения схемы БД обычно связано с большой болью.
Зона бесполезности
Рассмотрим компоненты, располагающиеся вблизи точки (1, 1).
Такие компоненты тоже нежелательны, т.к. они максимально абстрактны и не имеют входящих зависимостей. Они попросту бесполезны. Зачастую это абстрактные классы, которые так и не были реализованы.
Как не попасть в зоны исключения?
Расположить компоненты поблизости с главной последовательностью, или на ней. Такие компоненты не “слишком абстрактны” для своей устойчивости и не “слишком неустойчивы” для своей абстрактности. Они не бесполезны и не доставляют особенной боли. От них зависят другие компоненты в меру их абстрактности, и сами они зависят от других в меру конкретности.
Самыми желательными позициями для компонентов являются конечные точки главной последовательности. Однако в больших системах всегда найдутся несколько компонентов недостаточно абстрактных и недостаточно устойчивых. Такие компоненты обладают великолепными характеристиками, когда располагаются на или вблизи главной последовательности.
Расстояние до главной последовательности
Коль скоро желательно, чтобы компонент располагался на или вблизи главной последовательности, можно определить метрику, выражающую удаленность компонента от идеала.
- D: расстояние. D = |A+I–1|. Эта метрика принимает значения из диапазона [0, 1]. Значение 0 указывает, что компонент находится прямо на главной последовательности. Значение 1 сообщает, что компонент располагается на максимальном удалении от главной последовательности.
Взяв на вооружение эту метрику, можно исследовать весь дизайн на близость к главной последовательности. Метрику D можно вычислить для любого компонента. Любой компонент со значением метрики D, далеким от нуля, требует пересмотра и реструктуризации.
![image](https://habrastorage.org/webt/gw/3n/a0/gw3na07fmc40y8-r72hoqro8wrq.png)
Заключение автора (Роберта Мартина)
«Метрики управления зависимостями, описанные в этой главе, помогают получить количественную оценку соответствия структуры зависимостей и абстракции дизайна тому, что я называю “хорошим” дизайном. Как показывает опыт, есть хорошие зависимости, есть плохие. Данная оценка отражает этот опыт. Однако метрики не являются истиной в последней инстанции; это всего лишь измерения на основе произвольного стандарта. Эти метрики несовершенны – в лучшем случае, но я надеюсь, что вы найдете их полезными».
Приятель, я рад если ты до сих пор со мной. На этом с первой частью пожалуй всё. Хоть и еще очень много тем, и развернутых мыслей автора остались не затронутыми. Всё-таки главная цель моей статьи заключается не в пересказе книги, иначе тебе однозначно приятней было бы прочитать оригинал. Я, походу, и так уже конкретно перегнул с объемом содержания, но падлой буду, старался не раздувать как мог. Раунд!
День второй. Эмм, т.е. Часть 2
Вступление
О чем я там тёр так долго? — А, ну да… Мы типа архитекторы, ну или очень хотим быть ими, верно?
Чё у нас имеется? — Какой-то жалкий проектик, а может монструозная система, ПРОЕКТИЩЕ, мать его, ну или ваще нифига нет еще, а мы только ладошки потираем, зырим в календарь и прикидываем, как знаатно ща придётся поговнокодить.
Нет никаких нас, я работаю один! А может быть мы с корешем напару, в гараже, собрались накодить новый Apple, почему бы и нет? Ну или нас рать и все мы часть здоровенной компании, вкалываем изо всех, как домашние негры бедолаги (т.е. афроамериканцы конечно, ну ты понял), а может пинаем потихой, так чтобы только не уволили. За жирный оклад с премией, печеньками и видом из москва-сити или на голом энтузиазме, за идею и долю в стартапе, который естественно хрен когда взлетит.
На самом деле это неважно. Важно вот чё: все мы по итогу просто хотим, чтоб наше детище не загнулось преждевременно, ну хотя бы раньше нашего ухода, а ведь на то есть все шансы. Да-да…
И чё теперь делать, спросишь ты? Так вот, если у тебя в конторе нет большого брата, тут братан без третьего глаза не обойтись.
Краткое содержание первой части
В прошлой части статьи мы чутка поговорили о метриках, которые дают возможность архитекторам
А — абстрактность компонента
I — неустойчивость компонента
D — расстояние до главной последовательности на графике A/I
Выявили
Пришли к выводу, что в идеале A должно увеличиваться и уменьшаться пропорционально уменьшению и соответственно увеличению I. Хорошо спроектированные компоненты должны находиться на или хотя бы вблизи с главной последовательностью, т.е. D должно быть как можно ближе к нулю, а зависимости между компонентами должны быть ацикличны и направлены в сторону устойчивости.
Еще надеюсь каждому понятно, что одни компоненты могут знать про многие другие, а другие не должны знать ничего. Прям как в рабовладельческих штатах до прихода доброго дяди Авраама, только наоборот, там неграм (в смысле афроамериканцам) нихрена не было позволено (в плане знаний, ну и не только), в нашем же случае минимумом знаний должны обладать самые важные компоненты системы, они должны быть самодостаточными и абстрагированными от разных деталей.
Так что там про третий глаз-то? PHP Clean Architecture — инструмент появившийся в результате прочтения книги, спасибо дядюшке Бобу (Роберту Мартину) за ценные мысли, и бэшшаного желания всё это дело визуализировать, для удобства проведения анализа, и автоматизировать, для возможности включения различных проверок в CI/CD (на подобие PHPStan, Phan и др., только для выявления именно архитектурных проблем).
Инструмент и тестовый проект. Введение в тему
Фуф, с теорией вроде всё… Теперь можно и
А для того, чтоб мне сейчас не пришлось про абстрактных коней в вакууме втирать, и у тебя к концу чтения было меньше оснований обозвать меня мудилой, из-за которого ты
Накодил я его примерно за пол дня, грубо говоря, на коленке у подружки. Не могу ведь я взять и слить широкой публике детали боевых проектов, с которыми мне приходится работать… Пацаны на раёне не поймут, предъявят, а оно мне надо? Ну, а если ты простой хлопец, не из числа “особо выдающихся”, тогда сорян за такое долгое вступление, мне просто нужно было отвести риски и снизить потенциальную угрозу разведения в комментариях базаров ни о чём.
Так вот, сам проект лежит здесь github.com/Chetkov/php-clean-architecture-example-project.
В перспективе он мог бы стать полноценным интернет-магазином. Я конечно лютый перфекционист, но нужно быть
Хотя всё не так плохо. В данный момент в нём есть: пользователь, продукт и заказ, а также сценарии: регистрация пользователя, смена пароля, добавление продукта и бронирование заказа.
Проект мало-мальски рабочий, ты можешь клонировать его себе и позапускать скрипты из папки sandbox, хотя на самом деле, я мог бы даже этого не делать, т.к. для анализа архитектуры работоспособность от проекта не требуется.
В дальнейшем, я буду подкидывать ссылки на конкретные комиты, чтоб сразу фиксировать технические моменты, описанные в статье.
Первоначальное состояние
Предполагаю, что со структурой ты уже успел познакомиться. Значит можем двигать дальше.
Подключаем PHP Clean Architecture:
composer require v.chetkov/php-clean-architecture:0.0.3
Создаём и настраиваем конфигурационный файл
Визуализация для анализа
Выполняем в консоли (в корне проекта):
vendor/bin/phpca-build-reports
или vendor/bin/phpca-build-reports /path/to/phpca-config.php
И в ответ получаем:
Report: /path/to/reports-dir/index.html
Отчет состоит из 3-х экранов:
1. Общая информация о системе
![image](https://habrastorage.org/webt/pu/fx/qr/pufxqrtzh84_faltmuyavha60na.png)
На этом экране отображается:
Тот самый A/I график, отображающий рассеянность компонентов:
![image](https://habrastorage.org/webt/om/ef/ba/omefbattpqlmig8toha6fkaiebo.png)
График, отображающий удалённость компонентов от главной последовательности:
![image](https://habrastorage.org/webt/1o/wc/c4/1owcc49v0-3xpt2ty6tlake7iu4.png)
И график, отображающий взаимосвязи между компонентами:
![image](https://habrastorage.org/webt/i-/g5/gu/i-g5guwpecojz4hflw0v52pfsau.png)
Стрелки указывают направление зависимостей, а цифры на них — силу связи.
Т.к. современные IDE предоставляют лёгкий способ рефакторинга (сами находят и исправляют все участки, где явно вызывается метод или класс, допустим при переименовании или переносе в другой namespace), за силу связи я принял количество элементов модуля зависимости, используемых зависимым модулем.
К примеру: элементы модуля services знают про 12 элементов модуля model, а элементы infrastructure знают про 1 элемент services. И т.д.
Кстати, а что за *undefined*? Бро, вполне ожидаемый вопрос. Это модуль, создаваемый автоматически для всех элементов, которые в процессе анализа системы не удалось отнести к какому-либо из известных (т.е. перечисленных в конфигурационном файле).
Если же ты поправишь конфиг таким образом:
![image](https://habrastorage.org/webt/cm/2i/e7/cm2ie7i06bwj3ygr6qur5hwsrqq.png)
То в процессе анализа системы, будут учитываться еще и подключенные через composer пакеты (сами они сканированию не подвергнутся, но картинка станет куда более полной).
Кстати, в vendor_path необходимо указать путь к директории vendor, а в excluded пути к директориям пакетов, которые мы хотим исключить (к примеру если мы какой-то пакет описываем в основном конфиге модулей для сканирования).
После включения vendor_based_modules граф изменился, хотя конфиг самих модулей мы не меняли
![image](https://habrastorage.org/webt/-k/ll/vn/-kllvnfj6fdxmxzofi3wnya1gkg.png)
2. Детальная информация о компоненте
![image](https://habrastorage.org/webt/t2/ha/tp/t2hatp-gycfkeb2ytwctj4gdgvs.png)
Здесь мы также видим:
- Граф взаимосвязей, но уже не всей системы, а только участвующих компонентов
- A/I график, на котором отражен только текущий компонент
- Основные характеристики модуля
а также
- График исходящих зависимостей, отображающий количество и список файлов в текущем модуле, зависящих от файлов других модулей.
- И график входящих зависимостей, отображающий количество и список файлов в других модулях, зависящих от текущего модуля.
Последние 2 графика, на мой взгляд, особенно интересны, т.к. по ним легко находить нежелательные зависимости, но об этом чуть позже.
3. Детальная информация об элементе компонента
![image](https://habrastorage.org/webt/hl/mn/am/hlmnamudgfqzimh7ec9qscm56pc.png)
Здесь видно:
- Основные характеристики элемента, такие как: тип, абстрактность, неустойчивость, примитивность, а также к какому компоненту принадлежит и модификатор доступа.
“Чё, модификатор доступа у класса в PHP?” — говоришь ты, просверливая пальцем дырку в районе виска, так ведь? Не спеши, я знаю что в пыхе нет никаких приватных классов, но круто ведь было бы, если бы они были? В общем, об этом тоже чуть позже.
- Граф зависимостей, на котором отражено взаимодействие рассматриваемого элемента с другими элементами системы
- И таблички с исходящими и входящими зависимостями
С описанием экранов закончили, го конфиги ковырять и зырить, как это можно настраивать.
Варианты конфигурации
Ограничение максимально-допустимого расстояния до главной последовательности (для всех компонентов)
Для начала давай попробуем установить всем компонентам максимально допустимое расстояние до главной последовательности. Глупо будет думать, что все они лягут на главную диагональ, мир ведь не идеален, поэтому давай будем реалистами. Делаю 0.3
![image](https://habrastorage.org/webt/ky/cg/l7/kycgl7xitjnerrvsaw-duklidfy.png)
Перезапускаем команду генерации отчета и сразу на главной странице видим нарушение. Компонент model превышает допустимое расстояние на 0.192
![image](https://habrastorage.org/webt/nq/1l/nk/nq1lnkttwkz2ukpybccin99sarg.png)
Ограничение максимально-допустимого расстояние до главной последовательности (конкретному компоненту)
Замечательно, но давай предположим, что именно компоненту model можно нарушать это ограничение. Разное бывает… Мы ведь все зачастую уже имеем не идеальные системы, с которыми приходится работать, поддерживать их и улучшать. Любому понятно, что быстро сократить это расстояние ни у кого не получится, даже наверное у боженьки (надеюсь он меня простит).
На мой взгляд, главное зафиксировать текущее состояние, работать в сторону улучшений и контролировать ситуацию, чтобы не усугубить.
Снова идём в конфиг, и устанавливаем max_allowable_distance, только уже для конкретного модуля. Фиксирую на его текущем значении 0.492
![image](https://habrastorage.org/webt/cz/nh/3y/cznh3ykeuoalp65gflqjxg0tlag.png)
Збс, теперь снова всё норм.
![image](https://habrastorage.org/webt/-z/pq/2u/-zpq2um__xoxqtdyqy2sw81xa0m.png)
Но если, какой-то с**ка Вася, снова чё-то запушит и станет хуже —
Контроль зависимостей компонентов
Что еще было бы неплохо иметь в нашем арсенале для борьбы с плохой архитектурой? Конечно же контроль над зависимостями. Кто и про кого может знать, или наоборот знать не должен.
Как думаешь, может ли самая центральная часть системы, так скажем domain, ну чтоб прям ваще по DDDовски, что-то знать про какую-то
Конечно же ДА, у некоторых это, прям, атрибут успеха (ну, я про тех кто HTML вперемешку с бизнес-логикой рендерит). Но ваще, как бэ, не должен… Я конечно хз, но всякие-разные умные дядьки в книжках поговаривают, что это не по пацански.
Тогда давай замутим так… Всё там же, в конфиге ограничений для model.
![image](https://habrastorage.org/webt/t9/d-/vq/t9d-vqmz0wzxd9axkfmlise2e1y.png)
Хм… Пока всё норм, т.к. model про infrastructure действительно ничего и не знает…
Ну давай тогда приколемся… Тупость конечно редкостная, но ничего лучше я не придумал. Крч, в момент создания пользователя будем «слать» смс, а в момент создания заказа «отправлять» email(ы). Причем делать это через конкретные реализации уведомителей, которые лежат в infrastructure.
github.com/Chetkov/php-clean-architecture-example-project/commit/6bad365176b30999035967319a9c07fae69fabf9
Теперь зырим в отчет… Как мы видим, графы на разных страницах отчета, подсвечивают эту связь как нелегитимную.
Главная.
![image](https://habrastorage.org/webt/k1/ti/e_/k1tie_5t4puunp-vo9cemwfgcu8.png)
Отчет по компоненту.
![image](https://habrastorage.org/webt/xw/r1/qv/xwr1qvfggdbhblauxmfekjbfnfs.png)
На графиках “исходящих зависимостей” model и “входящих зависимостей” infrastructure сразу видим элементы, нарушающие введенное правило
![image](https://habrastorage.org/webt/yx/lq/fm/yxlqfmhtpyx81pdmjjjggzered0.png)
В таблицах “исходящих зависимостей” элементов model и “входящих зависимостей” элементов infrastructure, аналогично.
PHPCAEP\Model\User\User
![image](https://habrastorage.org/webt/_k/ka/du/_kkadurra4ab9onv9kazkypmr8s.png)
PHPCAEP\Infrastructure\Notification\Sms\SmsNotifier
![image](https://habrastorage.org/webt/vw/ho/es/vwhoestwzf3m41hr5sbux7n5shs.png)
А также на графе взаимосвязей элементов. Я думаю в дальнейшем вообще от него избавляться, т.к. он мало информативен (особенно для активно использующихся классов, из-за длины названия узлов + огромного их количества)
![image](https://habrastorage.org/webt/tn/10/wn/tn10wnmnrwjup2vvdfzzghfhwyw.png)
Аналогичные действия можно проделать и с конфигом allowed_dependencies.
![image](https://habrastorage.org/webt/lh/8t/ki/lh8tkinfuyuzmfe-f4i2giq2p54.png)
Результат будет полностью противоположный.
Можно компоновать конфиг указывая для модуля и forbidden, и allowed (если будет желание, подробнее обсудим в коментах). Указывать один компонент и в forbidden, и в allowed пока нельзя… Были мысли ввести конфиг для указания приоритета, но пока этого не сделал и не уверен что буду делать. Всё зависит в том числе и от твоей обратной связи. Может вообще впустую это всё?
Public/Private Class в PHP
Окей, научились значит круг знаний компонентам сужать и контролировать. Уже не плохо. Чё дальше?
А дальше еще одна проблема. Враг то не дремлет, хотя в большинстве случаев этот враг, это мы сами и есть, или наши кореша по офису.
Бывает ведь такое, ты с девочкой познакомился, пообщался, узнал про неё что-то, поухаживал даже может быть, но вот блин, не складывается у вас ничего. Мы друзья и всё такое… Она может чмокнуть тебя при встрече, может выслушать и поддержать добрым словом, но
Зато в коде, постоянно… Подключишь новый пакет, дня не прошло, а его уже используют, да прям
Ты: “Всё, хорош, растрынделся. Как решать проблему?”
Я: “Оставлять только дозволенные для взаимодействия классы, а к остальным ограничивать доступ.”
Ты: “Но в PHP ведь нет модификаторов доступа у классов.”
Я: “Согласен, зато в PHP Clean Architecture есть.”
Пример сейчас будет снова из наркоманских, но что поделать.
В реальных проектах, конечно же, кейсов полным полно, но за слив инфы так-то могут бутылку принести и попросить присесть. По этому приходится сочинять.
Давай предположим, что в model, по какой-то причине, только 2 класса, о которых другим разрешено знать… User и Order, все остальные должны быть скрыты.
![image](https://habrastorage.org/webt/qy/e9/4x/qye94xgk0qilcsun6t2b7xcyjfa.png)
Что мы имеем теперь?
Граф на главной.
![image](https://habrastorage.org/webt/5p/jh/89/5pjh89hoarwanv9cg5s4bnxftqa.png)
Сразу видно, какие компоненты требуют пересмотра.
Лезем смотреть, что не так с infrastructure.
![image](https://habrastorage.org/webt/6e/os/a7/6eosa7uzvggaixprcwujm6zo7iw.png)
На графике “исходящих зависимостей” видим элементы, нарушающие правило:
![image](https://habrastorage.org/webt/ce/cv/kh/cecvkhbffs9w0zoedu5h19-bqde.png)
На странице информации об элементе, аналогично.
![image](https://habrastorage.org/webt/7b/08/-x/7b08-x0k64pug5ab97xzjeweggc.png)
Ровно противоположный результат мы получим, если в конфиге укажем private_elements.
![image](https://habrastorage.org/webt/ea/bi/z7/eabiz7uuzgdvoer2hlcgrbquya4.png)
К примеру запрещаем всем знать про Fio.
Теперь на главной:
![image](https://habrastorage.org/webt/1w/be/xw/1wbexwzh6qyeud-klfzf6ixd4uu.png)
На странице информации о компоненте services:
![image](https://habrastorage.org/webt/rq/k4/qt/rqk4qtsjvdt320d0nhm9mpgmv9c.png)
И на странице информации об элементе PHPCAEP\Model\User\Fio:
![image](https://habrastorage.org/webt/yp/z1/fb/ypz1fbiu9tvqqawxcyxe7lj1rbk.png)
Контроль нарушений принципов ADP и SDP
Еще, пожалуй, нужно подсветить возможность включения проверок:
- на наличие нарушений принципа ADP, т.е. на существование циклических зависимостей в графе (check_acyclic_dependencies_principle)
- и на наличие нарушений принципа SDP, т.е. на наличие зависимостей направленных из более устойчивых модулей в менее устойчивые (check_stable_dependencies_principle)
![image](https://habrastorage.org/webt/wv/q-/4e/wvq-4e6g7ecklhsoiwhveftlvgm.png)
Но это пока работает только в phpca-check и в отчете не отображается. В дальнейшем думаю поправлю.
Автоматический контроль в CI/CD
Всё это время я рассказывал про первую часть — визуализацию. И анализ, который можно проводить на ее основе.
Пришло время заставить наших
Запускаем в консоли (в корне проекта):
vendor/bin/phpca-check
или
vendor/bin/phpca-check /path/to/phpca-config.php
Предполагается, что запуск этой команды будет добавлен в процесс CI/CD твоего проекта. В таком случае ошибка будет блокировать дальнейшее выполнение сборки.
Скрипт завершает работу с ошибкой, и вываливает что-то подобное:
![image](https://habrastorage.org/webt/gw/fk/yi/gwfkyinqrff76x6_ywh99mkzja0.png)
Зуб даю, в реальном проекте список будет куда длиннее.
Разбор полётов
- Нарушение принципа ацикличности зависимостей (ADP)
- Аналогично, но цикл чуть короче
- 2 элемента model (перечислены) зависят от infrastructure, что не позволено конфигом
- Снова нарушение ADP
- Еще один цикл и нарушение ADP
- Нарушение SDP, т.к. более устойчивый model зависит от менее устойчивого infrastructure
- Опять циклическая зависимость, будь она неладна
- 1 элемент services (перечислен) зависит от непубличного Fio
Примерно такой результат мы имеем благодаря нашим стараниям
Теперь давай вертать всё в зад. Ну, т.е. не совсем всё, конфиги оставляю естественно. Правлю только код.
Для начала имеет смысл поправить файлы, которые нарушают правила следования зависимостей. В данном проекте большая часть циклов, наверное, уйдёт сама, но в реальных, однозначно придётся повозиться дольше (хотя это зависит от степени запущенности системы и настроек конфига).
Устранение проблем
Убрал ранее выдуманную зависимость.
github.com/Chetkov/php-clean-architecture-example-project/commit/f6bbbf713ebb4a22dfb6061f347ef007ba3b422f
Перезапускаю vendor/bin/phpca-check
![image](https://habrastorage.org/webt/h8/4f/ww/h84fwwfjzezdnpbb4pfo9bafbso.png)
Ну, тут уже только конфиг править, т.к. пример был придуман действительно наркоманский.
![image](https://habrastorage.org/webt/dd/ak/t5/ddakt51werp2dhetkwxra8ejyn0.png)
Снова vendor/bin/phpca-check
![image](https://habrastorage.org/webt/pb/k7/tw/pbk7twux3ah-6novxv9adktsw4m.png)
И CI/CD поехал дальше!
На этом всё.
Послесловие
Лови пятюню, если дочитал до конца, если нет, чухай ногу об дорогу (всё-равно ты этого не увидел).
А если серьёзно, то спасибо тебе за уделенное время, не важно слился ты на середине или дочитал, вникал в каждую строчку и анализировал каждый скрин или пролистал обе части по диагонали. В любом случае спасибо.
Еще раз прошу прощения, если что-то было не так.
Возможно кому-то моя манера изложения могла показаться чересчур вальяжной или неуважительной, я просто хотел слегка разбавить статью и ни в коем случае не хотел кого-то обидеть. Мне кажется без этого она получилось бы слишком сухой и сложной для чтения.
Мне реально интересно знать твоё мнение и если у тебя есть какие-то мысли, идеи, предложения или возражения, давай обсуждать в коментах.
Чистого тебе кода и гибкой архитектуры!
Продолжение следует