Как стать автором
Обновить

Подход к реализации DI контейнера на golang

Пролог

Для начала давайте определимся почему стоит стремится использовать DI (Dependency injection) в своих сервисах.

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

Основная проблема в таком подходе заключается в следующем:

  • программисту всегда нужно обращаться к такой структуре,

  • держать ее у себя в голове (а она с ростом сервиса все дальше и дальше обрастает зависимостями)

  • понимать в какой последовательности нужно инициализировать те или иные компоненты системы

  • любое расширение компонентов приводит к рефакторингу большого объема кода

  • а самая боль, это то что со временем код превращается в такой кусок лапши что время на понимание структуры сервиса превалирует над процессом написания нового кода (-:

Что такое DI контейнер?

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

Такой подход позволяет:

  • освободить голову от хранения структуры программы

  • упростить читаемость кода

  • перейти к процессу реализации новых фич в сервисе без знания всего проекта в целом

Как реализовать свой DI контейнер

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

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

Используя его давайте представим наши зависимости:

Компонент

Зависимость

Результат инициализации

HttpServer

*Auth, *Logger, *Content

error - будем возвращать в случае если запуск не удался

Auth (будем использовать как middleware для проверки входящего запроса)

*Database

*Auth

Logger (для логирования всех запросов)

---

*Logger

Content

*Database

*Content

Database (наш источник знаний о контенте и о юзерах)

---

*Database

Благодаря такому анализу графа DI понимает что за чем инициализировать, может сохранить у себя структуру зависимостей и начать их инициализировать: первым что мы начнем создавать с помощью DI это Database и Logger т.к. для их создания не требуется чего-то дополнительного, далее создаем Auth и Content т.к. зависимость Database у нас уже создана и в конце создаем HttpServer, т.к. у него самое большое кол-во зависимостей и они уже созданы.

На что стоит обратить внимание при разработки DI контейнера:

  • при сборе информации о графе учитывать не только название компонентов но и их полный адрес в сервисе

  • учитывать возможные циклические зависимости, при их нахождении DI не должен давать запускать сервис а выдавать ошибку

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

P.S.

Пример реализации вышеупомянутой механики можно рассмотреть на примере библиотеки https://github.com/deweppro/go-app которая может быть каркасом для ваших сервисов.

P.S.S.

Буду рад комментариям и пулреквестам по оптимизации библиотеки ;-)

Всем удачи!

Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.