ソースコードで理解するNestJS③: RoutesResolver, RouterExplorer

# 前回のまとめなど
https://qiita.com/fj-th/items/5c11251bc66feb477f09

https://qiita.com/fj-th/items/2501b11fb789945d373d

これまでで、NestJSにおけるデコレータ（`@Controller`など）の内部的な動作と、指定された情報がどのように利用されているかをみました。今回は、前回の記事で触れた二つの重要なクラスである`RoutesResolver`と`RouterExplorer`について詳しく掘り下げます。

# RoutesResolver
https://github.com/nestjs/nest/blob/master/packages/core/router/routes-resolver.ts

`RoutesResolver`はNestアプリケーションのルーティングの中核をなすクラスです。NestJSアプリケーションを作成する際`NestFactory.create`によって生成される`NestApplication`インスタンスのコンストラクタ内で、`RoutesResolver`は生成されます。　

```typescript
export class NestApplication
  extends NestApplicationContext<NestApplicationOptions>
  implements INestApplication
{
  // 省略
  constructor(
    ...
  ) {
  
    // 省略 
    
    this.routesResolver = new RoutesResolver(
      this.container,
      this.config,
      this.injector,
      this.graphInspector,
    );
  }
```

https://github.com/nestjs/nest/blob/e4b07eb06b406183e708269e0439f442a1dadb1c/packages/core/nest-application.ts#L44


`RoutesResolver`は`Resolver`インターフェースを実装しています。
```typescript
export interface Resolver {
  resolve(instance: any, basePath: string): void;
  registerNotFoundHandler(): void;
  registerExceptionHandler(): void;
}
```

## `resolve`の実装

```typescript
  public resolve<T extends HttpServer>(
    applicationRef: T,
    globalPrefix: string,
  ) {
    ...
  }
```

ジェネリクスが使われており、`resolve`の第一引数である`instance: any`は`HttpServer`インターフェースを実装することがわかります(Expressの`express.Router`など)。

実際の処理を見てみると、現在のNestJSアプリケーションに存在するModuleの全体を取得し、それぞれのコントローラを取得して`this.registerRouters`を実行しています。

```typescript

const modules = this.container.getModules();
    modules.forEach(({ controllers, metatype }, moduleName) => {
      const modulePath = this.getModulePathMetadata(metatype);
      this.registerRouters(
        controllers,
        moduleName,
        globalPrefix,
        modulePath,
        applicationRef,
      );
    });
```

`this.getModulePathMetadata`については省略して、先に進みます。

```typescript
public registerRouters(
 // 対象のModuleに登録されたController群 ("ルートマップ")
 routes: Map<string | symbol | Function, InstanceWrapper<Controller>>, 
 // @Moduleを付与したクラスの名称 
 moduleName: string,
 // アプリケーション全体のパスに対するprefix ('v1'など)
 globalPrefix: string,
 // getModulePathMetadataでMODULE_PATHメタデータから取得された、
 // AppModuleの`@Module`において`RouterModule.register`で指定するモジュールパス
 modulePath: string,
 // express.Routerなど
 applicationRef: HttpServer,
) {
 // ルートマップ内の各コントローラをループ処理
 routes.forEach(instanceWrapper => {

   //省略
   
   // コントローラのルートパスを抽出 (一般には複数の@Getや@Postなどがあるのでパスが複数)
   const routerPaths = this.routerExplorer.extractRouterPath(
     metatype as Type<any>,
   );

   // 省略

   //各ルートパスについての処理を実行
   routerPaths.forEach(path => {

    // 省略
    
     // ルートの探索と登録を実行
     this.routerExplorer.explore(
           instanceWrapper,  // 個々のコントローラのインスタンス
           moduleName,       // コントローラが属するモジュール名
           applicationRef,   // express.Routerなど(HttpServer)
           host,             // @Controller(...)で指定されたホスト情報
           routePathMetadata,// 関連メタ情報群
     );
   });
 });
}
```

`routePathMetadata`については、`modulePath`や`globalPrefix`などを含んでおり、各種デコレータで指定された情報をまとめたものになります。インターフェース定義は以下を参照。

https://github.com/nestjs/nest/blob/master/packages/core/router/interfaces/route-path-metadata.interface.ts

::: note info
`RoutesResolver`は`AppModule`に登録された各モジュールについて、各コントローラの個別のルートパス(`@Get()`や`@Post()`などで指定される)単位まで処理を分解することが責務
:::

# RouterExplorer
`Resolver`によって解決されたパス情報をもとに、`RouterExplorer`は`applicationRef`、すなわち`express.Router`のようなルーティングオブジェクトにパスそのハンドラを登録していきます。これにより、NestJSのデコレータで宣言されたルートが、実際のHTTPリクエストに応じて正しく処理されるようになります。前回で軽く内容を見ましたが、改めて追っていきます。

```typescript

public explore<T extends HttpServer = any>(
    instanceWrapper: InstanceWrapper,
    moduleKey: string,
    applicationRef: T,
    host: string | RegExp | Array<string | RegExp>,
    routePathMetadata: RoutePathMetadata,
  ) {
    const { instance } = instanceWrapper;
    const routerPaths = this.pathsExplorer.scanForPaths(instance);
    this.applyPathsToRouterProxy(
      applicationRef,
      routerPaths,
      instanceWrapper,
      moduleKey,
      routePathMetadata,
      host,
    );
  }
```
ここで、`this.pathExplorer.scanForPaths`というメソッドが登場し、`RouteDefinition[]`型の`routesPaths`を返しています。ここには、パスやメソッド名などの情報、そしてコールバックが含まれます。

```typescript 
export interface RouteDefinition {
  path: string[];
  requestMethod: RequestMethod;
  targetCallback: RouterProxyCallback;
  methodName: string;
  version?: VersionValue;
}
```
`RouterProxyCallback`型は以下で定義されており、expressにおけるハンドラ関数=ルートが一致した時に実行される関数です。

```typescript
export type RouterProxyCallback = <TRequest, TResponse>(
  req?: TRequest,
  res?: TResponse,
  next?: () => void,
) => void;
```

https://expressjs.com/ja/starter/basic-routing.html

前回の記事で見た通り、`applyPathsToRouterProxy`以降の処理はシンプルで、基本は`RouteDefinition`のそれぞれについて、`applicationRef` = `express.Router`にパスとハンドラを登録していくだけです。ということで、あとはこの`RouteDefinition`を返すメソッド(を提供するクラス`PathsExplorer`)を理解すれば十分でしょう。

::: note info
`RouterExplorer`は「ルート定義情報」をもとに、コントローラクラスで定義された各ルートハンドラを`express.Router`のような実際のルーティングオブジェウト(HTTPサーバー)に登録する。
:::

# PathsExplorer

https://github.com/nestjs/nest/blob/master/packages/core/router/paths-explorer.ts

`PathsExplorer.scanForPaths`の実装は以下のようになっています。

```typescript
export class PathsExplorer {
  constructor(private readonly metadataScanner: MetadataScanner) {}

  public scanForPaths(
    instance: Controller,
    prototype?: object,
  ): RouteDefinition[] {
    const instancePrototype = isUndefined(prototype)
      ? Object.getPrototypeOf(instance)
      : prototype;

    return this.metadataScanner
      .getAllMethodNames(instancePrototype)
      .reduce((acc, method) => {
        const route = this.exploreMethodMetadata(
          instance,
          instancePrototype,
          method,
        );

        if (route) {
          acc.push(route);
        }

        return acc;
      }, []);
  }

```

ここで`MetadataScacnner#getAllMethodNames`は`Controller`のプロトタイプを受け、そこに定義されているメソッドの一覧を取得します。

取得されたメソッドのそれぞれについて、`exploreMethodMetadata`でメソッドを取得し、`RouteDefinition`を組み立てます。

```typescript

 public exploreMethodMetadata(
    instance: Controller,   // Controllerクラスのインスタンス
    prototype: object,      // 上記のプロトタイプ
    methodName: string,     // Controllerクラスで定義されたあるメソッドの名前
  ): RouteDefinition | null {

    // コールバックメソッドを取得
    const instanceCallback = instance[methodName];

    // そのメソッドのPATH_METADATAから、@Get(routePath)などで指定したルートパスを取得
    const prototypeCallback = prototype[methodName];
    const routePath = Reflect.getMetadata(PATH_METADATA, prototypeCallback);
    if (isUndefined(routePath)) {
      return null;
    }

    // @Getや@Postなどで指定しMETHOD_METADATAで管理されたHTTPメソッド名を取得
    const requestMethod: RequestMethod = Reflect.getMetadata(
      METHOD_METADATA,
      prototypeCallback,
    );

    // @Versionデコレータで指定したバージョン値を取得
    const version: VersionValue | undefined = Reflect.getMetadata(
      VERSION_METADATA,
      prototypeCallback,
    );

    // ルートパスを必要に応じて加工
    const path = isString(routePath)
      ? [addLeadingSlash(routePath)]
      : routePath.map((p: string) => addLeadingSlash(p));


    // まとめて返却
    return {
      path,
      requestMethod,
      targetCallback: instanceCallback,
      methodName,
      version,
    };
  }
}
```

::: note info
`PathsExplorer`はコントローラクラスのインスタンスから、ルートパスやハンドラ関数の実体などを取得し、ルート定義情報を生成する。
:::


# まとめ

今回の記事では、NestJSのルーティング処理における2つの重要なクラス、`RoutesResolver`と`RouterExplorer`について詳しく見てきました。

まず、`RoutesResolver`は、アプリケーション内のすべてのモジュールとコントローラを取得し、それらのルート定義を整理する役割を果たします。このクラスは、各コントローラが持つルートパスやHTTPメソッドの情報を集約し、`RouterExplorer`に渡す準備を整えます。

次に、`RouterExplorer`は、`RoutesResolver`から受け取ったルート定義をもとに、実際のルーティング処理を行います。具体的には、`express.Router`などのHTTPサーバーに対して、ルートとそのハンドラを登録します。このプロセスにより、デコレータを通じて宣言されたルートが正しくHTTPサーバーへ反映されるようになります。

①-③でNestJSのルーティング動作については大体見えてきました。次回からは、`@Injectable`デコレータ関連の実装を起点にして、NestJSのもう一つの重要な要素である依存性注入（DI）の理解を深めることにします。