この記事はアイスタイル Advent Calendar 2022 25日目の記事です。
はじめに
じゃんたま 雀士★1になりました。
はじめまして、アイスタイルT&C部に所属しているokashitayというものです。
今回の記事では、こないだまでNestJSどころかNode.jsアプリケーションでの開発実績も特別ない者でしたが、ここに実体験を元に知見をいくつか残していこうと思います。
NestJS採択の経緯
商品データ基盤APIは何らかの事情でPHP版とGO版が存在しています。
両方とも言語・フレームワークともEOL(End Of Life)に近い状態...2つのAPIのバージョンアップしつつAWSへ移行する労力、2種類の言語のAPIに機能追加・メンテナンスしていくことは開発者の負荷とAWS移行コスト&運用コストも高まるという状況を解消するため1つの言語に統一する方針へ、言語とフレームワークの最終的な決め手は公式ドキュメントが充実してあるのと、TypeScriptで品質と読みやすさを維持しつつバックエンドを構築できる点が大きい理由からNestJSを採択しAWS Fargate(ECS)に移行することにしました。
※商品データ基盤APIは
@cosmeと他多数のサービスから使用されている商品情報を取り扱うAPIです。
NestJS利用してみての感想
NestJSのDIを利用しModuleシステムとレイヤードアーキテクチャを意識した構造によって
自然と可読性は向上させることができました。またPHP、Goで開発されたコードよりもだいぶメンテナンスもしやすくなりました。ただこれはPHP、Goが劣っているわけでなく、当時に比べると今はソフトウェアアーキテクチャの情報とノウハウをネット上に残してくれている先人たちのおかげだと思います。
ここがイマイチかなと思ったところは、Module同士の関係性を気をつけて組み込まないと複雑な構造になってしまう点は注意が必要なところと、大規模なアプリケーションになっていくとビルド速度が気になるなといったところでしょうか。
NestJS on ECSでの学び
使用するライブラリ内部コードもなるべく見る
これはNestJSだからというわけではないですが、ライブラリのREADMEファイルだけでは細かい利用方法まで見えてこない状況があると思います。そういう場合は雰囲気で進めず、直にコード内部を直に見ていくことが重要だなと感じました。
たとえばclass-validatorは基本的なバリデーションは上記で記載されたデコレータで検証できます。
ただし@IsNumber(options: IsNumberOptions)
のoptionsを設定すると
どういう動きをするの?などある場合があります。
こういった場合、ライブラリ内部のコードを追ってどう実装しているのか見ると
使い方でなくライブラリの作りやTypescriptの書き方も学べたりします。
// NaN、Inifinity検証、小数点以下の桁数検証
export interface IsNumberOptions {
allowNaN?: boolean;
allowInfinity?: boolean;
maxDecimalPlaces?: number;
}
if (maxDecimalPlaces !== undefined) {
let decimalPlaces = 0;
if (value % 1 !== 0) {
decimalPlaces = value.toString().split('.')[1].length;
}
if (decimalPlaces > options.maxDecimalPlaces) {
return false;
}
}
class-validator IsNumber.ts より抜粋
NestJSアプリケーションのGraceful Shutdown
ECSで正常にアプリケーションをシャットダウンする必要があります。こちらはNestJSのフレームワークで準備されていますので、そちらを呼び出すことで制御できます。
デフォルトでは無効になっていますので、お気をつけください。
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// デフォルトでは無効になっています
// 以下を追加し有効にしてください
app.enableShutdownHooks();
await app.listen(3000);
}
AWS FargateのCPU メモリ構成
Dockerコンテナの原則「1コンテナ1プロセス」とNode.jsの特徴のCPUは1コア分までしか使えない、1プロセスが保持するヒープメモリは1.5GBという制限がありますので注意してください、AWS Fargateコンピューティングとメモリのリソース構成は1vCPU(1024CPUユニット)、メモリ2GBを選択、アクセス数が増えてもオートスケールでタスクを増やしていくことで、水平にスケールされパフォーマンス・可用性を高めることができます。
またNewRelicサイドカー(256CPUユニット)を配置する場合は、Node.jsが1vCPUの性能をフルに活かせない可能性がありすが、ECSタスクを増やすことにより対処できます。それ以上のサイドカー(FireLensなど)を追加する場合は、Node.jsがパフォーマンスを出せない可能性が高いと思いますので性能試験をしつつリソース構成を調整してください。
ALBの502エラーに陥る
ALBとNode.jsのkeepAliveTimtoutが異なると発生します。
ALBはアイドルタイムアウトで設定した時間が経過するまでデータが送受信されなかった場合、接続を閉じますのでNestJS側のKeepAliveTimeをALBより長く設定してください。
import { HttpAdapterHost } from '@nestjs/core';
import { Server } from 'node:http';
export class AppModule implements NestModule, OnApplicationBootstrap {
constructor(private readonly refHost: HttpAdapterHost<any>) { }
// keepAliveTimeoutを設定
onApplicationBootstrap() {
const server: Server = this.refHost.httpAdapter.getHttpServer();
server.keepAliveTimeout = 61 * 1000;
server.headersTimeout = 65 * 1000;
}
configure(consumer: MiddlewareConsumer) {
}
}
これに関しては性能試験中、なぜ稀に502エラーを返却するのか解決できず困っていました。
調べていくと以下のmediba Creator×Engineer Blogの「Node.js(Express)サーバ運用とタイムアウト」という記事に辿り着き解決の糸口を見いだすことができました。
今まで業務に追われ無意識に誰かが書いてくれた記事をみて、あたかも自分が問題を解決した気になっていましたが、この記事を書いている最中に改めて共有する文化の重要性に気づかせてくれました。
誰かが何らかしらの技術内容を共有することは、エンジニアの知見・技術向上だけでなく、
開発中プロダクトの問題点も改善しユーザーにより良いものを届けることができ、
それが自分たちの携わるサービス・事業の成長にも繋がっていくのかなと思います。
もちろん技術だけでなく何かをアウトプットすることは、それが時にその人の人生にさえ少なからず影響をあたえていくんじゃないかなと思いつつも、この文化がシンギュラリティによってどういう変化をしていくか気になるところです。
最後に
他の部署ではバックエンドにExpress+TypeScriptを採用しているのは遠目でみていたのですが、自分の所属部グループではNode.jsアプリケーション開発経験者もいなく、ぼっち...でした。ですが、グループ内の開発チームメンバーの方にNestJSの導入して進めていくことを快く受け入れてもらい(たぶん)、インフラ・DBA、各サービス担当者の協力のおかげで、@cosme
と各サービスはAWSに移行したAPIに順次切り替えて使っていただくところです。ほんとに関係各所に感謝です。thank you 〜
この記事に載せたメモ的な内容が、この先に誰かの役に立つきっかけになれば嬉しく思います。それではよいお年を〜