ソースコードで理解するNestJS①: @Controllerは何をするのか

# なぜ書いているのか
ちょっくらNestJSを触ってみようと思ったものの、Angular未経験の自分にはデコレータ中心の書き方に馴染みがない & なぜこれで動くのかが理解できずに気持ち悪いので、公式リポジトリのソースコードを読んでみることにした。  

<INS>前提知識</INS>
- Typescript
- Decoratorの概念と基礎 (https://zenn.dev/monicle/articles/b6a409eeb62f41)
- NestJS公式ドキュメントのOverView程度 (https://docs.nestjs.com/first-steps)

<INS>本記事で扱わないこと</INS>
- NestJSの特徴
- NestJSの使い方
など

## Controllerのソースコードを読む
公式リポジトリのcontroller.decorator.tsのソースコードは以下にあります。
https://github.com/nestjs/nest/blob/master/packages/common/decorators/core/controller.decorator.ts

後述のControllerOptionsインターフェースの宣言に続き、引数別のデコレータ関数の宣言が続いています。引数として取りうるのは以下の3種類

 - `Controller()`
 - `Controller(prefix: string | string[])`
 - `Controller(options: ControllerOptions)`

 公式ドキュメント含め、サンプルコード系でよく使われるのは真ん中でしょう。`Controller()`の場合は`'/'`を指定した場合と同値になるようです。

3番目で使用されている`ControllerOptions`インターフェースは以下のように宣言されています。
```typescript
/**
 * Interface defining options that can be passed to `@Controller()` decorator
 *
 * @publicApi
 */
export interface ControllerOptions extends ScopeOptions, VersionOptions {
  /**
   * Specifies an optional `route path prefix`.  The prefix is pre-pended to the
   * path specified in any request decorator in the class.
   */
  path?: string | string[];

  /**
   * Specifies an optional HTTP Request host filter.  When configured, methods
   * within the controller will only be routed if the request host matches the
   * specified value.
   *
   * @see [Routing](https://docs.nestjs.com/controllers#routing)
   */
  host?: string | RegExp | Array<string | RegExp>;
}
```
`path?`については2番目のパターンと同一の情報であり、違いはもう一つの`host`にあり、ホスト名をもとにしたルーティングが可能であることを示しています。コメントに記載のある公式ドキュメントの[Sub-Domain Routing](https://docs.nestjs.com/controllers#sub-domain-routing)をみると、例として以下が挙げられています。
```typescript
@Controller({ host: ':account.example.com' })
export class AccountController {
  @Get()
  getInfo(@HostParam('account') account: string) {
    return account;
  }
}
```

ここで、ホスト名には動的なパラメータが指定されており、例えばリクエストが `user123.example.com` というホスト名で送信された場合、`account` トークンには `user123` という値が入ります。そして、コントローラのメソッド引数において`@HostParam()`デコレーターを使用することで、その値をメソッド内で使用することができます。

### Controllerの実装
さて、Controllerの実装を見ていきましょう。どのような処理が行われているのか...と見てみると、非常にシンプルです。

```typescript
export function Controller(
  prefixOrOptions?: string | string[] | ControllerOptions,
): ClassDecorator {

  //  ------------------------------------------------------
  //  省略: 引数に応じたpath, host, scopeOptions, versionOptionsの設定
  //  ------------------------------------------------------
  
  return (target: object) => {
    Reflect.defineMetadata(CONTROLLER_WATERMARK, true, target);
    Reflect.defineMetadata(PATH_METADATA, path, target);
    Reflect.defineMetadata(HOST_METADATA, host, target);
    Reflect.defineMetadata(SCOPE_OPTIONS_METADATA, scopeOptions, target);
    Reflect.defineMetadata(VERSION_METADATA, versionOptions, target);
  };
}

```
省略したパラメータの設定 (単に指定されたものがあればそれを、なければ`undefined`にする、という程度のもの)を除けば、`Reflect.defineMetadata`というメソッドを実行するクラスデコレータを返却するだけになっています。ここで、`Reflect.defineMetadata`はreflect-metadataライブラリで提供されており、`@Controller`が付与されたクラスに対してメタ情報を付与しています。付与されたメタデータは`Reflect.getMetadata`によって取得可能です。

### `@Controller`とは何か
ここから、`@Controller`は、`CONTROLLER_WATERMARK`や`PATH_METADATA`といったメタデータを付与するところまでが責務であり、NestJSを理解するにはそれを利用する側(=`Reflect.getMetaData`を行っている場所)が本質的なのだろうということがわかります。

これは考えてみれば当たり前で、decoratorはあくまでもクラスやそのメソッドに対して付加的な処理を行うことができる機能です。routing、すなわち「どんな条件で実行されるか」を制御するにはデコレータではなくそのクラスやメソッドを呼び出す側で処理が必要になります。

### 別の例: `@Injectable`デコレータの実装
もう一つ、`@Injectable`のソースコードを見てみましょう。こちらも`@Controller`と同様にメタデータの付与が責務だろうと予想されます。
https://github.com/nestjs/nest/blob/master/packages/common/decorators/core/injectable.decorator.ts
```typescript
export function Injectable(options?: InjectableOptions): ClassDecorator {
  return (target: object) => {
    Reflect.defineMetadata(INJECTABLE_WATERMARK, true, target);
    Reflect.defineMetadata(SCOPE_OPTIONS_METADATA, options, target);
  };
}
```
予想通り、こちらも`INJECTABLE_WATERMARK`と`SCOPE_OPTIONS_METADATA`というkeyでメタデータを付与することしかしていません。

### 別の例: `@UseGuards`
性質の違うDecoratorとして、`@UseGuards`を見てみましょう。[公式ドキュメント](https://docs.nestjs.com/guards)にある通り、本デコレータはリクエストハンドラの実行条件を設定するものです。以下の例では、`GET`リクエストに対して、`AuthGuard`クラスで実装された`canActivate`がtrueを返す場合のみfindAllを実行するように指定しています。
```typescript
@Controller('users')
export class UsersController {
  
  @Get()
  @UseGuards(AuthGuard)
  findAll() {
    // ユーザー一覧を取得するロジック
  }
}
```
`@Controller`や`@Injectable`と異なり、`@UseGuards`の責務は一般的な(メソッド)デコレータの処理にかなり近いことがわかります。ソースコードを見てみましょう。

```typescript
export function UseGuards(
  ...guards: (CanActivate | Function)[]
): MethodDecorator & ClassDecorator {
  return (
    target: any,
    key?: string | symbol,
    descriptor?: TypedPropertyDescriptor<any>,
  ) => {
    const isGuardValid = <T extends Function | Record<string, any>>(guard: T) =>
      guard &&
      (isFunction(guard) ||
        isFunction((guard as Record<string, any>).canActivate));

    if (descriptor) {
      validateEach(
        target.constructor,
        guards,
        isGuardValid,
        '@UseGuards',
        'guard',
      );
      extendArrayMetadata(GUARDS_METADATA, guards, descriptor.value);
      return descriptor;
    }
    validateEach(target, guards, isGuardValid, '@UseGuards', 'guard');
    extendArrayMetadata(GUARDS_METADATA, guards, target);
    return target;
  };
}
```
色々ありますが、重要なのは以下の3つです。
```typescript
validateEach(target, guards, isGuardValid, '@UseGuards', 'guard');
extendArrayMetadata(GUARDS_METADATA, guards, target);
return target;
```
`validateEach`は[こちら](https://github.com/nestjs/nest/blob/master/packages/common/utils/validate-each.util.ts)で定義され、`predicate = isGuardValid`によって指定されたGuards全てがGuardsとして有効なものかどうかを見ています (`@UseGuards`は複数のGuardsを引数に取ることができる)。そして、不正な場合は例外を投げています。

すなわち、正常なGuardsが指定されている場合、`@UseGuards`の処理は実質的に`extendArrayMetadata`のみで、`@Controller`と同様にここでも**コアになるのはMetadata付与**である、ということがわかります。`extendArrayMetadata`の実装を一応いかに示しますが想像通りでしょう。

```typescript
export function extendArrayMetadata<T extends Array<unknown>>(
  key: string,
  metadata: T,
  target: Function,
) {
  const previousValue = Reflect.getMetadata(key, target) || [];
  const value = [...previousValue, ...metadata];
  Reflect.defineMetadata(key, value, target);
}
```

## 次回
初回である今回の記事では、NestJSの各種デコレータがMetadataの付与を責務としていることを見ました。次回の記事では、こうして付与されたメタデータがどのようにNestJSのリクエストルーティングやDIに使われるかを掘り下げていきます。