Posted at

フロントエンドAPIモック導入でビルド時間が爆速になった


フロントエンドAPIモック導入したことでビルド時間が爆速になった

おはようございます、モチベーションクラウドの開発に参画している@sinpaoutです。


TL; DR

Docker + Rails + Mysql + webpackerで起動するのが時間かかりすぎるので

全てを捨ててnodeのみ(webpack-dev-server)で生きていくことに。。。



環境

Rails + Mysql + webpacker(vue.jsビルド)がDockerイメージとして管理され、

コマンド一発で開発環境を起動できる。

Docker and Rails


問題

起動にdb:setupdb:migrate などDBの初期化が走り大体3〜5分前後かかる。

更に画面が起動後にWebpackerが走り、フロントのビルドは1分ちょい。

DBの処理化なしでマイグレーションのみでもRailsが立上がるまで2,3分かかってしまう。

普段はJSのビルド時間も入れると5分はかかってしまう。

立上がったあとは毎回ログインして目的の画面に進むが途中で何かしらエラーに直面して進めなくなることはよくある。

また、 Seeds データが全てのパターンきちんと用意さていない事が多く

目的の画面まで到達するのにかなりの時間と労力がかかってしまう。

ときにはDockerが壊れて丸一日をクジラのお世話に費やされてしまうエンジニアもいた。

Seedsデータを用意できても更新系やデータのありなしなどの

パターンはDBを直接触る必要が出てき来たりするので手間がかかるので

APIモックシステムの構築を検討することになった。


APIモックとは


APIをJSONファイルとしてwebpack-dev-serverなど簡易Webサーバで提供する仕組み

Railsなどのバックエンドを起動しないため高速に開発環境を起動可能


バックエンドの関係者たちを退場させる:

webpack-dev-server

Nodeは今どきnvmなど入れとけばバージョン管理も楽なのでDockerも退場。

(みんな今まで頑張ってくれてありがとう。。。)

アプリのAPIパスとJSONファイルのマッピングはyamlファイルで定義し

axiosのintercepeterでアドレスを変換する


使ってみる

マッピングの設定:

# js/mocks/apiMapper.yml

default:
desc: デフォルトのモック
api:
/users: mocks/users.json
/users/1/: mocks/users/detail.json

users.json の中身

{

"users": [{
"id": "",
"name": "",
}]
}

上記の例は

/users のAPIをmocks/users.json

/users/1/ のAPIを mocks/users/detail.json

に置き変える。


※ パスのidの部分は全て1として解釈するようにする。



APIをJSONファイルと関連付けてくれる人

API mocker 役割

apiMocker


API Mockerの詳細

// js/mocks/apiMocker.js

import urlParse from 'url-parse'
import apiMapper from './apiMapper.yml'

const defaultApi = apiMappers.default.api

global.apiMockIntercepter = (config) => {
const originalUrl = config.url
const parsedUrl = urlParse(config.url)
let apiPath = parsedUrl.pathname.replace(new RegExp(`^${config.baseURL}`), '')

// idをすべて1に置き換える
apiPath = apiPath.replace(/\/([0-9]+)\//ig, '/1/')
const mockApiPath = defaultApi[apiPath]

if (mockApiPath) {
// 強制的にGETに
config.method = 'get'
config.url = config.baseURL + mockApiPath
// 元情報を書き出す
console.info('api mocked', originalUrl, mockApiPath)
}

return config
}

※ 環境に合わせてパスを調整する必要がある。


Webpackの設定

普段は index のみバンドルするが、実行環境がlocalの時のみ apiMockerを挿入する。

apiMockerindex より前に挿入する必要がある。

CopyWebpackPlugin:

モックのJSONファイルをoutputパスにコピーさせる

// webpack.local.js

if (process.env.DEV_ENV === 'local') {
...

// Inject api mocker
webpackConfig.entry.index = [
`${dir.mocks}/apiMocker.js`,
`${dir.js}/index.js`
]

webpackConfig.plugins.push(new CopyWebpackPlugin([{
from: `${dir.mocks}/api/`,
to: `${output.path}/api/`
}]))

...
}


axiosの設定

実行環境がlocalの時かつ apiMockIntercepter が存在したら使うようにする

axios.interceptors.request.use((config) => {

if (process.env.DEV_ENV === 'local' && global.apiMockIntercepter) {
return global.apiMockInterceptor(config)
}
return config
})


モックマッピングの拡張

パターンごとに切り替えられるようにする。

# js/mocks/apiMapper.yml

default: &default
desc:
api: &api
/users: /users.json
/users/1/: /users/detail.json
...

noData:
<<: *default
api:
<<: *api
/users: /users_no_data.json

noData のパターンでは /users をデータなしに置き換える

users_no_data.json

{

"users": []
}


一捻り


Devtoolのネットワークでデバッグ

元のAPIやPOST場合は中身がリクエストの中身がわからなくなるので

console で元の情報を表示するように apiMocker を更新

// js/mocks/apiMocker.js

global.apiMockIntercepter = (config) => {
const originalUrl = config.url

...

if (mockApiPath) {

...

// 元情報を書き出す
console.info('api mocked', originalUrl, mockApiPath)
}

return config
}


UIからの置き換え

開発時に直接モックを切替変えられると、より効率上がるので

画面の右上あたりにパターン一覧を置き換えるポップアップ的なものを実装。

API pattern changing UI

ブラウザーのリロード後もモックの設定を有効にしたいのでsessionStorageに突っ込む。

cookieを使わない理由はhttpOnlyなどを考慮したため。


E2E用

Cypressなどからパターンを変えられるように apiMocker を更新

// js/mocks/apiMocker.js

const apiMocker = {
currentPattern: sessionStorage.getItem('apiMockerPattern') || 'default'
}

// E2Eようの外部モジュールから参照できるようにグローバル変数にしておく
window.apiMocker = apiMocker

...

// モックパターンをセットする関数を用意
apiMocker.setCurrentMock = function (patternName) {
apiMocker.currentPattern = patternName
sessionStorage.setItem('apiMockerPattern', apiMocker.currentPattern)
}

global.apiMockIntercepter = (config) => {
...
const apiMap = apiMappers[apiMocker.currentPattern].api
const mockApiPath = apiMap[apiPath]
...
}

Cypressから使う

cy.window().then((win) => {

win.apiMocker.setCurrentMock('noData')
cy.visit('localhost:8081/company/1/users')
})


結果


  • 開発環境の立ち上げ5分 → 1分ちょい

  • パターンごとにのデータの用意がjsonファイルのみで完結(ストレス激減)


    • Dockerの死亡やSeed不足の悩みから開放



  • E2Eからのパターンが切替えられるようになるためUIテストが書きやすい

  • DirやPOなどのエンジニア以外への画面共有が楽


今後の追加機能


  • Webpackerのビルド廃止(Railsと完全に縁を切る)


    • Railsは嫌いではない(むしろ好き)がやりすぎると制御しづらくなるの要注意。



  • POSTなどの更新系API対応

  • 4xx、5xx系のエラー対応

  • webpackのバージョンアップやチューニング

  • プルリクエスト単位でのレビュー環境の用意


    • 静的なファイルで再現できるためS3にビルド結果を展開が可能