もう少しでAlt Confの為にアメリカに出発なので少し緊張してきたiOSアプリエンジニアのかっくん(@fromkk)です。
少し前にリリースした機能で、APIサーバーでもクライアントサイドでもロジックを実装しない中間層として BFF
を構築・リリースしました。
BFFとは
BFF
はBackend for Frontendと呼ばれ、クライアントの為のサーバー環境を指します。(決してBest friend foreverでは無いので間違えない様に気をつけましょう)
BFF
環境を構築する事で、これまでのAPIサーバーはよりREST
に徹する事ができ、API
からのレスポンスを画面に表示する為の整形ロジックをBFF
に任せる事で、iOS/Android/Webそれぞれで同じ整形ロジックを利用する事が出来る様になります。
経緯
何故BFF環境を構築するに至ったかと言うと、過去に画面に表示する情報をサーバーで生成して返すAPIを作ったことがありました。
元々のロジックが複雑なのでiOS/Androidで同じ文言を表示する為にそれぞれのクライアントで同じ様なコードを書かないといけないと言うのがとても大変でした。
このAPIは僕がベースを作ったものなのですが、メンテナンスの頻度がとても高く、サーバーエンジニアが対応するコストが増加してしまいました。
この失敗を踏まえて、クライアントエンジニアだけで簡単に変更が出来る環境としてBFFを用意する事になりました。
アーキテクチャ
今回はServerlessフレームワーク
を利用して、Express
ベースのコードをAWS Lambda
上に配置する様にしました。
Node.JSのバージョンは 8.10
、言語はTypeScript
で開発を進めています。
今回の構築に参考にさせて頂いたのがこちらです。
ローカル環境構築
※macOSで開発する事を前提としています。
Node.JSバージョン固定
AWS Lambdaで用意されている環境に適応させる為、ローカルのNodeのバージョンを柔軟に変更出来る様にします。
(Homebrewがこちらからインストールされている事を前提としています)
brew install nodenv
作業用ディレクトリに .node-version
というファイルを作成し 8.10.0
という内容で保存します。
nodenv install 8.10.0
を実行して該当バージョンのNode.jsをインストールします。
インストールが完了した後に node -v
と打つと v8.10.0
と出力される事を確認します。
(インストールが失敗した場合はこちらを参考にしてインストールして下さい)
必要ツール・ライブラリのインストール
こちらにある通り npm install -g yarn ts-node
を実行します。
また、最低限の環境で良ければ同様にこちらのファイルをコピーし、必要な箇所を修正して npm install
を実行すれば必要なライブラリなどがインストールされます。
開発について
通信
Fammサーバーとの通信にはrequestモジュールを利用しました。
npm install --save request && npm install --save request-promise-native
でインストールし、 import * as request from 'request';
でインポートして利用しています。
翻訳
文言の翻訳にはi18nを利用しました。
npm install i18n --save
でインストールし import * as i18n from 'i18n';
でインポートして利用しています。
Expressの初期化時に利用する事を宣言する必要があり注意が必要です。
// app.ts import * as express from 'express'; import * as i18n from 'i18n'; export function configureApp() { const app = express(); i18n.configure({ locales: ['ja', 'en'], directory: __dirname + '/locales', defaultLocale: 'ja', register: global, }); app.use(i18n.init); // ルーティング処理 return app; }
ローカライズ
現時点では日付のローカライズは行なっていないので、金額のローカライズ表示を行いました。
Node.jsが持っている Intl.NumberFormat
という機能を利用しました。1
テスト
こちらを参考にして環境を構築するとJestというテストツールがインストールされるかと思いますのでそれをそのまま利用しています。
src/locales/en.jsonに
{ "hello": "Hello world" }
と記載して、app.tsのルーティングの部分にと書き、
app.get('/', (req, res) => { i18n.setLocale('en'); res.json({ message: i18n.__('hello') }); });
test/app.test.tsファイルに
import * as request from 'supertest'; import { configureApp } from '../src/app'; describe('a feature of the system', () => { it('displays some specific behaviour', async () => { const app = configureApp(); const response = await request(app).get('/'); expect(response).toMatchObject({ status: 200, text: '{"message":"Hello world"}', }); }); });
とする事でテストが書けました。実際にはモジュール毎に必要な箇所にテストを書く様にしています。
npm run test
を実行するとテストが実行されます。
デプロイの前には必ずテストを実行しています。
デプロイ
Github
とCircle CI
を連携して特定のブランチにプッシュされたら、自動でテストとデプロイが実行されるようにしています。
serverless
コマンドはdeploy
というサブコマンドを標準で持っているため、Circle CI
で使っているIAM
ユーザーにデプロイを行えるポリシーを付与することで自動デプロイを実現することができました。
クライアントからの利用
iOSでは URLSession
をラッピングしたクラスを作成して利用する様にしています。
アーキテクチャの図にある様に Authorization
と Authentication
用の情報をごにょごにょして付与して通信しています。
まとめ
BFF
環境を用意する事で、クライアントのエンジニアだけで共通ロジックを実装する事が出来る様になりました🎉
TypeScript
により型が明示的に利用出来るのでSwift
やKotlin
に馴染んだエンジニアもとっつきやすいかなと思います。
まだまだ開発し始めたばかりですが、今後もドンドン活用していきたいなと思います。
PR
子育て家族アプリFammを運営するTimers inc.では、現在エンジニアを積極採用中!
急成長中のサービスの技術の話を少しでも聞いてみたい方、スタートアップで働きたい方など、是非お気軽にご連絡ください!
採用HP: http://timers-inc.com/engineerings