Я не большой поклонник больших фреймворков, таких как NestJS; Мне всегда нравилась свобода создавать свое программное обеспечение так, как я хочу, с упрощенной структурой, которую я решаю. Но что мне понравилось при тестировании NestJS, так это внедрение зависимостей.
Внедрение зависимостей (DI) — это шаблон проектирования, который позволяет нам разрабатывать слабосвязанный код, снимая с наших классов ответственность за создание и управление зависимостями. Этот шаблон имеет решающее значение для написания поддерживаемых, тестируемых и масштабируемых приложений. В экосистеме TypeScript TSyringe выделяется как мощный и легкий контейнер для внедрения зависимостей, который упрощает этот процесс.
TSyringe — это облегченный контейнер внедрения зависимостей для приложений TypeScript/JavaScript. Поддерживается Microsoft на GitHub (https://github.com/microsoft/tsyringe) и использует декораторы для внедрения в конструктор. Затем он использует контейнер Inversion of Control для хранения зависимостей на основе токена, который вы можете обменять на экземпляр или значение.
Прежде чем углубиться в TSyringe, давайте кратко рассмотрим, что такое внедрение зависимостей и почему это важно.
Внедрение зависимостей — это метод, при котором объект получает зависимости из внешних источников, а не создает их самостоятельно. Этот подход дает несколько преимуществ:
Сначала давайте настроим TSyringe в вашем проекте TypeScript:
npm install tsyringe reflect-metadata
В вашем tsconfig.json убедитесь, что у вас есть следующие параметры:
{ "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true } }
Импортируйте метаданные отражения в точку входа вашего приложения:
import "reflect-metadata";
Точкой входа вашего приложения является, например, корневой макет Next.js 13 или основной файл в небольшом приложении Express.
Давайте возьмем пример из вступления и добавим сахар 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) ...
Этот пример довольно прост, поскольку каждый класс зависит только от другого, но наши сервисы могут зависеть от многих, и внедрение зависимостей действительно поможет сохранить все в порядке.
Но подождите! Не оставляй меня так! Как насчет тестов?
Наши инъекции также могут помочь нам протестировать наш код, отправляя макеты объектов прямо в наши зависимости. Давайте посмотрим пример кода:
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 содержит макет, который будет внедрен в зависимые классы.
Используйте токены для именования: вместо использования строковых литералов для токенов внедрения создайте константные токены:
export const USER_REPOSITORY_TOKEN = Symbol("UserRepository");
Контейнеры с областью действия: используйте контейнеры с областью действия для зависимостей на уровне запроса в веб-приложениях.
Не злоупотребляйте DI: не все нужно внедрять. Используйте DI для сквозных задач и настраиваемых зависимостей.
Если вы зашли так далеко, я хочу сказать спасибо за чтение. Надеюсь, вы нашли эту статью поучительной. Не забывайте всегда учитывать конкретные потребности вашего проекта при реализации внедрения зависимостей и архитектурных шаблонов.
Лайки и отзывы в комментариях — лучший способ стать лучше.
Удачного программирования!
Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.
Copyright© 2022 湘ICP备2022001581号-3