ELI5: Инъекция зависимостей

«Расскажите мне о Dependency Injection»… это был первый технический вопрос, который мне задали на собеседовании. В то время, будучи свежим выпускником буткемпа по кодингу, я, честно говоря, понятия не имел… Я сразу же почувствовал себя дураком и вынужден был признать, что ничего об этом не знаю. Интервьюер был мил и объяснил мне, но в тот момент я почти не слушал, я уже знал, что не получу эту работу. Спустя годы я понял концепцию, и, честно говоря, несмотря на все причудливые слова из компьютерной науки, которые люди используют, когда говорят о ней, ее идея на самом деле не такая уж сложная.

Что же это такое?

Давайте начнем с зависимости. Зависимость — это то, от чего зависит правильное функционирование части кода. Тогда Dependency Injection — это просто передача (внедрение) этой функциональности извне, а не создание ее внутри. Это разделение ответственности.

Пример

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

// imports database functionality
import dbConnection from 'dbConnection'

// router code
router.get('/:id', (request) => {

  // connects to our database
  const mockDatabaseConnection = new dbConnection({
    user: 'mockUsername',
    host: 'mockHost',
    database: 'mockDatabase',
    password: 'mockPassword',
    port: 'mockPort'
})

  // grabs the id from our request
  const { id } = request.params

  // builds a query object
  const query = `SELECT * FROM users WHERE id = ${id}`

  // queries our database
  mockDatabaseConnection.query(query)
    .then((response) => {
      // handles response
    })
})
Войти в полноэкранный режим Выйти из полноэкранного режима

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

  1. Код, обрабатывающий запрос и создающий из него объект запроса
  2. Код, который обрабатывает все, что связано с базой данных (подключение и запрос)
  3. Код, который обрабатывает, что делать с возвращенным ответом или ошибкой.

Попробуем извлечь код базы данных в другой файл, чтобы он мог быть общим для всех наших запросов:

DATABASE FILE

// imports database functionality
import dbConnection from 'dbConnection'

// connects to our database
const mockDatabaseConnection = new dbConnection({
    user: 'mockUsername',
    host: 'mockHost',
    database: 'mockDatabase',
    password: 'mockPassword',
    port: 'mockPort'
})

// a simple query function that we can export
const queryFunction = (query) => {
  return mockDatabaseConnection.query()
}

export default queryFunction
Войти в полноэкранный режим Выход из полноэкранного режима

Теперь давайте вставим этот код в нашу функцию маршрутизации, а не встраивать его внутрь:

// imports database functionality
import queryFunction from 'DatabaseFile'

// GET user by id
router.get('/:id', (request) => {

  // grabs the id from our request
  const { id } = request.params

  // builds a query object
  const query = `SELECT * FROM users WHERE id = ${id}`

  // queries our database
  queryFunction(query)
    .then((response) => {
      //handles response
    })
})

Войти в полноэкранный режим Выход из полноэкранного режима

Это выглядит намного чище… и это пример инъекции зависимостей! Мы внедряем код базы данных в код маршрута, от которого зависит код маршрута. Помимо визуальной чистоты, каковы некоторые преимущества этого?

  • Это гораздо более модульный и многоразовый подход. Теперь каждый раз, когда мы хотим написать новый маршрут, мы можем просто внедрить базу данных в маршрут, а не перестраивать его каждый раз.
  • Это намного чище и проще для тестирования. Наш код базы данных живет в одном файле и может быть протестирован сразу. Раньше нам пришлось бы тестировать код базы данных в каждом маршруте! (Для человека, который ненавидит писать модульные тесты, это очень важно).
  • Он разделяет логику таким образом, что каждая часть делает то, что ей нужно, независимо от другой. Почему код маршрутизатора должен быть связан с подключением к базе данных? Что если мы захотим сменить базу данных? Используя инъекцию зависимостей, мы можем просто обновить файл базы данных, и все готово. Раньше нам пришлось бы менять его в каждом отдельном маршруте… ой.
  • Код DRY (не повторяйтесь).

TL;DR

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

Оцените статью
Procodings.ru
Добавить комментарий