Slide 1

Slide 1 text

© Commune Inc. All rights reserved NestJSのコードから OpenAPIを 自動生成する際の最適解を探す 2024/03/17 NestJS meetup #6 浅見 達也@astatsuya1 1

Slide 2

Slide 2 text

© Commune Inc. All rights reserved 浅見 達也 (@astatsuya1) ● 仕事 ○ コミューン株式会社 SuccessHub事業部 ■ 2022年7月~2024年3月 CommuneのWebエンジニア ■ 2024年4月~ SuccessHubのWebエンジニア ● ペット ○ デグー 自己紹介 2

Slide 3

Slide 3 text

© Commune Inc. All rights reserved 3 チームの生産性を圧倒的に向上し、カスタマーサクセス組織に成果をもたらす 顧客に関するあらゆる重要な 情報や健康状態を一元管理 テックタッチの工数で ハイタッチの効果を実現 顧客の状態に応じた個別最適な アクションを実行管理

Slide 4

Slide 4 text

© Commune Inc. All rights reserved ● NestJSのコードからOpenAPIを自動生成する際の最適解を探す ○ これまでやってきて苦戦したことを共有 ○ 道半ばながら辿り着いた最適解を共有 今日の発表内容 4

Slide 5

Slide 5 text

© Commune Inc. All rights reserved ● 技術構成 ○ バックエンドはNestJS(monorepo, SWC)でREST API ○ class-validator, class-transformerを使ってAPIリクエストのバリデーション や変換を行っている ○ @nestjs/swaggerを使ってソースコードからOpenAPIを出力 ○ フロントエンド(今回関係ない) ■ Orvalを使ってOpenAPIからフロントエンドで使用するコード(型定義、データフェッチ、モックサーバー)を 自動生成 ■ ブラウザからNestJSにHTTPリクエスト(BFFはない) 前提となる技術構成 5

Slide 6

Slide 6 text

© Commune Inc. All rights reserved 過去やってきたことを紹介します 1. @nestjs/swagger CLI Pluginの導入をチャレンジ(失敗) a. 実装中にローカルサーバーがエラーで止まりまくる 2. @nestjs/swagger CLI Pluginを使わず、手動でデコレータを付ける(採用) a. 仕組みは出来たが不満あり 今までやってきた OpenAPIの自動生成への取り組み 6

Slide 7

Slide 7 text

© Commune Inc. All rights reserved ParametersとRequest bodyの出力 4. @nestjs/swagger CLI Pluginを使わず、手動でデコレータを付ける 7 ● リクエストボディ、リクエストパラメータは元々 dtoがあったので、ひたすら @ApiPropertyを つけるのみ ● この例は単純だが、他の dtoなどをimportし て型定義に使っている場合はもっと色々記 述する ○ 結構重要なポイントだが今回は割愛

Slide 8

Slide 8 text

© Commune Inc. All rights reserved Responseの出力のためにClassSerializerを剥がす 4. @nestjs/swagger CLI Pluginを使わず、手動でデコレータを付ける 8 ● ClassSerializerはクラスインスタ ンスをプレーンオブジェクトに変 換してくれる ● 元々エンティティに変換処理のた めのデコレータをつけることがみ んな微妙だと思っていた(コント ローラーで行うべき処理) ので、あまりためらいなく剥がし た

Slide 9

Slide 9 text

© Commune Inc. All rights reserved Responseの出力のためにdto作成 4. @nestjs/swagger CLI Pluginを使わず、手動でデコレータを付ける 9 ● レスポンスは新たにレスポンス用の dtoを作 成 ● レスポンスではバリデーションを行わない ので、OpenAPIのためだけに書いている

Slide 10

Slide 10 text

© Commune Inc. All rights reserved Response出力のためにコントローラーにデコレータを追加 4. @nestjs/swagger CLI Pluginを使わず、手動でデコレータを付ける 10 ● コントローラーの関数に @ApiOkResponseのデコレータを 追加 ○ これをつけるとOpenAPIの Responseが出力される ● コントローラーの関数の戻り値の型 にdtoを指定することで型安全にす る ○ dtoにあるプロパティを書き忘 れたらエラーになる

Slide 11

Slide 11 text

© Commune Inc. All rights reserved 記述が多くて面倒だが、理論上完璧 4. @nestjs/swagger CLI Pluginを使わず、手動でデコレータを付ける 11

Slide 12

Slide 12 text

© Commune Inc. All rights reserved ● OpenAPIのためのデコレータの記述を間違えがち ● 間違っていてもIDE上でエラー等は出ないので、気をつける、ちゃんと確認 する以外のソリューションがない ● フロントエンド開発中にミスに気がつくと、バックエンドのコード修正をまず やらないといけないのが面倒 ○ OpenAPIからフロントエンドのコードを自動生成をしているため、フロ ントエンドの型定義は手動で直せない 半年ほど運用してみて 12

Slide 13

Slide 13 text

© Commune Inc. All rights reserved かなり単純な記述でも忘れがち 今まで取り組んできたことの流れ 13

Slide 14

Slide 14 text

© Commune Inc. All rights reserved ● @nestjs/swaggerのCLI Plugin導入に再チャレンジ ○ NestJSのドキュメント通りに実装したところ ■ 上手く出来ている ● ローカルサーバーは動いている ● OpenAPIのParameters, Request body, Responseはおおよそ出力されている様 子 ■ 上手く出来ていない ● 12件型エラーが出ている ● コード変更しているとたまに型エラーが出る ● ローカルサーバーの起動時間が約 3秒→約27秒になった 改善に向けて 14

Slide 15

Slide 15 text

© Commune Inc. All rights reserved ○ 全て同じファイルにdtoが複数ある場合だった ■ 単にファイル分割で対応完了 これでエラーがなくなった! 12件型エラーが出ている 15

Slide 16

Slide 16 text

© Commune Inc. All rights reserved ● ファイル追加や削除、変数名を変更した場合にエラーが出やすい ○ ローカルサーバーは止まっていないようなのでエラーを無視すれば開発自 体は出来るが気になる ● エラー自体が起きないようにするのは難しそう エラーが起きたら復旧出来るようにする コード変更しているとたまに型エラーが出る 16

Slide 17

Slide 17 text

© Commune Inc. All rights reserved ● エラーを起こしているmetadata.tsを上書きする単純なスクリプトを作成 ○ metadata.tsはCLI Pluginを使うと生成されるOpenAPIのためのファイル ○ エラーが起きたらこのスクリプトを実行してリセット スッキリ解決はしてないけど、これでストレスはかなり軽減 コード変更しているとたまに型エラーが出る 17

Slide 18

Slide 18 text

© Commune Inc. All rights reserved ● 流石に許せない ○ SWCを入れる前と同じくらい時間がかかっている OpenAPIを立ち上げないコマンドと、OpenAPIを立ち上げるコマンドを用意する ローカルサーバーの起動時間が約 3秒→約27秒になった 18

Slide 19

Slide 19 text

© Commune Inc. All rights reserved ● OpenAPIを生成するときとしないときのnest-cliを作成 ○ nest-cli.json(しない)とnest-cli-openapi.json(する)を作成 ○ コマンド実行時にファイルパスを指定する ローカルサーバーの起動時間が約 3秒→約27秒になった 19 SwaggerModule

Slide 20

Slide 20 text

© Commune Inc. All rights reserved ● OpenAPIを使わない場合はSwaggerModuleも読み込まない ○ metadata.tsを読み込まなければ速度はあまり変わらないが、不完全な OpenAPIが見たいこと はない、混乱の元なので全部読み込まない ● npm scripts(コマンド長い) ローカルサーバーの起動時間が約 3秒→約27秒になった 20 SwaggerModule

Slide 21

Slide 21 text

© Commune Inc. All rights reserved コマンドの使い分け ● 普段のバックエンド開発では OpenAPIは生成しないで立ち上げ ○ バックエンド開発中は OpenAPIをあまり見ないため ○ ローカルサーバーは安定して欲しい、何度も起動するので速度も重要 ● OpenAPIを見たい時、変更するときは OpenAPIを生成して立ち上げ ○ 実装中にエラーが出たときはリセットコマンドで回避可能 ○ プロダクションビルド (nest build)も遅くなるが、サーバーの起動速度はほぼ変わらなかったの で問題なし 少し苦しいがなんとか課題解決! ローカルサーバーの起動時間が約 3秒→約27秒になった 21 SwaggerModule

Slide 22

Slide 22 text

© Commune Inc. All rights reserved ● dtoにあるOpenAPIには不要なプロパティに @ApiHidePropertyをつけていく ○ toNameのような変換関数が主に不要な値 ● 不要になった@ApiPropertyを消していく ○ あっても問題ない。@ApiPropertyに記述してある値 が優先的に使われる ○ 型定義が複雑な場合は引き続き指定する必要があ る 残りの作業は @ApiHidePropertyをつけていく 22

Slide 23

Slide 23 text

© Commune Inc. All rights reserved これで一通り完了! 23

Slide 24

Slide 24 text

© Commune Inc. All rights reserved ○ dtoは1ファイルに1つのみ記載する ○ dtoの各プロパティには基本的に@ApiPropertyをつけない。上手く出力されな い場合にのみ@ApiPropertyを使って正しい値を自分で記述する ○ dtoにあるOpenAPIには出力したくないプロパティには@ApiHidePropertyをつ ける 開発時に気にすることまとめ 24

Slide 25

Slide 25 text

© Commune Inc. All rights reserved ● OpenAPIのために技術的な選択が必要だった ○ ClassSerializerを剥がしたが、剥がしたくない場合は積んでいたかも ● コードからドキュメント(OpenAPI)が自動生成される一番嬉しい状態が作れた ● 快適なローカル開発環境との両立は許容できる 総じて満足 道半ばながら辿り着いた最適解振り返り 25

Slide 26

Slide 26 text

© Commune Inc. All rights reserved NestJSとOpenAPIの話は以上です 最後に本件と関係ないけど気になっていること 26

Slide 27

Slide 27 text

© Commune Inc. All rights reserved ● NestJSでそこまでコード量が多くないのにモジュールの読み込みに結構時間が かかるのをどう対処しているのか ● 漏れがなく、負荷が少ない操作ログの設計 ● MySQL, Prismaを使ってマルチテナントの実装する場合の工夫 ● Visual Regression Testで何をどこまで確認しているか この後の懇親会で相談させてください!! 最後に本件と関係ないけど気になっていること 27

Slide 28

Slide 28 text

募集中の求⼈はこちら カジュアル⾯談も 実施しています! コミューン株式会社では共に働く仲間を募集中です ありがとうございました