Вопрос или проблема
NodeJS – Метод ‘get’ прокси не доступен/не достигается
Я реализую внедрение зависимостей в классе TypeScript, используя пользовательский реестр и Proxy. Я выбираю этот подход, чтобы иметь возможность внедрять некоторые фиктивные реализации для тестов и, возможно, некоторые реализации, которые могут быть активированы удаленно.
Однако внедренная зависимость не разрешается при создании экземпляра класса. Метод get
Proxy не вызывается, и свойство остается неопределенным.
Вот некоторый релевантный код:
- Реестр (
registry.ts
):
import "reflect-metadata";
export default class Registry {
dependencies: { [name: string]: any };
static instance: Registry;
private constructor() {
this.dependencies = {};
}
provide(name: string, dependency: any) {
this.dependencies[name] = dependency;
}
inject(name: string) {
return this.dependencies[name];
}
static getInstance() {
if (!Registry.instance) {
Registry.instance = new Registry();
}
return Registry.instance;
}
}
export function inject(name: string) {
return function (target: any, propertyKey: string) {
target[propertyKey] = new Proxy(
{},
{
get: function (target, prop) {
const dependency = Registry.getInstance().inject(name);
return dependency[prop];
},
},
);
};
}
- Использование (
change-password-use-case.ts
):
// ... импорты
export class ChangePasswordUseCase implements ChangePassword {
constructor(
private readonly accountRepository: IAccountRepository,
private readonly tokenRepository: ITokenRepository,
private readonly bcryptHasher: IBCryptHasher & ICompare,
private readonly crypter: IEncrypter & IBCryptHashComparer,
private readonly changedPasswordEmailSender: SendChangedPasswordEmail,
private readonly canvas: ICanvasGateway,
) {}
async execute(params: ChangePassword.Params): Promise<ChangePassword.Result> {
console.log('Выполнение смены пароля');
// ... остальная часть метода
}
}
- Использование в контроллере (
change-password-controller.ts
):
import { inject } from "@/infrastructure/di/registry";
import { ChangePasswordUseCase } from "@/application/use-cases/account/change-password";
import { ChangePassword } from "@/domain/use-cases/account/change-password";
export class ChangePasswordController extends Controller<ChangePasswordRequest> {
@inject("useCase")
private useCase!: ChangePassword;
async run(request: HttpRequest<ChangePasswordRequest>): Promise<HttpResponse> {
const { token, password } = request.body!;
try {
await this.useCase.execute({
token,
password,
});
return this.success({
message: "Пароль успешно изменен.",
}, 200);
} catch (error: any) {
if (error instanceof ApplicationError) {
return this.badRequest({
error: error.name,
message: error.message,
});
}
return this.serverError({
error: "InternalServerError",
message: error.message,
});
}
}
}
- Маршруты, связанные с аккаунтом:
// ... импорты
const accountRouter = Router();
Registry.getInstance().provide("canvas", new CanvasGatewayFake());
Registry.getInstance().provide(
"useCase",
new ChangePasswordUseCase(
new AccountRepository(postgresql),
new TokenRepository(redis),
new BCrypt(),
new Crypter(),
SendChangedPasswordEmailFactory.createService(),
new CanvasGatewayFake(),
),
);
// ... некоторые другие маршруты
accountRouter.post("/change-password", expressAdapter(ChangePasswordFactory.createController()));
export default accountRouter;
- Фабрика:
import { ChangePasswordController } from "@/presentation/controllers/account";
export class ChangePasswordFactory {
static createController(): ChangePasswordController {
const controller = new ChangePasswordController();
return controller;
}
}
Проблема:
- Когда я создаю экземпляр
ChangePasswordUseCase
и предоставляю его в мой реестр, свойствоcanvas
остается неопределенным. Я уже пробовал переместить оператор provide в фабрику контроллера, но поведение осталось прежним. - Метод
get
Proxy не вызывается. - Я подтвердил, что
CanvasGatewayFake
правильно зарегистрирован в реестре, выводя значение атрибута зависимостей в консоль.
Что я пропустил? Как я могу дальше разобраться с этой проблемой?
Дополнительный контекст:
- Это часть более крупного приложения на Node.js с использованием Express.
ChangePasswordUseCase
используется в контроллере, который затем обрабатывается маршрутизатором.- Приложение использует TypeScript и следует паттерну чистой архитектуры.
Что я ожидал:
- Когда класс инициализируется, у него будет декорированное свойство, установленное как
undefined
; - В этом случае, когда это свойство вызывается, оно должно будет отозвать реализацию из реестра перед тем, как стать действительно доступным. То есть:
useCase
будет экземпляром прокси, и перед выполнением егоexecute
метода оно будет искать реальный экземплярChangePasswordUseCase()
.
NodeJS v20.14.0
Typescript 5.4.5
Любые предложения или решения будут очень полезны!
Ответ или решение
Для решения вашей проблемы с внедрением зависимостей через Proxy в TypeScript я предлагаю вам рассмотреть несколько моментов. Главная проблема, которую вы описали, заключается в том, что метод get
вашего Proxy не вызывается, и свойство остается неопределенным. Давайте разберем основные проблемы и потенциальные решения, чтобы помочь вам устранить данное поведение.
Причины проблемы
-
Декораторы в TypeScript: Декораторы в TypeScript работают во время определения класса, а не во время создания экземпляра класса. Это значит, что когда вы вызываете
new ChangePasswordController()
, декоратор@inject("useCase")
уже был применен, и он устанавливает значение свойстваuseCase
на созданный Proxy. Однако сам Proxy не инициализируется с объектом внедрения до того, как вы фактически обращаетесь кuseCase
. - Проблема с контекстом: Когда вы применяете Proxy, проверьте, что метод
get
realmente возвращает нужное значение. Еслиdependency[prop]
не существует, он вернетundefined
, и это повлияет на функциональность вашей программы.
Предложения по улучшению решения
-
Проверьте реализацию
inject
: Убедитесь, что зависимость доступна в реестре, когда Proxy пытается ее получить. Если вы не инициализируете реестр до того, как использовать его в классе, это может привести к тому, что зависимости не будут найдены. -
Проверка доступности зависимости: В методе
get
можно добавить проверку на наличие зависимости. Если она не найдена, можно выбросить ошибку или сгенерировать предупреждение для отладки:const dependency = Registry.getInstance().inject(name); if (!dependency) { console.warn(`Dependency ${name} not found.`); } return dependency[prop];
-
Инициализация реестра и зависимостей: Убедитесь, что ваш реестр и зависимости инициализируются до использования в контроллере. Например, переместите вызовы
Registry.getInstance().provide(...)
в более раннюю часть кода, или в само определение контроллеров, чтобы гарантировать, что они будут доступны для всех классов. - Пересмотрите использование Proxy: Возможно, вам следует использовать функцию, которая будет возвращать зависимость, а не Proxy. Например, вместо создания Proxy на свойстве можно использовать метод, который будет возвращать зависимость по требованию.
Пример как можно переписать ваш декоратор с использованием метода:
export function inject(name: string) {
return function (target: any, propertyKey: string) {
Object.defineProperty(target, propertyKey, {
get: function () {
return Registry.getInstance().inject(name);
},
enumerable: true,
configurable: true,
});
};
}
Заключительные замечания
Запуск приложения и доступ к свойству useCase
(например, при вызове await this.useCase.execute(...)
) должен теперь корректно вызывать get
у Proxy или возвращать правильную зависимость.
Обязательно протестируйте ваши изменения и следите за консольными сообщениями. Это поможет вам убедиться, что ваши зависимости инициализируются и доступны, когда они нужны.
Если после этих шагов проблема все еще остается, возможно, стоит рассмотреть использование отладчика или добавление большего количества логов для отследивания пути выполнения кода.