Вынос модуля авторизации на один уровень со всеми блоками

Вынос модуля авторизации на один уровень со всеми блоками

Введение

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

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

<p> Модули в проекте</p>

Модули в проекте

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

После того, как было решено вынести блок авторизации, нужно продумать, что будем выносить и каким образом? Из рабочей логики сервиса авторизации у меня было две зависимости: сервисы токенов и пользователей. Если сервис токенов можно спокойно переиспользовать, то вот пользователя нельзя, потому что у каждого модуля будет свой пользователь. Соответственно встает вопрос: каким образом можно подставить нужного пользователя?

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

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

<p> Схема взаимодействия в модуле авторизации</p>

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

Варианты решения, обоснование выбора

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

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

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

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

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

<p> Визуализация второго варианта решения</p>

Визуализация второго варианта решения

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

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

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

Реализация выбранного варианта

Разберем реализация данного способа в коде приложения.

<p> Три модуля (папки)</p>

Три модуля (папки)

На картинке «Три модуля» видно, что модули вынесены в отдельные папки.

<p> Методы взаимодействия модуля с юаем</p>

Методы взаимодействия модуля с юаем

На картинке «Методы взаимодействия модуля с юаем» экшены signup и login принимают параметр type по которому определяется модуль, который будет выбран. Модули checkAccessToken и refresh взаимодействую со всем модулями, потому что все токены хранятся в модуле jwt и через него сравниваются на актуальность.

<p> Общий сервис для всех модулей</p>

Общий сервис для всех модулей

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

<p> Пример реализации метода из общего сервиса модуля авторизации</p>

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

На картинке «Пример реализации метода из общего сервиса модуля авторизации» реализован метод, в котором показано, каким образом вызываются методы из разных классов. Давайте это разберем подробнее. В свойстве service лежит объект, в котором значение ключей хранятся ссылки на классы сервисов для работы с базой данных того или иного модуля. Для большего понимания можно посмотреть структуру свойства service на картинке «Общий сервис для всех модулей». По итогу получается, что в аргументе type получаем значение ключа и далее вызывается необходимый метод класса. Сами классы сервисов взаимодействия с базой данных того или иного модуля могут быть реализованы любым способом, главное, чтобы они соответствовали определенному интерфейсу, также в них еще прописываются репозитории, которые, в свою очередь, подключаются нужные схемы взаимодействия с базой данных.

Сложности, с которыми я столкнулся в процессе реализации

Когда модуль авторизации был вынесен на общий уровень и фабрика для работы с модулями в сервисах была реализована, то на этом этапе столкнулся с тем, что у меня отвалились валидаторы в DTO (data transfer object).

Для понимания ситуации мне необходимо будет описать предисторию. Как упоминалось выше, изначально у меня был один модуль блога. В его рамках был разработан модуль авторизации. Когда контроллер модуля авторизации принимал запрос, то перед тем как данные попадали в него они проходили валидацию в DTO с помощью библиотеки class-validator.

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

<p> Доработка обработки ошибок в файле app.ts</p>

Доработка обработки ошибок в файле app.ts

В итоге, благодаря тому, что получилось установить сервисный контейнер в главном файле app.ts, описано на картинке «Доработка обработки ошибок в файле app.ts», и добавлением кастомных валидаторов в providers в файле настроек модулей, у меня получилось сохранить принцип dependency injection, что позволило сделать все красиво, чтобы в рамках второго случая в класс валидатор можно было прокинуть данные через конструктор, а не через аргументы метода validate.

Выводы

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

Больше статей в моем блоге. Спасибо, что дочитали и до новых встреч в следующих статьях.

Начать дискуссию