虎の穴開発室ブログ

虎の穴ラボ株式会社所属のエンジニアが書く技術ブログです

MENU

OpenAPIで試すスキーマ駆動開発 #虎の穴ラボ Advent Calendar 2023

  • 本記事は 虎の穴ラボ Advent Calendar 2023 12日目の記事です。(予約投稿)
  • 前回はtzさんによる「笑って泣いて、品質改善:新規開発プロダクトのローラーコースター」でした。
  • 次回はY.N.さんによる「モダンなshell 「Nushell」 を触ってみる」です。ご期待ください!

趣味開発の困り事

とらのあなラボでエンジニアをしている浜田です。
皆様は趣味で開発を行う際、仕様書は作成されるでしょうか?
私の場合、思いつくまま実装を進めてしまい、仕様書のような資料はほとんど作っていませんでした。
その結果、以下のような問題が表面化してきました。
・APIのエンドポイントのURLを忘れる(自分で作ったのに)
・APIのパラメータを忘れる(自分で作ったのに)
・そもそもAPIを作ったことすら忘れる(自分で作ったのに)

特に開発に空白期間が出来てしまった場合、APIの仕様を調べるためにプログラムを解析するという作業が発生するようになってしまいました。
仕様書があれば上記のような問題に対処できると考えましたが、仕様書を作るのであればきちんと管理しないと仕様書と実装に齟齬が生じてしまうのが難点でした。
そこで「仕様書と実装を一元管理」するためにスキーマ駆動開発を試してみることにしました。

スキーマ駆動開発とは

スキーマ駆動開発とは システム開発において複数のシステムを結合する際に共通となる標準的なスキーマを用いてインターフェースを設計し開発を進める手法の一つです。 WebAPIの定義記述においてはOpenAPI、プロトコルとしてGraphQL, gRPCなどが存在します。

DX技術用語辞典より引用

ということで、端的にまとめると「APIやDBの構造(スキーマ)を定義してから実装を進める」開発手法になります。
スキーマ駆動開発を取り入れることで「仕様書からコードを自動生成する」という開発フローが実現でき、仕様書と実装の齟齬を防ぐことができるようになります。

OpenAPIで試すスキーマ駆動開発

今回はGoとReactを利用してスキーマ駆動開発を行います。 作成するアプリはフォームに名前を入力したら、「こんにちは、xxさん」とレスポンスを返却するWebAPIを作成します。
プロジェクトの最終的なディレクトリ構成はGitHubを参照して下さい。
github.com

OpenAPIを利用して仕様書を作成する

OpenAPIの仕様書はVSCodeを利用して作成しました。
以下の拡張機能を導入することで、VSCode上で仕様書をプレビューしながら作成することが出来ます。

導入した拡張機能
OpenAPIの仕様書をプレビューしながら作成出来ます

作成した仕様書は以下になります。

openapi: "3.0.0"
info:
  version: 1.0.0
  title: スキーマ駆動開発テストAPI 
servers:
  - url: http://localhost:1323/
paths:
  /greeting:
    post:
      summary: 名前を受け取り、挨拶を返答します
      operationId: greeting
      tags:
        - api
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GreetingRequest"
      responses:
        '200':
          description: 名前を含めた挨拶を返却する
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GreetingResponse"
        '500':
          description: サーバー内部エラー
          content:
            application/json:
              schema:
                example: Internal Server Error
                type: string
components:
  schemas:
    GreetingRequest:
      type: object
      required:
        - name
      properties:
        name:
          example: とらラボ
          type: string
          x-go-type: string
    GreetingResponse:
      type: object
      required:
        - name
      properties:
        greeting:
          example: こんにちは、とらラボさん
          type: string
          x-go-type: string
    Error:
      example: server error
      type: string
      x-go-type: string

Goでサーバーサイドを作成する

サーバーサイドはGoを利用して作成します。WebAPIフレームワークとして「Echo」を利用することにしました。

Echoプロジェクトの作成

以下のコマンドを利用してEchoのプロジェクトを作成します。

$ mkdir server && cd server
$ go mod init sdd_test
$ go get github.com/labstack/echo/v4

serverディレクトリ内にmain.goを作成し、以下を記述しました。

package main

import (
    "net/http"

    "github.com/labstack/echo/v4"
)

func main() {
    e := echo.New()
    e.GET("/", func(c echo.Context) error {
        return c.String(http.StatusOK, "Hello, World!")
    })
    e.Logger.Fatal(e.Start(":1323"))
}

この状態でmain.goを実行し、サーバーが稼働したか確認します。
実行後、「http://localhost:1323」にアクセスし、画面のような結果が返却されたか確認します。

実行結果(POSTMANを利用)

コードジェネレータの導入

OpenAPIの仕様に基づいて仕様書を作成することで、仕様書からプログラムを自動生成できるようになっています。
今回は「oapi-codegen」を利用することにしました。 まずは以下のコマンドでコードジェネレーターを導入します。

go install github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen@latest

次に、以下のコマンドで仕様書からGoのプログラムを出力します。

oapi-codegen -package main openapi.yaml > openapi.gen.go

ハンドラーの定義と登録

自動生成されたプログラム内にinterfaceとしてURL毎のハンドラーが定義されているため、この実装を行います。
今回は名前を受け取って挨拶を返却するだけなので、以下のようなプログラムとしました。

type AppHandler struct{}

// ハンドラーの実装
func (h AppHandler) Greeting(ctx echo.Context) error {
    var param GreetingRequest
    err := ctx.Bind(&param)
    if err != nil {
        return ctx.JSON(http.StatusInternalServerError, err.Error())
    }

    greeting := "こんにちは、" + param.Name + "さん"
    var result GreetingResponse
    result.Greeting = &greeting
    return ctx.JSON(http.StatusOK, result)
}

次に作成したハンドラーを登録します。
自動生成されたプログラム内にRegisterHandlers関数が用意されているため、この関数を利用することで定義したハンドラーとURLの紐づけが設定されます。

func main() {
    e := echo.New()
    e.Use(middleware.CORS())

    // ハンドラーを登録する
    handler := AppHandler{}
    RegisterHandlers(e, &handler)

    e.Logger.Fatal(e.Start(":1323"))
}

この時点で、もう一度動作テストを行います。 POSTMANを利用してURLにパラメータを送信します。

パラメータを渡して実行した結果

以上でサーバーサイドの実装は終了です。

Reactでクライアントを作成する

クライアントはReactとTypeScriptを利用します。

Reacrプロジェクトの作成

まずは以下のコマンドでReactのプロジェクトを作成します。 コマンド実行後の質問については「Would you like to use Tailwind CSS?」のみNoを選択し、ほかはデフォルトの設定を選択しました。

$ npx create-next-app@latest

コードジェネレータの導入とプログラム出力

次にOpenAPIの仕様書からTypeScriptのプログラムを出力するためのコードジェネレータをインストールします。 今回は「openapi-typescript-codegen」を利用することにしました。

$ npm install openapi-typescript-codegen --save-dev

インストール完了後、以下のコマンドでプログラムを生成しました。 generatedディレクトリが作成され、生成されたプログラム一式が格納されます。

$ openapi --input ../openapi.yaml --output ./generated

フロントエンドの修正

実際にAPIサーバーにアクセスし、挨拶を取得する処理を作成します。 今回は「axios」を利用してAPIサーバーと通信するため、以下のコマンドでインストールを行います。

$ npm install axios

Reactプロジェクトの生成時に作成された「app/page.tsx」を以下のように修正します。 名前の入力フォームと送信ボタンを作成し、APIから返却された挨拶文を表示します。

"use client"

import { GreetingRequest, GreetingResponse } from '@/generated'
import axios, { AxiosResponse } from 'axios';
import { useState } from 'react';

export default function Home() {

  const [name, setName] = useState<string>("")
  const [greeting, SetGreeting] = useState<string>("")

  function handleClick(){

    const postData : GreetingRequest = {name: name};

    try {
      axios.post("http://localhost:1323/greeting", 
      postData, {
        headers: {
          'Content-Type': 'application/json',
        },
      })
      .then((response: AxiosResponse<GreetingResponse>) => {
        const responseData: GreetingResponse = response.data;
        SetGreeting(responseData.greeting ?? '');
      });

    } catch (error) {
      console.error('POSTエラー:', error);
      throw error;
    }
  }

  function handleChange(event: React.ChangeEvent<HTMLInputElement>){
    setName(event.target.value);
  }

  return (
    <>
      <label>名前<input type='text' onChange={handleChange}></input></label>
      <button onClick={handleClick}>送信</button>
      <span>{greeting}</span>
    </>
  )
}

動作確認

以上でサーバー、フロントエンドともにプログラムの作成が完了しました。 それぞれを動作させた状態で以下のURLにアクセスします。

http://localhost:3000/

http://localhost:3000にアクセスした結果

名前を入れて送信ボタンを押すと、挨拶が表示されます。

レスポンスとして挨拶文が取得されて、画面に表示されました

終わりに

今回は趣味開発にスキーマ駆動開発を取り入れた話をさせていただきました。
仕様書と実装の乖離は趣味開発だけでなく、実際のシステム現場でも度々発生する事象では無いでしょうか。
スキーマ駆動開発を利用することで仕様書からコードを生成するフローが実現でき、仕様書と実装に齟齬が生じるのを防ぐことが出来ます。 仕様書と実装を一元管理したいと考えている方がいらっしゃれば、スキーマ駆動開発を試してみることをおすすめします。

採用情報

虎の穴では一緒に働く仲間を募集中です!
この記事を読んで、興味を持っていただけた方はぜひ弊社の採用情報をご覧下さい。
カジュアル面談やエンジニア向けイベントも随時開催中です。ぜひチェックしてみてください♪
yumenosora.co.jp