昨日公開したサーバの挙動がどうもおかしかったのです。ガチガチにValidationかけてたので不具合とはならなかったのですが、想定してるよりバリデーションにひっかかる可能性が高い。具体的には、Controllerの上のほうの処理と下のほうの処理で、ユーザが異なるような挙動が確認される。
具体的なコードで紹介すると
@Injectable()
export class AuthService {
public authId: number;
get userId() {
return authId;
}
// ...認証やらいろいろして、認証できたらauthIdにユーザIDを入れる処理とか
}
といったサービスをDIした以下のようなControllerがあったとします。
export class StatusController {
constructor(
private auth: AuthService,
) {}
@Get()
async userStatus(): Promise<IResponseGetUserStatus> {
console.log(this.auth.userId); // 【1】
// ...時間のかかる非同期処理
console.log(this.auth.userId); // 【2】
}
}
みたいな処理をしていると、【1】と【2】でそのリクエスト内では再ログインや上書きをしていないのに、なぜか値が異なる!!
ちょっと各位に相談したりドキュメントみたりしてたのですが、どうもNestJSではデフォルトではDIはHTTP Request単位ではなく、サーバ全体で行われるようです。簡単にいうと、ControllerもServiceもユーザ間で共有します。なので、プロパティでユーザIDをいれようものなら、取り合いがはじまって容赦なく書き換わります。
Issuesはこちらですね。
Http Request based DI #1376
https://github.com/nestjs/nest/issues/1376
いやー、焦った。テストでまったくでてこないし、当然ローカルテストで発見できるようなものではないので。確認したところ、ちょうど1年前にこれを解決するための機能が実装されていました。
Injection scopes
https://docs.nestjs.com/fundamentals/injection-scopes
この機能を使うことによって、DIをHTTP単位で生成するようにすることができます(代わりにコストが高くなることを注意する旨書かれていたりしますので、何でもすればいいわけじゃないです。認証といったクリティカルなものはそうしないとだめなだけです)。
先程の例だと、以下のようにすれば治ります。
@Injectable(
{ scope: Scope.REQUEST } // 追記
)
export class AuthService {
public authId: number;
get userId() {
return authId;
}
}
実環境で動かすまで気づけない的にはかなりハマりポイントだと思いますので、ぜひ設計段階でNodeのDIをどの範囲で行うかは考えるようにすることをおすすめします。ブラウザと違って、DIは他のユーザと共有するリスクがあることにご注意ください。
それではまた。