【ハッカソン向け】Openapi Generatorのコード生成を使えばフロントとバックの接続は瞬【Node js】

この記事は、法政大学アドベントカレンダー2021の3日目の記事です。

https://adventar.org/calendars/6528

こんにちは、reudです。

みなさ〜〜〜ん、ハッカソン出てますか〜〜〜〜

ハッカソンに出よう!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

ということで、ハッカソンに出ようとしてたら出てたりする人達、こんにちは。

ハッカソン、とても楽しいのですが、ハッカソンに出ている人全員が思うことってただ一つ。アレですよね?

・ ・ ・

今回はその様な悩みを解決する様なツールについて話をしていきたいと思います。

え?そんなの存在しないだろ!って? あるんですよこれが。

なんで開発時間って足りなくなるの?

そもそも何故開発時間って足りなくなるのでしょうか? この原因から調べていこうと思います。

雑にフロントエンドとバックエンド(APIサーバ)が別れている様な、今時のアプリケーションをハッカソンで開発したと仮定します。

  • 謎のバグを踏んだ!
  • ハマった!
  • 環境構築で躓いた!
  • コードの記述量が多くて大変!
  • フロントエンドとバックエンドの認識ズレにより手戻りが多発した!

ざっと思いついただけで、これくらいでしょうか。

これらを解決すれば、開発時間をキュッと短縮出来ることでしょう。

今回はこれら全部・・・とは行きませんが下二つの問題をサッと解決できる手段について紹介したいと思います。

つまり、

  • コードの記述量が多くて大変!
  • フロントエンドとバックエンドの認識ズレにより手戻りが多発した!

この二点が解決されるということです。

幸福がそこに、ある。

この記事で話すこと

  • OpenAPI Generatorの紹介
  • OpenAPIについての軽い説明
  • OpenAPI Generatorを用いたサーバサイド側のコードの生成【NodeJS Express】  

この記事で話さないこと

  • OpenAPI Generatorを用いたクライアント側のコード生成
    • ググるとそれなりの量解説記事が出てくるので

OpenAPIについて

OpenAPI Generator について語る前に、OpenAPIについての話をする必要があります。 これは避けられません。

OpenAPI仕様はRESTful APIのインターフェースの仕様として振舞います。

OpenAPI仕様を使って作成したドキュメントをOpenAPI定義と呼びます。

何が得かというと、そのサービスの機能をOpenAPI仕様で書かれたOpenAPI定義を見るだけで理解することが出来るようになります。

つまり、ソースコードや仕様書などを見なくても、OpenAPI定義を見るだけで内容が理解できるということです。

OpenAPI定義を書いてみる

それでは早速Open API仕様に基づいて、OpenAPI定義について書いてみましょう。

フォーマットについてはJSONYAMLが選べるのですが、今回はYAML形式で記述していこうと思います。

カフェ検索サービスを考えてみよう

例として近くのコーヒーショップを検索し、予約して席を確保してくれるサービスのついて考えてみます。

ユーザは現在地近くのコーヒーショップを参照することが出来て、気に入る店があれば予約が出来る仕組みにしましょう。

つまり、フロントエンドとバックエンドで通信する箇所は二つです。

  • ユーザの現在地を送ると近くのコーヒーショップの情報がいくつか返ってくる
  • コーヒーショップのIDを送ると予約される。

これらの前提をもとに、Open API定義について書くとこの様になります。

※OpenAPI定義の書き方はここでは割愛します。書けなくても読めば理解は出来る仕組みになっているので

※OpenAPI Generatorの問題で記述するバージョンは3です。2とは大きく異なるので書いたことある人は注意してください

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
openapi: 3.0.3
info:
  title: コーヒショップ検索アプリ
  version: 0.0.1
servers:
  - url: http://localhost:8080
    description: ローカルPC開発
  - url: https://cofee.dev.example.com
    description: 開発用サーバ
  - url: https://cofee.example.com
    description: 本番サーバ
paths:
  /search:
    get:
      tags:
        - getSearch
      summary: 現在地近くのコーヒーショップの情報を取得する。
      operationId: getSearch
      parameters:
          - in: query
            name: latitude
            description: 緯度
            required: true
            schema:
              type: number
          - in: query
            name: longitude
            description: 経度
            required: true
            schema:
              type: number
      responses:
        200:
          $ref: '#/components/responses/GetSearchResponse'
  /reserve:
    post:
      tags:
        - postReserve
      summary: コーヒーショップの予約
      operationId: postReserve
      requestBody:
        description: postReserveRequest
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PostReserveRequest'
      responses:
        204:
          description: "No Content"
components:
  schemas:
    CoffeeShop:
      type: object
      description: コーヒーショップ
      properties:
        id:
          description: ID
          type: integer
          example: 1
        latitude:
          description: 緯度
          type: number
          example: 1.3
        longitude:
          description: 経度
          type: number
          example: 2.2
        description:
          description: 説明
          type: string
          example: 美味しいよ
        name:
          description: 店名
          type: string
          example: ギャラクシーバックス
      required:
        - id
        - latitude
        - longitude
        - name
    CoffeeShops:
      type: object
      description: コーヒショップの一覧
      properties:
        CoffeeShops:
          type: array
          items:
            $ref: '#/components/schemas/CoffeeShop'
      required:
        - CoffeeShops
    PostReserveRequest:
      type: object
      description: コーヒーショップの予約リクエストモデル
      properties:
        id:
          description: コーヒーショップのID
          type: integer
          example: 1
        user_uuid:
          description: 予約者のUUID
          type: string
          example: 1186B0F2B-CCA7-0ABC-1E12-05E41CD395EE
      required:
        - id
        - user_uuid
  responses:
    GetSearchResponse:
      description: リクエスト成功
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/CoffeeShops'

この定義をSwagger Editorで貼り付ければグラフィカルに表示されます。

https://editor.swagger.io/

これを正にして開発を進めることで、フロント側とバック側の認識の相違を減らすことが出来ます。

あら、一つ問題が解決されましたね・・・

- フロントエンドとバックエンドの認識ズレにより手戻りが多発した!

仮に認識ズレが起きてもこの後、yamlからソースコードを生成する方法について話すので、yamlが書き変わったらコード生成するだけなのでなんの問題も無いです。

コード生成をしよう

yamlを作ったら後はコード生成をするだけです。

ちなみに今回生成するコードはNode 16では動かないので14を用います。

準備をしよう

Open API Generatorというコード生成器を使うのですが、それを利用するためのインターフェースとなるのがOpenAPIGenerator CLIです。これはnode jsのnpmを利用してインストールすることが出来ます。

適当なプロジェクトをnpm initで作って、そこに

1
npm install @openapitools/openapi-generator-cli

で入ります。

OpenAPI Generatorの実行にはJava8系が必要なので適宜インストールしておきます。

こんな感じでバージョンが出てればおkだと思います。

1
2
3
4
reud@reudnoMacBook-Pro 2021adv-1201 % java -version 
java version "1.8.0_281"
Java(TM) SE Runtime Environment (build 1.8.0_281-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.281-b09, mixed mode)

できたら、さっきのOpen API定義をopenapi.yamlファイルに保存して、

1
openapi-generator-cli generate -g nodejs-express-server -i openapi.yaml -o server

でコード生成しましょう。

そのまま動くコードが出来ました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
└── server
    ├── README.md
    ├── api
    │   └── openapi.yaml
    ├── config.js
    ├── controllers
    │   ├── Controller.js
    │   ├── GetSearchController.js
    │   ├── PostReserveController.js
    │   └── index.js
    ├── expressServer.js
    ├── index.js
    ├── logger.js
    ├── package.json
    ├── services
    │   ├── GetSearchService.js
    │   ├── PostReserveService.js
    │   ├── Service.js
    │   └── index.js
    └── utils
        └── openapiRouter.js

編集をしよう

とはいえ色々編集せねばなりません。ちなみにそのままでも動きますが、全てのAPIは空のJSONか入力した値を返します。

見るべき部分は非常に少なく、serviceディレクトリ 配下です。

設定したタグの名前がついたファイルがあるのでみてみます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/* eslint-disable no-unused-vars */
const Service = require('./Service');

/**
* 現在地近くのコーヒーショップの情報を取得する。
*
* latitude BigDecimal 緯度
* longitude BigDecimal 経度
* returns CoffeeShops
* */
const getSearch = ({ latitude, longitude }) => new Promise(
  async (resolve, reject) => {
    try {
      resolve(Service.successResponse({
        latitude,
        longitude,
      }));
    } catch (e) {
      reject(Service.rejectResponse(
        e.message || 'Invalid input',
        e.status || 405,
      ));
    }
  },
);

module.exports = {
  getSearch,
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/* eslint-disable no-unused-vars */
const Service = require('./Service');

/**
* コーヒーショップの予約
*
* postReserveRequest PostReserveRequest postReserveRequest (optional)
* no response value expected for this operation
* */
const postReserve = ({ postReserveRequest }) => new Promise(
  async (resolve, reject) => {
    try {
      resolve(Service.successResponse({
        postReserveRequest,
      }));
    } catch (e) {
      reject(Service.rejectResponse(
        e.message || 'Invalid input',
        e.status || 405,
      ));
    }
  },
);

module.exports = {
  postReserve,
};

なんとなくわかりましたでしょうか?

1
2
3
4
      resolve(Service.successResponse({
        latitude,
        longitude,
      }));

ここに適当な値を入れればそれがJSONとして返る様になっています。簡単ですね。

後はこの部分に必要な値が入るように自分でロジック書いていけばそれでバックエンド側は完成です。

しかし、これだけだと一点だけ悩みがまだあります。

yamlが更新されて、再度コード生成したら今まで編集した部分のソースコードは書き直しにならない?

なります。

.openapi-generator-ignoreというファイルを作り、.gitignoreライクに書けば指定したファイルは上書きされなくなります。

これで、 コードの記述量が多くて大変! も解決され、世界が平和になり、平穏は取り戻されました。

参考

明日4日目は「うえおあい」のbf2042の感想話です。楽しみ〜〜〜〜!!!

https://adventar.org/calendars/6528

Licensed under CC BY-NC-ND 4.0
Built with Hugo
テーマ StackJimmy によって設計されています。