Tech Blog

グローバルな家族アプリFammを運営するTimers inc (タイマーズ) の公式Tech Blogです。弊社のエンジニアリングを支える記事を随時公開。エンジニア絶賛採用中!→ https://timers-inc.com/engineering

iOSエンジニアがBFF環境を構築しました #bff

もう少しでAlt Confの為にアメリカに出発なので少し緊張してきたiOSアプリエンジニアのかっくん(@fromkk)です。
少し前にリリースした機能で、APIサーバーでもクライアントサイドでもロジックを実装しない中間層として BFF を構築・リリースしました。

BFFとは

BFFBackend 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で開発を進めています。
今回の構築に参考にさせて頂いたのがこちらです。

f:id:bboykk:20190508181244p:plain

ローカル環境構築

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 を実行するとテストが実行されます。
デプロイの前には必ずテストを実行しています。

デプロイ

GithubCircle CIを連携して特定のブランチにプッシュされたら、自動でテストとデプロイが実行されるようにしています。
serverlessコマンドはdeployというサブコマンドを標準で持っているため、Circle CIで使っているIAMユーザーにデプロイを行えるポリシーを付与することで自動デプロイを実現することができました。

クライアントからの利用

iOSでは URLSession をラッピングしたクラスを作成して利用する様にしています。
アーキテクチャの図にある様に AuthorizationAuthentication 用の情報をごにょごにょして付与して通信しています。

まとめ

BFF環境を用意する事で、クライアントのエンジニアだけで共通ロジックを実装する事が出来る様になりました🎉
TypeScriptにより型が明示的に利用出来るのでSwiftKotlinに馴染んだエンジニアもとっつきやすいかなと思います。
まだまだ開発し始めたばかりですが、今後もドンドン活用していきたいなと思います。

PR

子育て家族アプリFammを運営するTimers inc.では、現在エンジニアを積極採用中!
急成長中のサービスの技術の話を少しでも聞いてみたい方、スタートアップで働きたい方など、是非お気軽にご連絡ください!
採用HP: http://timers-inc.com/engineerings

Timersでは各職種を積極採用中!

急成長スタートアップで、最高のものづくりをしよう。

募集の詳細をみる