Привет Хабр!
В данной статье я планирую развить тему важности умения "Программировать на уровне интерфейсов", а именно обсудить направление зависимостей. Это достаточно важная тема, так как только осознавая направленность зависимостей, можно спроектировать действительно гибкое и масштабируемое приложение (Данная статья является расшифровкой видео).
Что такое зависимость?
Но давайте все по порядку. Начнем с того как выглядят в наших проектах зависимости - это обычный import
какого-то модуля.
// a.js
import b from './b'
Здесь мы видим, что файл a.js
зависит от файла b.js
. A может ли теперь b.js
зависеть от a.js
?

Большинство из вас сразу же ответят конечно же нет. У этой проблемы даже есть название Circular Dependencies
или круговые зависимости. И есть даже webpack
плагин, который помогает вам найти Circular Dependency
в вашем проекте. Зачастую они могут быть не совсем очевидными, круг замыкается через 5-6 импортов.
Почему это проблема для архитектуры?
Но это все проблемы для webpack
, а почему это создает проблемы для архитектуры проекта? Допустим вам понадобилось внести изменения в файл a.js
, а так как b.js
зависит от a.js
, значит изменения повлияют и на него. Соответственно оба файла придется редактировать. С другой стороны, если вы захотите внести изменения в файл b.js
, тогда вам придется так же вносить изменения и в файл a.js
, так как они оба друг от друга зависимы. Согласитесь, звучит не очень приятно даже при использовании всего 2-ух файлов.

А теперь представьте, что у вас таких 5 файлов и все друг от друга зависят. Не важно в какой из файлов понадобится вносить изменения, в любом случае, вам придется вносить изменения или хотя бы проверить, что ничего не сломалось во всех 5 файлах. А если таких файлов не 5, а 100 или 1000 и большинство из них друг от друга зависимы.
Как эта проблема решена в большинстве проектов
Чтобы решить такого рода проблему, структура вашего проекта чаще всего напоминает дерево файлов. Во главе стоит какой-нибудь сборщик типа webpack
, который импортирует стартовый файл и дальше как ветки импорты расползаются по всему проекту, главная особенность этого дерева, что направление зависимостей идет строго в одну сторону. А на самом нижнем уровне чаще всего находятся сторонние библиотеки.

Технически такая структура файлов многих устраивает, webpack
не жалуется на круговые зависимости, разработчикам такой подход более понятен. Если отредактировал какой-то файл, тогда посмотри кто его импортирует и поправь. Очень просто для понимания.
Недостатки такого решения
Но хорошо ли это для масштабируемости? Давайте вспомним пример, который мы обсуждали в предыдущей статье "[js] Программируйте на уровне интерфейсов" (не обязательно читать, чтобы понять нижесказанное). Одной из подключаемых к тому проекту библиотек является socket.io
. И если, в такого рода дереве, мы обновим версию socket.io
или мигрируем на SSE
, в этом случае возможно и не радикально, но это все же повлияет на половину проекта. С точки зрения архитектуры, это конечно звучит не очень хорошо.

Для решения этой проблемы, в прошлой статье мы создали абстракцию. Что это значит физически. Мы добавили несколько файлов перед библиотекой и объединили их в так называемый модуль, который единственный взаимодействует с SSE
. После этого мы выставили перед этим модулем интерфейс, а именно, то что наш модуль принимает лишь 1 метод onMessage
. И теперь наш модуль обязан подстраиваться под нужды интерфейса, а это значит, что направление зависимостей изменилось. И теперь модуль зависит от интерфейса, а не наоборот.

Соответственно если мы решим заменить снова SSE
на socket.io
все, что нам придется перепроверять, это наш модуль, так как интерфейс от модуля не зависит. Такой процесс называется инверсия зависимостей, 5-ый принцип SOLID
, Dependency Inversion
, если вы не до конца понимаете, как это работает, у нас есть отдельное видео, где мы очень подробно рассказываем как это работает на практике, и еще не раз воспользуемся этим принципом в будущем
А кто от кого должен зависеть?
Мы поняли, что в проекте с помощью инверсии зависимостей мы можем менять направление зависимостей. И результатом этого является дополнительная гибкость проекта, для замены одного кода на другой, с минимизированными затратами. Тогда остается вопрос: "а как правильно организовать направление зависимостей?" Проще говоря кто от кого должен зависеть?
Давайте рассуждать логически. Вернемся к файлам a.js
и b.js
. Допустим нам приходится в a.js
вносить изменения в 2 раза чаще, чем в b.js
. И например, за месяц при доработке фич вам пришлось внести 10 раз изменения в a.js
и соответственно 5 раз в b.js
. Если между ними нет никаких зависимостей, тогда конечное количество файлов которых нам пришлось бы редактировать равно 15.

А если, например a.js
будет зависеть от b.js
. Тогда при каждом из пяти редактирований файла b.js
, скорей всего нам придется вносить правки и в a.js
, это значит, что при такой зависимости нам уже придется редактировать 20 файлов.

Осталось рассмотреть последний вариант, когда b.js
зависит от a.js
. Тогда при каждом из 10 редактирований файла a.js
, скорей всего нам придется вносить правки и в b.js
, это значит, что при такой зависимости нам уже придется редактировать целых 25 файлов.

Возможно на первый взгляд, кажется это небольшой разницей 15, 20 и 25 редактирований. Но давайте масштабируем ситуацию. Допустим в месяц разработчик в среднем выдает 5 таких фич. А в команде 5 таких фронтенд разработчиков. В году 12 месяцев разработки, отбросим отпуска. И на дистанции в 5 лет разработки получим, что при одном направлении зависимостей такой команде придется отредактировать 30 000 файлов, а при другом направлении зависимостей 37 500 тыс файлов.

Разница составляет 7 500 редактирований файлов. А я напомню мы закладывали, что один разработчик в месяц редактирует 100 файлов. Это значит, что если такая команда менее эффективно выставила направление зависимостей в их проекте, тогда они переплатили 75 месяцев разработки одного человека, если в годах, то это более 6 лет разработки. Т.е. команда в 4 человека с более эффективными направлениями зависимостей, сделала бы больше фич, чем команда в 5 человек с менее эффективными направлениями зависимостей.
Конечно все эти цифры максимально утрированы и не отображают реальную картину, так как в реальной разработке на скорость разработки фичи влияет огромное количество факторов. Эти расчеты предназначены лишь помочь вам задуматься, о том, что такая вещь как направление зависимостей, так же очень сильно может повлиять на ваш проект.
Я попробую подытожить мысль, которую я пытался донести: "Направление зависимостей должно строиться так, чтобы файл, который обновляется чаще зависел от файла, который обновляется реже." Т.е. вам в вашем проекте нужно все время анализировать, какой код чаще подвержен модификациям. Если вы нашли такой код, то от него никто не должен зависеть и наоборот.
А если в некоторых модулях вы с трудом можете определить, какой код меняется чаще. Тогда перепишите свой модуль так, чтобы отделить неизменяемый код модуля в отдельный файл или модуль, а часто изменяемый код в другой файл. В таком случае ответ кто от кого должен зависеть будет крайне очевидным.
Викторина
Для тех кому понравилась данная тема, я оставлю ссылочку на мини викторину