«Если рабочий хочет хорошо выполнять свою работу, он должен сначала заточить свои инструменты» — Конфуций, «Аналитики Конфуция. Лу Лингун»
титульная страница > программирование > TSyringe и внедрение зависимостей в TypeScript

TSyringe и внедрение зависимостей в TypeScript

Опубликовано 31 октября 2024 г.
Просматривать:529

TSyringe and Dependency Injection in TypeScript

Я не большой поклонник больших фреймворков, таких как NestJS; Мне всегда нравилась свобода создавать свое программное обеспечение так, как я хочу, с упрощенной структурой, которую я решаю. Но что мне понравилось при тестировании NestJS, так это внедрение зависимостей.

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

TSyringe — это облегченный контейнер внедрения зависимостей для приложений TypeScript/JavaScript. Поддерживается Microsoft на GitHub (https://github.com/microsoft/tsyringe) и использует декораторы для внедрения в конструктор. Затем он использует контейнер Inversion of Control для хранения зависимостей на основе токена, который вы можете обменять на экземпляр или значение.

Понимание внедрения зависимостей

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

Внедрение зависимостей — это метод, при котором объект получает зависимости из внешних источников, а не создает их самостоятельно. Этот подход дает несколько преимуществ:

  1. Улучшенная тестируемость: зависимости можно легко имитировать или заглушать в модульных тестах.
  2. Повышенная модульность: компоненты более независимы и их можно легко заменять или обновлять.
  3. Улучшение повторного использования кода: зависимости можно использовать в разных частях приложения.
  4. Улучшенная поддержка: изменения в зависимостях оказывают минимальное влияние на зависимый код.

Настройка TSyringe

Сначала давайте настроим TSyringe в вашем проекте TypeScript:

npm install tsyringe reflect-metadata

В вашем tsconfig.json убедитесь, что у вас есть следующие параметры:

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

Импортируйте метаданные отражения в точку входа вашего приложения:

import "reflect-metadata";

Точкой входа вашего приложения является, например, корневой макет Next.js 13 или основной файл в небольшом приложении Express.

Реализация внедрения зависимостей с помощью TSyringe

Давайте возьмем пример из вступления и добавим сахар TSyringe:

Начнем с адаптера.

// @/adapters/userAdapter.ts
import { injectable } from "tsyringe"

@injectable()
class UserAdapter {
    constructor(...) {...}

    async fetchByUUID(uuid) {...}
}

Обратили внимание на декоратор @injectable()? Это значит, что TSyringe может быть внедрен во время выполнения.

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

// @/core/user/user.service.ts
import { injectable, inject } from "tsyringe"
...

@injectable()
class UserService {
    constructor(@inject('UserAdapter') private readonly userAdapter: UserAdapter) {}

    async fetchByUUID(uuid: string) {
    ...
        const { data, error } = await this.userAdapter.fetchByUUID(uuid);
    ...
    }
}

Здесь я также использовал декоратор @injectable, потому что Сервис будет внедрен в мой класс команд, но я также добавил декоратор @inject в параметры конструктора. Этот декоратор сообщает TSyringe указать экземпляр или значение, которое он имеет для токена UserAdapter, для свойства userAdapter во время выполнения.

И последнее, но не менее важное: корень моего ядра: класс команд (часто ошибочно называемый usecase).

// @/core/user/user.commands.ts
import { inject } from "tsyringe"
...

@injectable()
class UserCommands {
    constructor(@inject('UserService') private readonly userService: UserService) {}

    async fetchByUUID(uuid) {
    ...
        const { data, error } = this.userService.fetchByUUID(uuid);
    ...
    }
}

На этом этапе мы сообщили TSyringe, что будет внедрено и что будет внедрено в конструктор. Но мы до сих пор не создали контейнеры для хранения зависимостей. Мы можем сделать это двумя способами:

Мы можем создать файл с нашим реестром внедрения зависимостей:

// @/core/user/user.dependencies.ts
import { container } from "tsyringe"
...

container.register("UserService", {useClass: UserService}) // associate the UserService with the token "UserService"
container.register("UserAdapter", {useClass: UserAdapter}) // associate the UserAdapter with the token "UserAdapter"

export { container }

Но мы также можем использовать декоратор @registry.

// @/core/user/user.commands.ts
import { inject, registry, injectable } from "tsyringe"
...

@injectable()
@registry([
    {
        token: 'UserService',
        useClass: UserService
    },
    {
        token: 'UserAdapter',
        useClass: UserAdapter
    },
])
export class UserCommands {
    constructor(@inject('UserService') private readonly userService: UserService) {}

    async fetchByUUID(uuid) {
    ...
        const { data, error } = this.userService.fetchByUUID(uuid);
    ...
    }
}

container.register("UserCommands", { useClass: UserCommands})

export { container }

У обоих методов есть плюсы и минусы, но, в конце концов, это дело вкуса.

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

import { container, UserCommands } from "@/core/user/user.commands"

...
const userCommands = container.resolve("UserCommands")
await userCommands.fetchByUUID(uuid)
...

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

Но подождите! Не оставляй меня так! Как насчет тестов?

Тестирование с помощью TSyringe

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

import { container, UserCommands } from "@/core/user/user.commands"

describe("test ftw", () => {
    let userAdapterMock: UserAdapterMock
    let userCommands: UserCommands

    beforeEach(() => {
        userAdapterMock = new UserAdapter()
        container.registerInstance("UserAdapter", userAdapter)
        userCommands = container.resolve("UserCommands")
    });

    ...
});

Теперь токен UserAdapter содержит макет, который будет внедрен в зависимые классы.

Лучшие практики и советы

  1. Используйте интерфейсы: определите интерфейсы для своих зависимостей, чтобы их можно было легко заменять и тестировать. В этой статье я не использовал интерфейсы для простоты, но интерфейсы — это жизнь.
  2. Избегайте циклических зависимостей: структурируйте свой код так, чтобы избежать циклических зависимостей, которые могут вызвать проблемы с TSyringe.
  3. Используйте токены для именования: вместо использования строковых литералов для токенов внедрения создайте константные токены:

    export const USER_REPOSITORY_TOKEN = Symbol("UserRepository");
    
    
  4. Контейнеры с областью действия: используйте контейнеры с областью действия для зависимостей на уровне запроса в веб-приложениях.

  5. Не злоупотребляйте DI: не все нужно внедрять. Используйте DI для сквозных задач и настраиваемых зависимостей.

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

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

Удачного программирования!

Заявление о выпуске Эта статья воспроизведена по адресу: https://dev.to/gdsources/tsyringe-and-dependent-injection-in-typescript-3i67?1. Если обнаружено какое-либо нарушение прав, свяжитесь с [email protected], чтобы удалить ее.
Последний учебник Более>

Изучайте китайский

Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.

Copyright© 2022 湘ICP备2022001581号-3