📜

Luup Serverチームの開発生産性向上の取り組み:ZodとOpenAPIの共存 & 生成AIの活用

2024/11/12に公開

こちらの記事は、LUUP のTVCM放映に合わせた一足早い「Luup Developers Advent Calendar 2024」の12日目の記事です。

こんにちは! Luup Serverチーム所属の安元です。私たちServerチームでは、開発の効率と品質を向上させるためにさまざまな取り組みを行っています💪 その一環として、APIドキュメントの整備に関してZodとOpenAPIを共存させる方針を採用しました。また、生成AIの利用を通じて、開発者体験の改善にも取り組んでいます。この記事では、その具体的な取り組みについてサンプルコードとともに紹介します。

ZodとOpenAPIの共存によるAPIドキュメント管理

APIのドキュメントを開発者とクライアント双方が理解しやすい形で提供することは、チームの生産性向上に不可欠です。LUUPにはモバイルアプリのクライアントが3つあり、それ以外にも複数のWebアプリケーションが存在し、それぞれが並行して開発・運用されています。このような多様なクライアントに対して、統一されたAPIドキュメントを提供することが特に重要です。そのため、TypeScriptのスキーマ定義にZodを利用しつつ、それをOpenAPI形式に変換することで、APIドキュメントを自動生成する仕組みを導入しています。

ZodとOpenAPIの役割

  • Zod: TypeScript環境でのスキーマ定義とバリデーションに使用しています。Zodを使うことで、型安全なスキーマ定義が可能となり、バリデーションエラーが早期に検知できます。
  • OpenAPI: 外部の開発者やチーム内の他のメンバーに対してAPIの構造を共有するために使用します。クライアント開発者はこのドキュメントを参照してAPIを呼び出し、正確なデータのやり取りをします。

Zodのスキーマ定義をそのまま利用してOpenAPIのドキュメントを生成することで、スキーマとドキュメントを両方管理していくコストを避け、よりメンテナブルなプロジェクト構成を実現しています。

サンプルコード

以下に、Zodで定義したスキーマからOpenAPIドキュメントを生成する例を示します。

import { z } from "zod";
import {
  extendApi,
  generateOpenApiDocument,
} from "@asteasolutions/zod-to-openapi";

// ユーザースキーマの定義
const UserSchema = z
  .object({
    id: z.string().openapi({
      description: "ユーザーID",
      example: "123",
    }),
    name: z.string().openapi({
      description: "ユーザー名",
      example: "John Doe",
    }),
    email: z.string().email().openapi({
      description: "メールアドレス",
      example: "john.doe@example.com",
    }),
  })
  .openapi("UserSchema");

このように、Zodで定義したスキーマに.openapi()メソッドで説明や例を付与することで、より詳細なドキュメントを生成できます。また、Zodのスキーマを直接OpenAPIに変換することで、APIドキュメントの整備を自動化し、コードとドキュメントの整合性を保つことができます。

Swagger JSDocを使用したoas.yamlの生成

さらに、私たちはswagger-jsdocを利用してJSDocコメントに@openapiを付与し、OpenAPI仕様に基づいたoas.yamlを自動生成しています。これにより、既存のコードに対しても手軽にAPIドキュメントを追加できるようになっています。

Swagger JSDocの利用方法

以下に、Swagger JSDocを使ってJSDocからOpenAPIドキュメントを生成する例を示します。

/**
 * @openapi
 * /users:
 *   get:
 *     summary: Retrieves a list of users
 *     responses:
 *       200:
 *         description: A list of users
 *         content:
 *           application/json:
 *             schema:
 *               type: array
 *               items:
 *                 $ref: '#/components/schemas/User'
 */
function getUsers(req, res) {
  // ユーザー一覧を返す処理
  res.json([
    /* ユーザーデータ */
  ]);
}

このように、JSDocに@openapiを付与することで、コードとドキュメントを密に連携させることが可能になります。また、swagger-jsdocを使用してJSDocからoas.yamlファイルを生成し、ドキュメントの整備を効率化しています。

Swagger JSDocからoas.yamlを生成するサンプルコード

以下に、swagger-jsdocを使ってJSDocからoas.yamlファイルを生成するサンプルコードを示します。

import swaggerJSDoc from "swagger-jsdoc";
import { OpenApiGeneratorV3 } from "@asteasolutions/zod-to-openapi";
import { UserSchema } from "./schemas/UserSchema";
import fs from "fs";

const OPEN_API_VERSION = "3.1.1";
const DOCUMENT_VERSION = "1.0.0";

const openApiDocFromZodSchema = new OpenApiGeneratorV3([
  UserSchema,
]).generateDocument({
  openapi: OPEN_API_VERSION,
  info: {
    title: "Open api doc from zod schema",
    version: DOCUMENT_VERSION,
  },
});

// Swagger JSDocの設定
const options = {
  definition: {
    openapi: OPEN_API_VERSION,
    info: {
      title: "User API",
      version: DOCUMENT_VERSION,
    },
    components: {
      schemas: {
        User: userSchemaOpenApi,
      },
    },
  },
  apis: ["./**/*.js"], // JSDocコメントを含むファイルのパスを指定
};

// OpenAPI仕様のドキュメントを生成
const openapiSpecification = swaggerJSDoc(options);

// oas.yamlファイルとして保存
fs.writeFileSync("oas.yaml", JSON.stringify(openapiSpecification, null, 2));

console.log("oas.yamlファイルが生成されました。");

このコードを使うことで、JSDocから自動的にoas.yamlファイルを生成できます。swagger-jsdocを利用することで、JSDocコメントを元にしたAPIドキュメントの自動生成が簡単に行えます。
ZodスキーマをOpenApiGeneratorV3でOpenAPI形式に変換し、そのスキーマをSwagger JSDocのcomponents.schemasに設定しています。これにより、Zodで定義されたスキーマをSwagger JSDocで利用できるようになり、APIドキュメントの自動生成がさらに効率的になります。

生成されるoas.yamlの例

以下は、上記のサンプルコードで生成されるoas.yamlの一例です。

openapi: "3.1.1"
info:
  title: "User API"
  version: "1.0.0"
paths:
  /users:
    get:
      summary: "Retrieves a list of users"
      responses:
        '200':
          description: "A list of users"
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/UserSchema'
components:
  schemas:
    UserSchema:
      type: object
      properties:
        id:
          type: string
          description: "ユーザーID"
          example: "123"
        name:
          type: string
          description: "ユーザー名"
          example: "John Doe"
        email:
          type: string
          format: email
          description: "メールアドレス"
          example: "john.doe@example.com"
      description: "ユーザー情報のスキーマ"

生成AIの活用によるドキュメント作成の効率化

もう1つの取り組みとして、生成AIを活用したドキュメント作成支援があります。例えば、APIのエンドポイント説明やサンプルリクエストの生成にAIを活用することで、手間を削減し、開発者の負担を減らしています。

特に私たちはGitHub Copilotを利用して、JSDocコメントやスキーマ定義の自動生成しています。Copilotは開発者がコードを書く際にリアルタイムで提案し、ドキュメントの整備も含めた多くの作業を効率化しています。

以下のようなケースでは生成AIが役立っています。

  • APIの説明文の自動生成: OpenAPIドキュメントに含まれるエンドポイントの説明文を、生成AIを使って自然な文章で記述する。
  • サンプルコードの生成: 特定のエンドポイントに対するリクエストとレスポンスのサンプルコードをAIで生成し、クライアント開発者が参照できるようにする。
  • スキーマ定義の補完: Copilotにより、スキーマ定義のバリデーションやプロパティの補完を簡単に行うことで、スキーマの作成を迅速化。

簡単な補完イメージのサンプルです。

generate-schema

generate-jsdoc

これにより、開発者はドキュメント作成の手間を省き、コアの開発作業に集中できます。

まとめと今後の課題

ZodとOpenAPIを組み合わせたアプローチにより、APIのスキーマ管理がシンプルになり、開発者の生産性向上に寄与しています。また、生成AIを活用したドキュメント作成の自動化により、ドキュメント整備のコストが削減されました。しかしまだまだ改善の余地もあり、たとえば生成AIが必ずしも正確な説明を生成しない場合や複雑なスキーマに対しての生成力が弱かったりと、開発者によるレビューやテコ入れは必要です‥😣

今後は、生成AIの活用方法の向上とともに、さらに効率的なドキュメント管理手法を模索していきたいと考えています!😁✨

最後に

Luup では、一緒に開発してくださるソフトウェアエンジニアを積極的に募集しています。
カジュアル面談も実施しておりますのでぜひお気軽にお声掛けください。
https://recruit.luup.sc/

参考リンク

Luup Developers Blog

Discussion