フューチャー技術ブログ

OpenAPIからモックサーバを建てられるPrismを実際のプロジェクトに導入してみた

こんにちは!フューチャー22卒内定者の大岩と申します。現在は、TIG DXユニットでアルバイトとして従事しています。

はじめに

私が参加しているプロジェクトで、OpenAPI定義ファイルからモックサーバを建てることができるOSSツール「Prism」を導入することになりました。この記事では、Prism導入の手順や、躓いた点などを紹介します。

導入の背景

現プロジェクトでは、フロントエンドにVue.jsを採用し、バックエンドはGo言語で書かれたAPIサーバ2台で構成されています。これまでフロントエンドの開発を行う際には、ローカルでAPIサーバとDBを立ち上げる必要があり、フロントエンドを少しだけ変更したいという場合でもかなりの手間が掛かっていました。そこでモックサーバを構築し、画面の開発の際にはそこからデータを取得出来れば、フロントエンドの作業が格段に楽になると考えました。

バックエンドのAPIドキュメントは、OpenAPI(Swagger)形式で整備されています。そこで、このOpenAPI形式のファイルからモックサーバを建てることができる、Stoplight社のOSSツール「Prism」を採用しました。

OpenAPI(Swagger)について

OpenAPIとは、API構造を記述するインターフェース記述言語です。yamlもしくはjsonで記述することで、綺麗なAPIドキュメントを作成することができたり、この記事で紹介するようにモックサーバを建てることができます。

OpenAPIは、もともとはSwaggerという名前で開発が進められていました。2015年に、もともとの開発元であったSmartBear Softwareから、OpenAPI Initiativeへ移されると同時に Swagger Specification から OpenAPI Specification (以下OASと記載) という名前に変わりました。なお、現在でも「Swagger UI」や「Swagger Editor」などのOpenAPIドキュメントを整備するツール群は Swagger の名前が使われています。

このFuture Tech Blogでも、OpenAPIに関する記事が多く公開されています。詳細はこちらをご覧ください。

Prismについて

Prismとは、API設計関係のツールを提供するStoplight社によって開発されている、OSSのHTTPモックツールです。OAS2.0およびOAS3.0に準拠したドキュメントから、自動的にモックサーバを構築することができます。

今回使用したバージョンは、v4.1.2です。

https://github.com/stoplightio/prism

実際に導入してみる

インストール方法

まずはPrismのパッケージをnpm経由で、プロジェクトの devDependencies に追加します。

1
$ npm install -D prism

あとは、npx で起動してみて、モックサーバが起動できていたら完了です。以下の例は、カレントディレクトリ内にある swagger.yaml ファイルを指定して、8080番ポートで起動する例です。

1
$ npx prism mock ./swagger.yaml -p 8080

これを実行すると、OpenAPIファイルに記載されている全てのエンドポイントのURLが一覧で表示されます。(ここでは、 Swagger Petstore を実行した結果を掲載しています)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ npx prism mock ./swagger.yaml -p 8080
[14:30:40] › [CLI] … awaiting Starting Prism…
[14:30:40] › [CLI] ℹ info POST http://127.0.0.1:8080/pet
[14:30:40] › [CLI] ℹ info PUT http://127.0.0.1:8080/pet
[14:30:40] › [CLI] ℹ info GET http://127.0.0.1:8080/pet/findByStatus?status=sold,sold,sold,available,pending,pending,pending,pending,available,available,available,pending,pending,sold,available,available,pending,sold,available,pending
[14:30:40] › [CLI] ℹ info GET http://127.0.0.1:8080/pet/findByTags?tags=quis,aperiam,velit,repudiandae,et,rem,accusantium,omnis,ut,eius,dolor,enim,nam,et,ipsam,velit,est,veritatis,nesciunt,possimus
[14:30:40] › [CLI] ℹ info GET http://127.0.0.1:8080/pet/127
[14:30:40] › [CLI] ℹ info POST http://127.0.0.1:8080/pet/128
[14:30:40] › [CLI] ℹ info DELETE http://127.0.0.1:8080/pet/483
[14:30:40] › [CLI] ℹ info POST http://127.0.0.1:8080/pet/641/uploadImage
[14:30:40] › [CLI] ℹ info GET http://127.0.0.1:8080/store/inventory
[14:30:40] › [CLI] ℹ info POST http://127.0.0.1:8080/store/order
[14:30:40] › [CLI] ℹ info GET http://127.0.0.1:8080/store/order/10
[14:30:40] › [CLI] ℹ info DELETE http://127.0.0.1:8080/store/order/963
[14:30:40] › [CLI] ℹ info POST http://127.0.0.1:8080/user
[14:30:40] › [CLI] ℹ info POST http://127.0.0.1:8080/user/createWithArray
[14:30:40] › [CLI] ℹ info POST http://127.0.0.1:8080/user/createWithList
[14:30:40] › [CLI] ℹ info GET http://127.0.0.1:8080/user/login?username=itaque&password=exercitationem
[14:30:40] › [CLI] ℹ info GET http://127.0.0.1:8080/user/logout
[14:30:40] › [CLI] ℹ info GET http://127.0.0.1:8080/user/quo
[14:30:40] › [CLI] ℹ info PUT http://127.0.0.1:8080/user/fugiat
[14:30:40] › [CLI] ℹ info DELETE http://127.0.0.1:8080/user/minima
[14:30:40] › [CLI] ▶ start Prism is listening on http://127.0.0.1:8080

確かにこの表示されたURLにアクセスしてみると、モックデータが返却されるようです。

例として、 GET /pet/{petId} を見てみましょう。Swagger Petstore のOpenAPI定義ファイルでは、以下のように記述されています。

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
  /pet/{petId}:
get:
tags:
- "pet"
summary: "Find pet by ID"
description: "Returns a single pet"
operationId: "getPetById"
produces:
- "application/xml"
- "application/json"
parameters:
- name: "petId"
in: "path"
description: "ID of pet to return"
required: true
type: "integer"
format: "int64"
responses:
"200":
description: "successful operation"
schema:
$ref: "#/definitions/Pet"
"400":
description: "Invalid ID supplied"
"404":
description: "Pet not found"
security:
- api_key: []
(中略)
definitions:
Pet:
type: "object"
required:
- "name"
- "photoUrls"
properties:
id:
type: "integer"
format: "int64"
category:
$ref: "#/definitions/Category"
name:
type: "string"
example: "doggie"
photoUrls:
type: "array"
xml:
name: "photoUrl"
wrapped: true
items:
type: "string"
tags:
type: "array"
xml:
name: "tag"
wrapped: true
items:
$ref: "#/definitions/Tag"
status:
type: "string"
description: "pet status in the store"
enum:
- "available"
- "pending"
- "sold"
xml:
name: "Pet"

ここでは、 name というプロパティに example として Doggie という値が指定されています。では、モックサーバから返却される値を見てみます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"id":0,
"category":{
"id":0,
"name":"string"
},
"name":"doggie",
"photoUrls":[
"string"
],
"tags":[
{
"id":0,
"name":"string"
}
],
"status":"available"
}

ちゃんとexampleで指定した値が返ってきていることがわかります。exampleを指定していない部分は、 0 や "string" に固定されてしまうものの、正しい型で返ってきています。なお、モックサーバの起動時に -d オプションをつけることで、値をランダムに変更することができます。

basePath が反映されない

ここが詰まったポイントです。今回Prismを導入したプロジェクトではOAS2.0を使用しており、現行最新版のOAS3.0と比べると、仕様が異なる点があります。

OAS2.0では、エンドポイントのパスを以下のように設定します。

1
2
3
4
host: "petstore.swagger.io"
basePath: "/v2"
schemes:
- "https"

OAS3.0では basePath が削除され、以下のように記述します。

1
2
3
servers:
- url: https://petstore.swagger.io/v2
description: server description

schemeshostbasePathservers という一つのプロパティにまとまったおかげで見やすくなりました。

Prismはv3からOAS3.0での書き方に準拠するようになり、 OAS2.0のファイルを読み込ませると、basePathを読み取ってくれず、エンドポイントのパスに反映されません 。つまり、 https://petstore.swagger.io/v2/pets というエンドポイントを定義していたとしても、Prismでは https://petstore.swagger.io/pets として認識してしまいます。

これではモックサーバとしての意味を為さないため、エンドポイントのパスを変更する必要があります。

Vue CLIのプロキシ機能で解決する

今回のフロントエンドで利用しているVue.jsは、Vue CLIを用いて環境構築されています。Vue CLIには、webpackの DevServer の機能が内包されており、ホットリロードなどを実現しています。この機能の一つに、 APIサーバなど外部のサーバをプロキシして接続する機能があります。

このプロキシ機能で、 pathRewrite というオプションを指定すると、パスを上書きすることができます。

実際に設定してみましょう。例えば、フロントエンドから http://localhost:3000/api1/v1/ にリクエストを飛ばすと、 http://localhost:8080/api1/v1/ に繋がるようになっている環境であるとします。

vue.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
module.exports = {
devServer: {
port: 3000,
proxy: {
'^/api1/': {
target: 'http://localhost:8080/',
},
'^/api2/': {
target: 'http://localhost:8081/',
}
}
}
};

このAPIサーバ api1api2 をモックサーバに置き換えたいとします。しかし、前述の通りPrismでは /api1/v1 の部分を無視してしまうため、そのままではアクセスすることができません。そこで使うのが、 pathRewrite オプションです。

例えば以下の例では、 NODE_ENVdesign という値が設定されている場合のみ、 http://localhost:3000/api1/v1/http://localhost:8080/ にプロキシされるように設定しています。

vue.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module.exports = {
devServer: {
port: 3000,
proxy: {
'^/api1/': {
target: 'http://localhost:8080/',
pathRewrite:
process.env.NODE_ENV === 'design' ? { '^/api1/v1/': '/' } : null
},
'^/api2/': {
target: 'http://localhost:8081/',
pathRewrite:
process.env.NODE_ENV === 'design' ? { '^/api2/v1/': '/' } : null
}
}
}
};

これで、実際に先ほどの手順でモックサーバを起動してみて、 http://localhost:3000/api/v1/ にアクセスしレスポンスが返ってきたらOKです。

npm scriptでコマンド一つで起動できるようにする

せっかくなのでコマンド一つで起動できるようにして、楽に開発がスタートできるようにしておきたいです。まずは、以下のパッケージをインストールします。

1
npm install -D cross-env concurrently

cross-env は、Windows環境下で NODE_ENV を指定すると正常にコマンドを実行できない問題があるため、環境差異を解消すべく導入しています。

concurrently は、同時に複数のコマンドを実行できるようにします。今回はPrismによるモックサーバを2つ、vue-cliの開発サーバを1つ、合計3つのコマンドを同時実行させます。

インストールできたら、 package.jsonscripts に追記します。ここでは、モックサーバの起動スクリプトを別に分けて、モックサーバを個別で起動できるようにもしています。

package.json
1
2
3
4
5
6
7
{
scripts: {
"design": "cross-env NODE_ENV=design concurrently \"npm run mock-1\" \"npm run mock-2\" \"vue-cli-service serve --mode design --open\"",
"mock-1": "npx prism mock ./swagger1.yaml -p 8080",
"mock-2": "npx prism mock ./swagger2.yaml -p 8081"
}
}

あとは、

1
npm run design

を実行すれば、モックサーバが2つとVue CLIの開発サーバが立ち上がり、 http://localhost:3000 で確認できるようになります。これで、簡単にフロントエンドを開発できるようになりました!🎉

補足: nginxを使ってプロキシする方法

今回のプロジェクトでは、Vue CLIを使っていたため、簡単にプロキシすることができました。しかし、中にはプロキシ機能をもたないものを利用しているケースもあると思います。

そこで、 docker-compose を用いて nginxprism のコンテナを立てて、nginxにプロキシさせる方法を紹介します。

docker-composeファイル

まず、nginx のコンテナに nginx:alpine のイメージを選択し、8080番ポートと8081番ポートを開け、volumesに設定ファイルを指定します。次に、それぞれのモックサーバのコンテナに stoplight/prism:3 のイメージを選択し、volumesにOpenAPIファイルを指定します。

docker-compose.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
version: '3'
services:
nginx:
image: nginx:1.19-alpine
ports:
- 8080:8080
- 8081:8081
volumes:
- ./default.conf:/etc/nginx/conf.d/default.conf
api1-mock:
image: stoplight/prism:4
command: mock -h 0.0.0.0 /swagger.yaml
volumes:
- ./swagger1.yaml:/swagger.yaml
api2-mock:
image: stoplight/prism:4
command: mock -h 0.0.0.0 /swagger.yaml
volumes:
- ./swagger2.yaml:/swagger.yaml

nginx設定ファイル

nginx設定ファイルは、 location の部分にパスを書き、プロキシの設定を書くことで、そのパス以降に来たリクエストをリバースプロキシすることができるようになります。この例では、 0.0.0.0:8080/api1/v1 に来たアクセスを、 api1-mock コンテナの4010番ポートにプロキシするように設定しています。

default.conf
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
server {
listen 8080;
server_name 0.0.0.0:8080;
location /api1/v1/ {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://api1-mock:4010/;
}
}

server {
listen 8081;
server_name 0.0.0.0:8081;
location /api2/v1/ {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://api2-mock:4010/;
}
}

あとは、docker-compose.yamlファイルがある場所で、以下のコマンドを実行すると、2つのモックサーバを同時に立ち上げることができます。

1
docker-compose -p (任意の名前) up -d

立ち上げた後に、 http://localhost:8080/api1/v1/ にアクセスして、レスポンスが返ってきたらOKです。

まとめ

Prismを使うことで、OpenAPIファイルさえ記述していれば簡単にモックサーバとして機能し、開発に効果的に組み込むことができます。ローカルにバックエンドサーバを立ち上げる手間が省けて、開発体験を格段に向上させることができました。

ぜひ皆さんも快適な開発環境構築のために、導入を検討してみてはいかがでしょうか。