В eslint есть одно простое, но мощное правило, которое поможет вам в поддержании архитектуры приложения.

import/no-restricted-paths позволяет указать зону, по которой запрещено импортировать определенные пути:

"zones": [{
  "target": ["./moduleA"],
  "from": ["./moduleB"],
  "message": 'Модуль A не может импортировать модуль B',
}]

Импорт модуля B в модуль A вызовет ошибку в IDE:

ESLint: Unexpected path "/moduleB" imported in restricted zone. 
Модуль A не может импортировать модуль B(import/no-restricted-paths)

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

Далее рассмотрим, как одна простая настройка eslint-правила поможет при проектировании, разработке или рефакторинге сложных систем.

Влияние импортов на архитектуру

В любой архитектуре есть модули, которые взаимодействуют друг с другом.

Каждый такой модуль строится в первую очередь на принципе единственной ответственности (The Single Responsibility Principle или SRP) — «модуль должен иметь одну и только одну причину для изменения».

Правильные связи между этими модулями — один из важных аспектов построения масштабируемой архитектуры и развития проекта. Когда мы импортируем один модуль в другой, мы создаем между ними связь, следовательно, импорт = связь.

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

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

В целом, кажется, всё сводится к человеческому фактору. А человеческий фактор нужно автоматизировать.

Пример использования

Рассмотрим следующий пример. Стрелками на картинке обозначены импорты/связи между модулями

Пример некорректного использования импортов
Пример некорректного использования импортов

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

Так как же это автоматизировать?

rules: {
  'import/no-restricted-paths': ['error', {
    "basePath": rootPath,
    "zones": [{
      "target": ["./lib/ui-kit/*"],
      "from": ["./backend/rest-api"],
      "message": 'Пакет ui-kit не может содержать импорт из rest-api',
    }],
  }],
}

Не правда ли, очень дешево написать пару строчек конфига и решить потенциальные проблемы архитектуры?

Заключение

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

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

Итого import/no-restricted-paths:

  • Помогает контролировать связанность модулей.

  • Упрощает рефакторинг модуля.
    Можно зафиксировать связи модуля и начать его доработку.

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

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

А какие неожиданные решения архитектурных проблем встречали вы?

Комментарии (11)


  1. cry_san
    00.00.0000 00:00

    Дополните заголовок словом "приложения"


    1. fzkirablackwhy Автор
      00.00.0000 00:00

      добавила, спасибо!


  1. Juul
    00.00.0000 00:00

    Полезно, спасибо!


  1. vsviridov
    00.00.0000 00:00

    Попробуйте https://feature-sliced.design


    1. bogatyrev_vl
      00.00.0000 00:00

      Кажется данный подход хорошо подходит для изоляции слоев в feature-sliced????

      Или там есть своя автоматизированная изоляция?


      1. vsviridov
        00.00.0000 00:00

        Там есть eslint плагин, который ругается на неправильные импорты...


        1. bogatyrev_vl
          00.00.0000 00:00

          Изучил немного)

          Если там линтер настраивается как описано тут, то они используют как раз схожий подход???? Только с дополнительными апгрейдами)

          Спасибо, возможно добавим ещё улучшений линтинга в наш проект!


  1. ArthurG
    00.00.0000 00:00

    Спасибо за статью!

    Очень больная тема. Сам использую похожий механизм в Nx.

    Хотелось бы инструмент, который бы умел в нескольких измерениях контролировать связи удобно.


    1. bogatyrev_vl
      00.00.0000 00:00

      Раньше не видел этой возможности nx, прикольно????

      Нет проблем в проекте из-за index файлов?

      Мы пока решили от них отказаться, так как с ними плохо отрабатывал tree shaking у webpack, и после отказа бандл сократился на ~30%)) Но в будущем попробуем вернуться к ним, когда наладим все связи между модулями, так как идея с публичным api пакета/модуля крайне верная)


      1. ArthurG
        00.00.0000 00:00

        Я занимаюсь серверной разработкой, поэтому с индекс-файлами не сражался.

        Вебпак неправильно реэкспорта обрабатывает?


        1. bogatyrev_vl
          00.00.0000 00:00
          +1

          Не то чтобы неверно, но у него возникают сложности при оптимизации файлов с сайдэффектами https://webpack.js.org/guides/tree-shaking/