Как поддержать объем "под запрос"

Я хочу получить токен пользователя из промежуточного программного обеспечения и ввести его в контроллер, возможно ли это?

class TaskController {
    @inject private currentUser

    @Post('/tasks')
    addTask() {
        if (!hasPermission(currentUser)) throw new Error("Unauthorized.")
        // ...
    }
}

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

2 ответа

Решение

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

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

InversifyJS имеет inRequestScope()в течение некоторого времени, но это не очень помогает в случае контейнера на http-запрос, потому что область запроса InversifyJS фактически привязана к одному вызову , то есть каждый вызов считается запросом, и он будет работать только как предназначено для контекста HTTP, если есть только один вызов getза запрос.

У меня была та же проблема, что и у вас: из промежуточного программного обеспечения мне нужно было извлечь текущего пользователя из запроса и ввести эту информацию в CurrentUserкласс, чтобы информация могла быть позже доступна другим службам в будущем.

Для этого мне понадобилось как минимум два обращения к get<CurrentUser>: один в промежуточном программном обеспечении, а другой для создания экземпляра контроллера/обработчика запроса.

Так, inRequestScopeне является жизнеспособным решением, и inSingletonScopeили же inTransientScopeтакже исключены.

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

Во-первых, вот как вы будете его использовать :

      // register-global-dependencies.ts
ScopedContainer.globalContainer = (() => {
  const container = new Container();
  container
    .bind<SomeSingletonDep>(TOKENS.SomeSingletonDep)
    .to(SomeSingletonDep)
    .inSingletonScope();
  return container;
})();

☝️ Это позволяет вам по-прежнему иметь зависимости Singleton.

      // register-scoped-dependencies.ts
import "register-global-dependencies";

ScopedContainer.postConfigure((container) => {
  container
    .bind<RequestSpecificDep>(TOKENS.RequestSpecificDep)
    .to(RequestSpecificDep)
    .inSingletonScope();
});

☝️ Это определяет, какие зависимости должны быть разрешены один раз для каждого HTTP-запроса.

      // lambda-handler.ts
import "register-scoped-dependencies";

handler = (event, context) => {
  const requestId = event.requestContext.requestId;
  const container = ScopedContainer.for(requestId);

  try {
    // This will be the same for every request
    const singletonDep = container.get(TOKENS.SomeSingletonDep);

    // And this will be a new instance for every request
    const requestSpecificDep = container.get(TOKENS.RequestSpecificDep);
  }
  finally {
    ScopedContainer.remove(requestId);
  }
}

Это ScopedContainerучебный класс:

      import { Container, interfaces } from "inversify";

const DEFAULT_SCOPE_ID = "__default__";

type PostConfigureAction = (container: Container) => void;
type ScopedContainerCache = {
  [id: string]: Container;
};

class ScopedContainer {
  private static _postConfigureActions: PostConfigureAction[] = [];
  private static readonly _instances: ScopedContainerCache = {};

  /**
   * Options object to use when creating a new container for a
   * scope ID.
   */
  static containerOptions: interfaces.ContainerOptions;

  /**
   * A global container instance, which enables truly
   * singleton instances when using a scoped container. All scoped
   * containers reference the global container as parent.
   */
  static globalContainer: Container;

  /**
   * Returns a @see Container that is unique to the specified scope.
   * If this is the first time getting the container for the scope, then a
   * new container will be created using the provided factory. Any post configure
   * actions will also be applied to the new container instance.
   * @param scopeId Any string to identify the scope (e.g. current request ID).
   * @returns A @see Container that is unique to the specified scope.
   */
  static for(scopeId = DEFAULT_SCOPE_ID): Container {
    let container = this._instances[scopeId];
    if (!container) {
      container = this.makeNewContainer();
      this._instances[scopeId] = container;
    }
    return container;
  }

  /**
   * Unbinds the @see Container (i.e. container.unbindAll()) and removes
   * it from the cache.
   * @param scopeId
   */
  static remove(scopeId = DEFAULT_SCOPE_ID): void {
    let container = this._instances[scopeId];
    if (!container) return;
    container.unbindAll();
    delete this._instances[scopeId];
  }

  /**
   * Runs the @method remove method on all instances.
   */
  static removeAll(): void {
    Object.keys(this._instances).forEach((key) => this.remove(key));
  }

  /**
   * Adds a post configure action.
   * @param fn A function that will be run everytime a new @see Container is created.
   * @returns The @see ScopedContainer itself, to allow chaining.
   */
  static postConfigure(fn: PostConfigureAction): ScopedContainer {
    this._postConfigureActions.push(fn);
    return this;
  }

  /**
   * Removes any post configure actions.
   */
  static resetPostConfigureActions(): void {
    this._postConfigureActions = [];
  }

  private static makeNewContainer(): Container {
    const container =
      this.globalContainer?.createChild(this.containerOptions) ??
      new Container(this.containerOptions);
    this._postConfigureActions.forEach((action) => action(container));
    return container;
  }
}

export default ScopedContainer;

https://github.com/inversify/InversifyJS/issues/1076#issuecomment-1045179988

Другие вопросы по тегам