この記事は、法政大学アドベントカレンダー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定義について書いてみましょう。
フォーマットについてはJSON
とYAML
が選べるのですが、今回は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