ウォーターフォールを見直して自チームに最適化した開発フローを構築する

エンジニアの佐野です。バンドルカードではポチっとチャージという後払いの機能を利用する際に年齢確認が必須となりました。通信キャリアや銀行との連携等によって年齢確認ができるようになっています*1。今回はこの機能の開発を題材に普段開発でどのようなことを考えて開発し、本機能の開発ではどのようなフローを構築して進めていったかを書きます。

少し概要を書くと、本件についてはウォーターフォールモデル "のような" 開発フローで行いました。事業上の理由でビッグバンリリースが必要でした。要件をしっかり決めてステップバイステップで開発を行いすべての機能を同時にリリースする...案件の性質を考えるとウォーターフォールが開発フローの候補の1つだと思っていたためです。ただそのまま一般的に思われているウォーターフォールを導入するのではなく、その欠点や面倒な点を解消しつつ、認識齟齬なしに設計と実装を行い、納期を死守しつつ、バグを最小限に抑えて一撃で出すにはどうするのが最適かを考える必要がありました。本記事ではその開発の計画、DB設計やシーケンス図のような各フェーズの成果物、実装方法、テスト、リリースについて書ける範囲で書きます。本件のバックエンド側はやることは比較的単純(後述しますが、簡単に言うと連携先から生年月日を取得して突合する)なのですが、リリースが遅れたりリリース後にバグると収益にかなりのダメージがある案件でした。

自分がエンジニアということもありPdM、デザイナー、エンジニアといった製作サイドの話、特に開発計画とバックエンドの話がメインになりますが、製作サイド以外の部署 (事業開発、法務、セキュリティ、CS、データ分析チーム...etc)ももちろん存在しており、リーガルチェックや連携先を含む関連各社との交渉、LP 作成やオペレーション体制の構築などに尽力してくれました。書きっぷりから私が PJ を主導して成功に導いた...ことを大々的に書いているかのように見えるかもしれませんが、自分の視点から見た PJ の進め方と要所要所で考えていたことや実際に起きたことの記録だと思ってもらえるとよいです。

  1. 年齢確認についての前提知識
  2. ウォーターフォールを考える
  3. 開発フロー
  4. 各フェーズの説明
  5. まとめ

1. 年齢確認についての前提知識

バンドルカードはユーザ新規登録時に生年月日を入力することになっています。この時点では自己申告の生年月日です。ポチっとチャージを利用する際はその生年月日の正当性チェックを必須とするのが今回の案件です。正当性が認められたユーザは「年齢確認済」となります。ポチっとチャージを利用する際、そのユーザが年齢確認済かどうかをチェックします。年齢確認済でなければ通信キャリアや銀行との連携等による年齢確認を促します。通信キャリアや銀行と連携する際の模式図を描くと次のような形となります。

通信キャリアおよび銀行は一般的な OIDC もしくは独自の認証・認可の仕組みを持っています。年齢確認を実施する際、バンドルカードはユーザをそれらのサイトにリダイレクトさせ、個人情報の連携に同意していただきます。それによって各社に格納されているユーザの個人情報を取得し、新規ユーザ登録時に入力された生年月日と突合することで年齢確認を行います。冒頭にも書きましたが要件としてすべての連携先を同日にリリースし、アプリの強制アップデートで全ユーザに年齢確認を提供します。いろいろな事情がありカナリアリリースや連携先を1つずつ解放することはできない案件でした。

2. ウォーターフォールを考える

話がだいぶ逸れてしまうのですが、ウォーターフォールと呼ばれている開発モデルの歴史や誤解について語っておきます。記事の頭で「ただそのまま一般的に思われているウォーターフォールを導入するのではなく、その欠点や面倒な点を解消しつつ...」と書いたように、自分の経験上、ウォーターフォールで失敗した過去や、もちろん成功した過去もあるため、これを機にウォーターフォール含め開発フローを再考して今回の PJ に活かします。

ウォーターフォールやらアジャイルやら開発フローについては「どうやってもうまくいくときはうまくいくし、ダメなときはダメ」と冷めた目で見ていた節もあるのですが...ちょっと真面目に考えてみようと思いました。

2.1 多くの人がウォーターフォールと呼んでいるもの

ウォーターフォールというのはなんなのか、ですが、多くの人は以下の様な図をイメージするかもしれません。使っている言葉に揺れはありますが概ねこのような流れで行われます。谷底に開発(コーディング) を挟んで V 字で図示して V モデルと呼ぶこともあります。双方向矢印は左側の設計フェーズと対応するテストフェーズを紐付けています。

特徴としては以下の通り、あえて悪評のようなものをここでは羅列しておきます(後で回収します)。

  • 前のフェーズへの手戻りは行わない (が、往々にして要件定義の変更が後から行われてしまい大規模な巻き戻りが発生することがある)
  • 各フェーズで膨大な成果物が必要
  • 多くの人が嫌っている (玄人感を出したいからかとりあえず否定するワナビーもいる)

私は SI に従事していたときは疲弊した記憶はあります。ただまったくダメというイメージはありません。

ここでウォーターフォールという言葉がこの世に登場したとき、それはこのようなものを指していません。

2.2 元祖ウォーターフォール

ウォーターフォールモデルの歴史を掘っていくと次の様な文献が見つかります。

  1. Production of Large Computer Programs HERBERT D. BENINGTON 1956
  2. MANAGING THE DEVELOPMENT OF LARGE SOFTWARE SYSTEMS Dr. Winston W. Royce 1970
  3. SOFTWARE REQUIREMENTS: ARE THEY REALLY A PROBLEM? T. E. Bell and T. A. Thayer TRW Defense and Space Systems Group Redondo Beach, California 1976
  4. SOFTWARE PROCESS MANAGEMENT: LESSONS LEARNED FROM HISTORY Barry W. Boehm 1987

時系列に並べています。1が1956年の論文で SAGE*2 を開発する際の技術について述べられています。そこで次のような図が出てきます。

Production of Large Computer Programs HERBERT D. BENINGTON 1956

計画 -> 仕様策定 -> コーディング ... -> システム評価 の順に進む。V 型のウォーターフォールの図のように、コーディング (実装)を谷底として各フェーズのテストが行われています。見たことある図に近いですね...。

With SAGE, we were faced with programs that were too large for one person to grasp entirely and also with the need to hire and train large numbers of people to become programmers.

一人では把握できない巨大なプログラムに直面したこと、そして大量のプログラマーを採用してトレーニングする必要があった、と書かれており、これがこの開発手法の背景にあったと思われます。

続いて1970年にロイス博士の論文 MANAGING THE DEVELOPMENT OF LARGE SOFTWARE SYSTEMS が登場します。この論文では次の様なことが述べられています。

  • すべてのプログラムには解析・分析(要求分析のようなことを表しているのだと思う)とコーディングという2ステップが存在する。複雑さやサイズにかかわらずそれが基本である。
  • 小規模プログラムではそれで十分だが大規模開発では追加のステップが必要である。(要件定義、設計、テスト...)
  • テストフェーズが最後の方にあるのでそこで問題が発見されると大幅な設計変更、つまり巻き戻りが発生することがある。
  • ↑を軽減するために予備プログラム (A preliminary program design phase プロトタイプのことかな...)を作る、ドキュメントをがっつり書く、2回作れ (Do It Twice)、テストの計画と管理をする、顧客を関与させる

みなさんが知っているウォーターフォールについて1956年の論文のような図を引き合いに出し、その問題点と解決策を提唱しています。この時点でプロトタイピングや反復開発の概念について言及しています。それを図にしたものが次です。かなり複雑に見えます。

MANAGING THE DEVELOPMENT OF LARGE SOFTWARE SYSTEMS Dr. Winston W. Royce 1970

そして3の1976年の論文 SOFTWARE REQUIREMENTS: ARE THEY REALLY A PROBLEM? で次の一節、

The same top-down approach to a series of requirements statements is explained, without the specialized military jargon, in an excellent paper by Royce [5]; he introduced the concept of the "waterfall" of development activities.

ここで初めてウォーターフォールという文字が登場します。引用の [5] はロイスの2の論文を指し示しています。ウォーターフォールという言葉が出現したとき、それはプロトタイピングや反復開発の概念があるスタイルのものを指しています。みなさんが思っているものと違うものが「ウォーターフォール」と呼ばれています。

ではなぜこんにちの Vモデルのようなものであったり、巻き戻りはしない、という考えのものがウォーターフォールと呼ばれるようになったのかですが正直わかりません (掘れていません)。1987年の4の論文では「3の論文では2の論文がウォーターフォールであると言っているが、実際は1なんじゃないの?」と書かれていて、たしかに実際にみなさんが思うウォーターフォールはこちらの方が近いと思います。しかし1の論文はウォーターフォールとは呼ばれていません。

とりあえずですが、ウォーターフォールについては歴史的観点から細かいことを言うと現代のウォーターフォールウォーターフォールではないと言えるかもしれません。

2.3 元祖ウォーターフォールから得るヒント

物事を真似する、参考にするにはその背景や再現性を考慮する必要があります。「一人では把握できない巨大なプログラムに直面したこと、そして大量のプログラマーを採用してトレーニングする必要があった」ようなことが書かれていたとおり、ウォーターフォールは超大規模向けに考案されたというのが背景にあります。今回の案件はそのような規模ではありません。しかしいくつか取り入れるべきヒントがあります。

  • 2回やれ

元祖では全フローをもう一回繰り返せ...ということを言っています。我々は全フローではなく、巻き戻るとダメージの大きい要件定義と一部の設計を2回やることにします。軌道修正するフェーズを最初から設けておきます。

  • テスト計画と管理を行う

テスト計画を要件定義の次くらい、設計の前段くらいに作り上げ、それをもとにテスト時の人員と資材を計画しておきます。テスト項目表 (=期待動作一覧表) も並行して先に作っておきます。すべてのケースの期待動作を洗い出し、それを設計、実装、そしてもちろん QA テストの前段の成果物にするという思惑です。これはテストフェーズが最後の方にあるのが問題点である、というものを解消するためです。なるべく早い段階からバグの芽を摘み取れるようにテスト項目を早い段階から作り、それを正解の仕様としてテスト駆動で開発を進められるようにします。

これは当たり前...だと私は思っていますが、ちゃんとドキュメントを書きます。エンジニア向け、エンジニア以外のチーム向けを意識したものを成果物として出します。

ちなみにこのドキュメント業なのですが...たまにドキュメントや資料の類いは無駄、不要と言う人々が存在しており、私とは全く考えが合わないのですが、少なくとも顧客に提供して長く運用を回す前提の業務アプリケーションはドキュメンテーション必須だと思っています。エンジニアチームとしては設計レビュー、コードレビューで必要になります。例えばコードレビューですが、ドキュメント不要と言っている人はコードの何を見ているのか...というのは非常に気になります。ソースコードのみてくれを確認しているとは思いますが、コードが仕様通りに書かれているかどうかを確認するのはレビューのはずで、例えばある条件のときに true を返すテストコードとその関数の実装のセットがあったとして、それ自体が正しいのか?は前段の design doc がないと判断ができないはずです。これがそのままスルーされて外部結合テストのような後続のフェーズで見つかると面倒な巻き戻りが発生し、またドキュメントがないことでバグなのか仕様なのかがわからずドツボにハマります。

昔の同僚にも不要派がいて、彼らの話を聞くとドキュメントを永遠に更新し続けるのは無理だしソースコードとドキュメントに齟齬が出る、と言うのですが、それに対しては私は対策をしています。それは設計ドキュメントと運用ドキュメントは別にすること、ドキュメントに寿命を設けること、です。設計用ドキュメントですが、必要になるのはエンジニア以外が含まれるチームでの仕様の確認時、そしてエンジニアチームで行う設計レビューとコードレビュー時です。その寿命は最初のリリースが終わるまで、とします。つまり最初のリリースが終わるまでは仕様変更や巻き戻りによってドキュメントの更新はしますが、リリースが済んだらもうそれはお役御免とします。資料として残しはしますが、有効期限は最終更新日としてそれ以降の変更についてはシステムの方が正しい、とします。設計用ドキュメントは1stリリースまでの仕様確認用であり設計レビュー用であると割り切ります。

運用ドキュメントはいわゆる Runbook のようなものであったり、CS チーム用に作る問い合わせ指南書のようなものです。こちらは運用とともに育てていく前提で永遠に更新します。Runbook を更新したり書くのが面倒であればそれこそ Runbook が不要となるようにシステムを改善すれば良いです。コードを書いて直しましょう。そうすればいくらかの運用ドキュメントは消滅させることができます。

寿命についてはドキュメントを書くのがしんどいというのであればそのハードルを下げればよいという考え方です。受託開発であれば話は別ですが、納品が発生しない前提であれば軽い口調の書き方や多少の表記揺れやミスがあっても気にしなくてもよいはずです。

ミドルウェアの開発やライブラリの開発であったり、エンジニア同志の口頭の意思伝達で済ませているチームの場合は本当に自然言語のドキュメントが必要かどうかはわかりません。私は逆にそのような組織に属したことがないので...。

この節では私の主張が少し(だいぶ?)入りました...。

3. 開発フロー

さて、長ったらしいウォーターフォールの話から元に戻りまして...開発の条件は次の通りです。

  • 製作サイドのメンバー数人 (元祖ウォーターフォールの想定のような人数規模ではない)
  • ビッグバンリリースが必要
  • リリースでバグがあったら会社として結構マズい

以下がその際に実際に行われたフローです。上述のヒントや先人達の与えてくれた教訓を盛り込み次のようになりました。ちなみに図自体は今作った後付けです。PJ発足時に「俺はこう考えている」というのをちゃんと示しておいた方がよかったかもしれない...。

3.1 考案した開発フロー

時系列は概ね左から右です。双方向矢印は工数として考慮済の手戻りを表しています。実装と設計を行き来しないわけがない、テストでバグが見つかって実装に手戻りが発生しないわけがない、というような...。

3.2 現実の開発フロー

ここで現実を書いておきます。計画通りにいったら苦労はしない。

赤入れした部分が計画崩れした部分や想定と変わってしまった部分です。

  • 連携先との疎通テスト: 連携先との疎通確認は早々に終わらせてしまい、インフラ面でのハマりは早々に解消したかったのですが...。TCP/IPレベルでの疎通はある程度はできたもののアプリケーションレイヤでの疎通は QA テストのときにいきなり結合する形となってしまった。そしてそこで早めに疎通試験できていれば見つかったであろうバグを見つけるなど...。
  • ER図とアプリ/サーバ間のAPI: これは元から手戻りはあるだろうと思っていたが本当にちょっとしたミスで手戻りがあった。APIが1本足りなかった、連携先から取得した生年月日は保存しない算段だったが保存が必要だった、etc...。図を見てわかるとおりシーケンス図は2回やったこともあり手戻りはほとんどなく、ここで「2回やれ」の効果が奇しくも出ることになった(2回やったから大丈夫だったと言い切ることはできないが)。
  • 一部仕様変更: とある連携先だけ年齢確認時の挙動を変えることになった。こちらも幸いダメージは少なかったが...。
  • リリース: ビッグバンリリースはいうことになっていたがとある連携先だけ先出しする形になった。ちなみにこれはいくつかの連携先の開発が遅れているので先に出せるところだけ...といったネガティブな理由ではなく、むしろ先に一部の連携先を開放してユーザにインセンティブを付与する+ユーザの反応や動きを先に分析したいという戦略上の要因です。
  • 俺の離脱: そう俺が PJ から外れる。他案件の兼ね合いで。QAテストの前あたり。各社との連携の実装までは完了したが、後から追加で出現した年齢確認手段の要件定義〜設計〜実装、QAテスト以降のフェーズを残して俺がこの案件からいなくなった...。フェーズが巻き戻ることや想定外の要件が後から出てくるといったようなものは気にしていたのですが、メンバーチェンジ...まあそれもあるっちゃあるか、と思ってはいたがそしてそれが割とメインの自分...うーむ、これはさすがに考えていなかった...。自分が離れたあとのバグの発生がチャットに流れてくるとすまねえなあ...と、なんとも言えない気持ちになるなど...入れ替わったメンバーが完遂してくれました。

4. 各フェーズの説明

以下、それぞれのフェーズで何を行い、どのようなことを考えていたかを書きます。ここから先は多少はテクニックの話も出てきます。

4.1 要件定義 (1回目)

まずはやりたいことを PdM にまとめてもらいます。こちらについては PJ キックオフ前から経営層と PdM が水面下で進めていました。自分の進言としては、上のフロー図のとおり、連携先の仕様確認と画面設計のあとにこれをやり直す (再精査する)べきであると告げました。この時点ではどのような開発フローで進めるかはふわふわしていました。

4.2 画面設計 (1回目)

チーム内では仮UIと呼んでいました。こちらも後続のフェーズからフィードバックする前提のアイテムです。まずは素直に我々が実現したいこと (≠できること)を図にします。

4.3 連携先仕様調査

1回目の要件定義と仮UIこと画面設計をやりつつ連携先の仕様書を精読します。最初に仕様書読めば?という人がいるかもしれませんが先方の仕様書も膨大です。やりたいことを決める -> やりたいことに関連した箇所を読んで実際にできるかどうかを調べる、といった流れで要件定義と仮UIにフィードバックします。この仕様書からインフラ設計に必要な情報や非機能要件も読み取っておきます。また先方に質問を投げるなどして仕様の確認をする、結合テストする際の制約事項*3のスケジュールについても洗い出しておきます。

4.4 シーケンス図 (1回目)

仕様書の情報から1回目のシーケンス図を書きます。ここでは要件定義と画面設計にフィードバックするという目的があるのでそれを意識した書きっぷりにします。粒度としても細かいパラメータの表記は最低限として、PdM などエンジニア以外の職種にも理解してもらえる表記にします。以下のような成果物です。図そのものも重要ですが「※」マークで書いた箇所、「これを決める必要がある」「この画面が追加で必要である」「こういう遷移を考慮する必要がある」といった情報を洗い出します。

4.5 要件定義 (2回目)とスケジュール決め

仕様調査と仮UI、先ほどの1回目のシーケンス図をもとに再度要件定義を行います。ここでスケジュール線表も引きます。線を引くのが私、バックエンドの人間ということもあり、バックエンド観点のものになってしまうため、他メンバーともすり合わせをします。またここで引いたスケジュールは我々製作サイドの考えたスケジュールでもあるのでこれを PdM から経営層にぶつけてもらい、そちらともすり合わせを行います。

ところでものの本によると一流は納期を決めないらしいですが私は三流なので納期は決めます...。と、まあ人様の本を揶揄するつもりはなく、カンムのこの PJ では会社としてリリース日に大きな意味がありました。ということでこちらの案件については納期を決めて守る必要がありました。納期に大きな意味がなければ自分も、完成したら出します、くらいにするかもしれません。

4.6 画面設計 (2回目)

必要になった画面をデザイナーおよびフロント担当にフィードバックして画面遷移図を再描画します。

4.7 期待動作洗い出し

これがコアです*4。仕様書の読み込みからシーケンス図(1回目)を描き、要件定義、画面設計の精度が高まったらここで正常系、準正常系、異常系すべての期待動作を洗い出します。ある状態のユーザがある端末である操作を行ったときの、画面遷移、DBの状態変化まで洗い出しておきます。DB 設計はこの後段のフェーズで行うのでここでは「年齢確認済となる」というような表現になっています。

これは設計のインプットであり実装のインプットであり QA テストのテスト項目であり...すべての動作を網羅した仕様書になります。CS チームにも共有することで何が仕様で何がバグかを明らかにすることもできます。今確認すると150パターンほどありました。大変ではありますが事前に苦労しておくには十分価値のある大変さではあります。複雑な案件ではさらにパターンが増えるでしょう。

4.8 連携先 API クライアント&モック開発

こちらについては連携先の仕様書が手に入り次第単独で取りかかれるアイテムです。1回目の要件定義が完了すればやろうとしていることの概略はわかるので、それを実現するためのクライアントを早々に書き始めます。Testability を上げるため、またサーバ本体に組み込んだときに実際に連携先と接続しなくても動かせるようにモックできるようにしておきます。これはあたりありふれた手法でありここで改めて言うほどのことではないとは思いますが...。

// TokenRequester is the interface that wraps the token Request method
type TokenRequester interface {
  Request(ctx context.Context, tokenEndpoint, code string) (*TokenResponse, error)
}

// TokenClient is client for token
type TokenClient struct {
   ...
}

// NewTokenClient returns new TokenClient
func NewTokenClient(config *TokenConfig) TokenRequester {
  return &TokenClient{
   ...
  }
}


// Request requests token
func (c *TokenClient) Request(ctx context.Context, tokenEndpoint, code string) (*TokenResponse, error) {
   ...
}

4.9 インフラ設計

連携先の仕様書をインプットとしてインフラ設計を行います。先方から配られるクレデンシャルであったり、セキュリティ面などの非機能要件や接続要件をインフラに落とし込みます。また本 PJ ではこちらのシステムの情報を先方に出す必要もあったためそれもこのフェーズでまとめておきます。

4.10 インフラ構築

実際の構築です。今回はそれほど土台の工事は必要ではありませんでした。概ね以下のような作業でした。

  • カンム <-> 連携先のアクセスコントロール
  • AWS Secrets manager へのクレデンシャルの格納

4.11 連携先との疎通テスト

先にやってしまおうと思っていた疎通テストです。上の方にも書きましたが当初の計画よりかなり後ろのフェーズでの確認になってしまいました。QA テストのタイミングで以下のようなバグが見つかるなど...。

  • 連携先の仕様書に変更があり通信時に応答が返らない (変更に気づかず...)
  • 単純に自分の書いた API クライアントにミスがあった

4.12 シーケンス図 (2回目)

今度のシーケンス図の粒度は自分以外のエンジニアとセキュリティエンジニアが理解できるように書きます。レビュー待ちが開発のボトルネックにならないようにするため、本PJに参画していなくてもコードレビュー可能なよう、この設計図自体もレビューにかけつつ、PJ外メンバーには内容を理解してもらいます。

「おおまかなシーケンス図」がシーケンス図(1回目)のことで「期待動作」がその名の通り期待動作洗い出しの成果物です。これらをインプットとして数々のパターンを網羅する形でシーケンス図を書きます。

4.13 ER図

期待動作洗い出しで DB の状態が定まったらデータチームからの要件も盛り込んで DB 設計を行います。ポイントとなるテーブルだけ抜き出すと次の様な設計としました。

  • ユーザ: 既存テーブル。このテーブルには変更なし。
  • 年齢確認リクエスト: 新規作成。どのような年齢確認手段(銀行、通信キャリア)においても必ず作られる。年齢確認の起点となるテーブル。ユーザは何回も年齢確認を行う可能性があるため、ユーザ:年齢確認リクエスト = 1:N となる。
  • 年齢確認リクエスト_手段A_固有情報: 新規作成。それぞれの年齢確認手段を行う際の固有のデータを格納するテーブル。年齢確認リクエストは各手段で0回(途中でアプリを落とすなど)または1回行われるため年齢確認リクエストと1:0..1。
  • 年齢確認リクエスト_手段A_実施: 新規作成。実際に年齢確認処理を行った際に記録されるテーブルで、年齢確認リクエスト_手段A_固有情報 と JOIN することでリプレイアタックが行われたかのチェックにも使う。年齢確認リクエストと1:0..1。
  • 年齢確認リクエスト_手段A_取得した生年月日: 新規作成。連携先から取得した生年月日を格納するテーブル。年齢確認リクエストと1:0..1。
  • 年齢確認_生年月日差異: 新規作成。連携先から取得した生年月日とカンム側に格納されているユーザの生年月日が異なっていた場合に作られる。年齢確認リクエストと1:0..1。
  • 年齢確認_完了:新規作成。連携先から取得した生年月日とカンム側に格納されているユーザの生年月日が一致していた場合に記録される。年齢確認リクエストと1:0..1。

ポイントは以下の通り。

  • 積み上げ式とする: 自分がこのような設計を好んでいるというのもありますが delete と update はしない方針とします。insert で状態を表現していきます。これはデータは存在していることに価値があるためです。ユーザ分析の要件としてもユーザがどの手段でどこまで年齢確認を進めたか?(ex: ある通信キャリアで途中まで進めたが離脱、後に銀行で年齢確認を試みて成功、など...)を知る必要があるためユーザの行動をトレースできるような設計としておきます。
  • 年齢確認手段が増えた際に対応できるようにする: 拡張性です。実は本人確認書類のアップロードによる年齢確認も可能なのですが、 PJ 発足当時はやるかやらないかが揺れていて、後続のフェーズになってようやく「やるかも?」くらいの状態になっていました。ここで本人確認書類のアップロードを含め、新しい年齢確認手段が増えたら手段Bのような形で年齢確認リクエストテーブルを起点にリレーションを生やす戦略としました。

投げる SQL とインデックスもここで示しておきます。テーブル設計は済んだが SQL が無茶になりすぎたら厳しい気持ちになるのでここで面妖な SQL にならないかの確認もしておきます。

4.14 アプリ/サーバ間の API 設計

バンドルカードのアプリとサーバの間の API 設計を行います。期待動作洗い出しとシーケンス図 (2回目) をインプットに API スキーマを考えます。上の図で「アプリ/サーバ間の API 設計」が ER 図とシーケンス図と同列になっているのは間違いですね...。

4.15 セキュリティレビュー (設計編)

1回目のセキュリティレビューを行います。シーケンス図、ER図、API設計をセキュリティエンジニアに見てもらいます。連携先の仕様書にセキュリティガイドラインも書かれているケースもあるので、それをクリアできているかどうかもチェックしてもらいます。

設計のタイミングでセキュリティエンジニアに入ってもらうのはとてもよいです。セキュリティエンジニアに最初からこのタイミングでレビューをしてほしいので工数もらっていいですか?と声をかけておいてよかった。

4.16 実装

セキュリティレビューのフィードバックを設計に盛り込むと、期待動作、各種設計、連携先のAPIクライアントが出揃います。ここでようやく本体の実装が可能になります。例えば年齢確認を実行する API エンドポイントについて、あらかじめまとめた期待動作を網羅する形でテストを書いていきます。期待動作をまとめてあるため比較的容易に TDD を行うことができます。このテストをパスするように本体の実装を書くことで、PdM やデザイナーやフロント担当などチーム内で決めた期待動作を実装の時点で結構な割合で保証することができます。

        t.Run("誕生日一致", func(t *testing.T) {
            ...


            res, err := executeAgeVerificationRequestDocomo(ctx, app, queryParams)
            assert.NoError(t, err)
            assert.Equal(t, constant.AgeVerificationSuccessURL, res)

            status, err := service.GetAgeVerificationRequestStatusByRequestIDAndUserID(ctx, tx, avr.ID, avr.UserID)
            assert.NoError(t, err)
            assert.Equal(t, constant.AgeVerificationRequestStatusCompleted, status)

            avred, err := model.GetAgeVerificationExecDocomoByPkContext(ctx, tx, avr.ID)
            assert.NoError(t, err)
            assert.NotNil(t, avred)
            t.Logf("age_verification_exec_docomo: %+v\n", avred)

            diff, err := model.GetAgeVerificationBirthdayDiffDetectionByPkContext(ctx, tx, avr.ID)
            assert.Error(t, err)
            assert.Nil(t, diff)
            t.Logf("age_verification_birthday_diff_detection: %+v\n", diff)

            completion, err := model.GetAgeVerificationCompletionByPkContext(ctx, tx, avr.ID)
            assert.NoError(t, err)
            assert.NotNil(t, completion)
            t.Logf("age_verification_completion: %+v\n", completion)

            userInfo, err := model.GetAgeVerificationIdentificationDocomoByPkContext(ctx, tx, avr.ID)
            assert.NoError(t, err)
            assert.NotNil(t, userInfo)
            t.Logf("age_verification_identification_docomo: %+v\n", string(userInfo.UserIdentificationData))

            bd, _, err := service.GetAgeVerificationBirthdayDocomoByRequestID(ctx, tx, avr.ID)
            assert.NoError(t, err)
            assert.Equal(t, time.Date(2001, 4, 1, 0, 0, 0, 0, time.UTC), bd)
        }
        t.Run("誕生日不一致", func(t *testing.T) {
            ...
        }
        t.Run("認証セッションがタイムアウトした", func(t *testing.T) {
            ...
        }
        t.Run("ユーザが同意しなかった", func(t *testing.T) {
            ...
        }
        t.Run("情報が存在しなかった", func(t *testing.T) {
            ...
        }
        t.Run("法人契約端末が使われた", func(t *testing.T) {
            ...
        }
        t.Run("レスポンスが返ってこなかった", func(t *testing.T) {
            ...
        }
        t.Run("メンテナンス中", func(t *testing.T) {
            ...
        }

4.17 セキュリティレビュー (実装編)

セキュリティのプロに実装の方も目を通してもらいます。セキュリティホール、ログに出したらいけないものを出そうとしていないか、セキュリティレビュー (設計編) で指摘された事柄がクリアされているかを確認してもらいます。

4.18 QA テスト

度々登場する期待動作一覧の表をもとにひたすらテスト -> 修正を繰り返します。このフェーズの前あたりに自分は離脱...。横目でバグの発生を見ながら申し訳ない気持ちになるなど...。

4.19 リリース

もはや私はいないのですが PdM がリリース体制を構築してくれました。対ユーザ向けにはバグなしで機能を提供できたはずです。 最終的なリリース日時について「その日・時間帯は避けろ」と横から口出しをしたくらいです。

5. まとめ

開発フローに以下のようなことを導入して一定の効果が得られました。

  • ウォーターフォールを見直して先人のヒントを盛り込むことである程度円滑に PJ を進めることができた。
  • 「2回やる」を局所的に導入した。局所的な導入でも今回については効果があり、要件定義の考慮漏れが回収された。
  • 早い段階からテスト計画を練り、期待動作の一覧を網羅したものを作った。その期待動作一覧を正解の仕様として設計、実装、リリース後の仕様/バグ確認として活かすことができた。
  • 上記により TDD が容易になった。
  • セキュリティレビューを早い段階から2回行い、セキュリティの観点からの修正を早期に行うことができた。

カンムのチーム環境としては以下の通りです。PJ運営をする際に製作サイドから見てしがらみが少なかったのは開発フローの柔軟性に大きく寄与されていると思われます。

  • 職種間のパワーバランスがそこまで大きくなく要件を決めたりスケジュールを引く権限が現場にあった
  • 開発スタイルは担当者に委ねられている
  • 必要に応じて各職種を巻き込むことができる

開発フローの策定においては開発するものの特性、難易度、規模、チームの状況、会社の事情、自社内と他社を含んだステークホルダーのパワーバランス、個々のスキル...といったパラメータが複雑に絡み合っています。これらを考慮して最適なフローを構築することが必要です。世の中によく知られた既存の開発フロー、例えば本記事でも挙げたウォーターフォールですが、半世紀以上前に考案されたものをそのまま適用するのは無理があります*5。当時の状況を鑑みて再現性を考慮する必要があります。今回の我々の PJ もたまたまうまく適合したのかどうかは振り返りを行い、成功に再現性のある事柄は他チームに展開するなどして自チームにあったスキームを育てていく必要があります。

おわり

*1:ポチっとチャージの年齢確認って?: https://support.vandle.jp/hc/ja/articles/31207907137817-%E3%83%9D%E3%83%81%E3%81%A3%E3%81%A8%E3%83%81%E3%83%A3%E3%83%BC%E3%82%B8%E3%81%AE%E5%B9%B4%E9%BD%A2%E7%A2%BA%E8%AA%8D%E3%81%A3%E3%81%A6

*2:おそらくこちら: 半自動式防空管制組織 https://ja.wikipedia.org/wiki/%E5%8D%8A%E8%87%AA%E5%8B%95%E5%BC%8F%E9%98%B2%E7%A9%BA%E7%AE%A1%E5%88%B6%E7%B5%84%E7%B9%94

*3:負荷試験や不可、異常系はやってはいけない、端末はカンムで用意、などテスト時に必要な情報が洗い出される

*4:振り返ってみるとこれをこのタイミングでしっかり作ったことがかなり大きかったと思っています

*5:古いからダメというわけではない

「React Native Meetup #17」を開催しました

こんにちは、カンムのエンジニアリングマネージャー佐藤です。

先日、React Native Japanと一緒に開催した「React Native Meetup #17」のイベントレポートをお届けします!前回に引き続き、今回もたくさんの方にご参加いただき、賑やかな会になりました。

皆さんのご参加、本当にありがとうございました。それぞれの発表者が共有してくれた経験や知見のおかげで、懇親会までとても充実した時間を過ごすことができました。

発表内容のご紹介

@mtry さんの「EAS Custom Buildを使ってビルドの開始/完了をSlackに通知する方法」

@mtry さんは、EAS Custom Buildを使ってビルドの進捗をSlackに通知する仕組みを発表してくれました。弊社ではExpoは使用しておらず、EAS Build自体の知見が少ないので、非常に興味深く聞かせていただきました。ぜひチェックしてみてください。詳しくはこちらからどうぞ!

@katayama8000 さんの「Rustで作ったExpo Push Notification Clientが公式ドキュメントに掲載された話」

@katayama8000 さんは、Rustで作ったExpo Push Notification Clientが公式ドキュメントに掲載されるまでのプロセスを発表してくれました。Discordでの地道な活動を重ね、少しずつコミュニュケーションを取っていく過程に苦労が垣間見え、とても引き込まれる内容でした。

@kondo_script さんの「React Nativeで防衛戦をする方法」

@kondo_script さんは、React Nativeを使ってプロジェクトの品質を守るための具体的な方法について発表してくれました。ビジネスとの合意形成や、内部品質を泥臭く保守する様子など、非常に共感できる実践的なアプローチが紹介されました。詳しくはこちらもご覧ください。

@hiraikyo さんの「React Native + Cloudflare Workerで個人開発アプリを作る話」

@hiraikyo さんは、React NativeとCloudflare Workerを使って個人開発アプリを作り上げた一連の流れを紹介してくれました。企画から設計、実装までのプロセスを通じて、個人開発ならではの面白さが詰まった内容でした。詳しくはこちらをご覧ください!

余談

今回のイベントにあたり、前回の開催後に課題の洗い出しを行い、少し設備やフローを整えました。その結果もあって、メンバーが前回の経験を活かし準備がすごいスピードで進んでいたり、設営と同時にドキュメントが出来上がっていたりしました!

まだまだ不便をかけてしまった部分や課題はありますが、今後も様々な社内イベントが円滑にできるように改善を繰り返していこうと思います。

最後に

今回も無事にイベントを進行でき、スポンサーとして参加できたことに大変感謝しております!ご参加いただいた皆さん、スピーカーの皆さん、そしてサポートしてくれたReact Nativeコミュニティやスタッフの皆さん、本当にありがとうございました!

カンムではフロントエンドエンジニアを募集中!

カンムでは現在、フロントエンドエンジニアを募集しています。「お金の新しい選択肢をつくる」というミッションに共感していただける方、ぜひ一緒に働きませんか?興味のある方はお気軽にご連絡ください!

team.kanmu.co.jp

バンドルカードが Google Pay™ に対応しました

バンドルカードのバックエンドエンジニアをしているshibaです。生粋のiPhoneユーザです。

昨年の10月頃にバンドルカードは Google Pay に対応しました。少し遅くなってしまいましたが、 Google Pay 対応について簡単に紹介したいと思います。なお、 Google Pay というアプリ名は2023年3月頃からGoogle ウォレット に変更され、 Google Pay はGoogle ウォレット 内の1機能という扱いになっています。

Google Pay について

まず、 Google Pay や、 Google Pay を使った決済の仕組みについて簡単に紹介します。

まずバンドルカードの説明になりますが、バンドルカードのアプリをインストールし、電話番号を使ってアカウント登録することで、バーチャルカード(オンラインのみで利用できるプリペイドカード)を即時発行することができます。 Google Pay 対応以前は、バンドルカードを実店舗で利用するにはリアルカードもしくはリアル+カードと呼ばれる物理カードの発行が必要でした。物理カードを発行するには発行費用や諸手続きが求められ、加えて、カードが家に届くまで待つ必要がありました。しかし、今回 Google Pay に対応したことによって、バンドルカードのアプリをインストールしてアカウント登録後、 Android 端末に搭載されている Google Pay にカードを登録することで、物理カードを発行する必要なく、すぐに実店舗で Google Pay を通したVisaのタッチ決済ができるようになりました。アプリをインストール後すぐに実店舗で決済できるというところは、バンドルカードと Google Pay の素晴らしい体験だと感じています。

続いて、 Google Pay を使った決済の仕組みについて紹介します。Google Pay を利用しない従来の決済であれば、PAN(カード番号)を含んだ決済データが加盟店様からVisa様のネットワークを通して弊社のようなカード発行会社に連携されます。一方、 Google Pay を使った決済では、PANではなく、トークンと呼ばれる、カードに紐づく一意なIDを含む決済データが連携されるようになります。トークンは、 Google Pay にバンドルカードを登録する際にVisa様を介して発行され、 Google Pay にはPANではなくこのトークンが格納されています。Google Pay を使った決済では、PANの代わりにトークンが加盟店様からVisa様に連携され、弊社のケースではそこから、Visa様がトークンをPANに変換した上で決済データが弊社に連携される仕組みになっています。Google Pay を使ったフローの全体像としては次のようになります。

Google Pay のトークン発行及び決済のフロー

PANの代わりにトークンを使うメリットですが、最も大きいのは漏洩時のリスクを抑えられることだと考えています。Google Pay に格納されるトークンは、カードを登録した特定の Android 端末および特定のGoogleアカウントに紐づいているため、 Google Pay に格納されているトークンの番号が仮に漏洩したとしても他の端末では決済ができないはずです。(実際に試したわけではないため、この情報の信憑性は担保できていません。)Visa様のネットワークまでの経路においてPANではなくトークンでやりとりされることは、カード所有者にとって非常に安全で良い体験だと感じています。

Google Pay 対応について

Google Pay に対応するにあたって、弊社が対応した内容を簡単に紹介します。バンドルカードを Google Pay で利用できるようするためには、大きく次の2つの要件を満たす必要がありました。

  • カードに紐づくトークンの発行を可能にすること
  • Visaのタッチ決済を処理できるようにすること

まずトークンの発行を可能にするために行なった弊社の対応について説明します。先に説明しましたが、バンドルカードを Google Pay に登録する際に、カードに紐づくトークンをVisa様が発行しており、発行されたトークンが Google Pay に格納されています。トークンの発行及び管理はVisa様が行なっております。ただし、Visa様は弊社のカードやカード所有者についての詳細な情報を把握していないため、 Google Pay にカードを登録する際に「トークンの発行を承認しても良いカード及びカード所有者か」どうかを弊社が都度判断する必要があります。カードを Google Pay に登録するフローの裏側では、Visa様と弊社のサーバ間でAPI連携がされており、弊社はAPIを通じて、この、トークン発行を承認するかどうかという判断をしています。加えて、カードを Google Pay に登録するフローの一部ではOne Time Passwordが求められますが、その認証コードを弊社がユーザに連携したりもしています。なお、 Google Pay にカードを登録するフローは2つあります。まず Google Pay のアプリから直にカード情報を入力するフロー、そして、バンドルカードのアプリのトップ画面に表示されている「 Google Pay に追加」ボタンをタップして登録するフローの2つです。

Android 端末におけるバンドルカードのTOP画面

Google Pay に追加」ボタンをタップするフローにおいては、 Google Pay に用意されている Push Provisioning という仕組みを利用しています。いずれのフローを利用しても、裏側では先に説明したAPI連携がされています。このように、Visa様とのAPI連携及びPush Provisioningの対応をすることで、弊社はトークンの発行に対応し、 Google Pay にカードを登録できるようにしました。

次にVisaのタッチ決済を処理できるようにするために行なった弊社の対応について説明します。決済データはバイナリ形式でVisa様から弊社に連携され、弊社のProcessorと呼ばれるシステムがバイナリをパースして処理しています。Visaのタッチ決済では従来のバイナリデータに新しいフィールドが追加されて送られてくるため、既存のパース処理を拡張する必要がありました。そのため、Visa様が公開している決済データのドキュメントを確認し、タッチ決済時に追加されるフィールドの仕様を確認した上でパース処理を追加することで、Visaのタッチ決済を処理できるようにしました。余談ですが、弊社に入社した当時から、カンムのエンジニアはバイナリをパースする特殊な集団であるというイメージがありました。どこかのタイミングで自分もバイナリのパース処理を扱ってみたいと思っていたので良い機会になりました。なお、バイナリ処理については2022年度のGoConにて、関連したクイズを弊社が出題しているので参考にしてみてください。

tech.kanmu.co.jp

プロジェクトを振り返って

プロジェクトを振り返ってみると、リリースまでに1年強の期間を要しましたが、グローバルにサービスを展開するVisa様とこのような長期プロジェクトを進められたことは、弊社ならではの貴重な経験だったと感じています。Visa様とのプロジェクトには特有の面白さがありました。例えば、プロジェクトの初期段階でVisa様のシンガポール支社にいるエンジニアから英語でプロジェクト概要の説明を受けたり、設計レビューでこちらから英語で質問をする機会があったりしたことは面白かったなと感じています。また、プロジェクトはウォーターフォール方式で進められました。プロジェクトを開始する前にVisa様からリリースまでのタスク一覧とスケジュールの目安が提示され、全体のスケジュールやタスクなどを詰めた上でプロジェクトが開始されました。ウォーターフォール方式で長期プロジェクトを進めた経験がなかったので良い経験になりました。

特有の面白さとして1つ関連したエピソードを紹介します。Google Pay に対応するにあたって、本番リリースする前に試験環境でタッチ決済の動作確認をする必要がありました。Visa様から提供される試験環境の決済用のソフトウェアはWindowsで実行する必要があるため、弊社では手元のMacからRDPでWindowsインスタンスに接続して決済を試すようになっています。従来の決済の動作確認は、このソフトウェアで完結しますが、タッチ決済の動作確認をする場合はカードリーダーをWindowsに接続し、カードリーダーに Android 端末をタッチして試す必要がありました。弊社ではuTrust 4701 F デュアルインターフェーススマートカードリーダーを購入し、Windowsに接続を試みましたが、Windowsインスタンス側でカードリーダーが認識されず、頭を抱えていたことがありました。動作確認の期日が迫っていたこともあり、先輩のsummerwind が「俺にやらせてみろ。とりあえずカードリーダーをスカイツリーに持ってきてくれ。」と伝えてくれたので、スカイツリーでカードリーダーを受け渡したのですが、その後、すぐに原因を特定して問題を解決してくれました。物理カードリーダーのドライバはMacにインストール済みであったもののNFCリーダーのドライバをMacに追加でインストールする必要があったようでした。力量の差に空いた口が塞がらなかったのは良い思い出です。

余談ですが、 Google Pay 対応のリリース後にTwitterで見かけたコメントが印象に残っています。Google Pay 対応のリリース後、何か問題が起きていないかを確認するために、Twitterエゴサーチをちょくちょくしていたのですが、リリース直後に次のようなツイートを見かけて大変嬉しく感じたことを覚えています。良い声も悪い声もですが、こういったユーザの声をダイレクトに確認できるところは、toCサービスならではだと感じています。

バンドルカードがGooglepayに対応したからpixelwatchでもタッチ決済に使えるようになったんだが えっめちゃくちゃ楽じゃん 助かる…… どんどん使いやすくなる…… https://x.com/Ruri_Midorimiya/status/1714272066302886004

最後に

私はiPhoneユーザなので、Apple Payの対応を心待ちにしていたりします。TwitterでもApple Pay非対応を嘆く声を度々見かけます。バンドルカードはApple Payにも対応する予定ではあるのですが、エンジニアの人数も少なく、手が回っていません。

ということで、バンドルカードではプロダクトを改善するバックエンドエンジニアを募集しています。ご応募お待ちしております。

kanmu.co.jp

AndroidGoogle Pay、Google Wallet は Google LLC の商標です。

「React Native Meetup #16」を開催しました

こんにちは、カンムのエンジニアリングマネージャーの佐藤です。カンムでは5月30日(木)に、React Native Japanコミュニティと協賛してReact Native Meetup #16を開催しました。本記事では、その様子をレポートしました。

当日の様子

イベントは、弊社オフィスの会議室で行われました。当日は30人ほど集まり、とても活気のある勉強会となりました。発表の後には懇親会も設けられ、皆様の意見交換や情報共有が盛んに行われました。

発表内容の紹介

今回は、弊社の社員含め5名のスピーカーからLTが発表されました。

1. 「React Navigation v7で導入されるStatic APIについて」

弊社からは私が、React Navigation 7で新たに導入されるStatic APIについて発表させていただきました。 背景や変更点、それによって何が嬉しいのか?弊社はどのような課題を解決できたのか?などをお話しさせていただきました。 詳しくは資料をご覧ください。

2. @yukukotani様「Capacitor製のWebViewアプリからReact Native製のハイブリッドアプリへ」

Ubie 社の@yukukotani様は、Capacitor 製のWebViewアプリをReact Nativeに移行する際のお話をテーマに発表されました。 React Nativeへ移行されたモチベーションとして、Capacitorに比べエコシステムの維持が強固であること、Next.js & TypeScriptで作られたアプリケーションとの親和性などを挙げられていました。 資料と合わせてご覧ください。

3. @mok_oshi様「React Nativeでスケジュール帳を作っている話」

@mok_oshi様の発表は、React Nativeを用いた美容サロンの予約システムKarutekunを作成する過程での経験を発表されていました。カレンダービューというとても複雑なUIを、FlatListScrollViewを巧みに組み合わせて表現されているのがとても印象的でした。 資料はこちらからご覧ください。

4. @kazutoyo様「React Native Skiaを使ってみよう!」

@kazutoyo様の発表は、ShopifyチームがメンテしているReact Native Skiaについてでした。Skiaは過去にFlutterが採用していた2Dグラフィックライブラリで、React Nativeでもリッチな体験を提供できます。 実際に動く多数のサンプルが掲載されている資料もぜひご覧ください。

5. shibafu-san様「WebViewを使って既存のウェブアプリをReact Nativeアプリに組み込む話」

shibafu-san様は、既存のウェブアプリとして展開しているL COLLECTIONなどのサービスをWebViewを用いてReact Nativeアプリに統合する方法について発表しました。

最後に

弊社では初となるエンジニア勉強会の開催でしたが、無事にイベントを進行できました。これもイベントへ参加してくださったゲストの皆様、スピーカーの皆様、そして準備にご協力いただいたReact Nativeコミュニティ及び当日スタッフの皆様のおかげです。心よりお礼申し上げます。

カンムではフロントエンドエンジニアを募集しています!

カンムでは現在、フロントエンドエンジニアを募集しています。私たちと一緒に「お金の新しい選択肢をつくる」ミッションを達成するを仲間を求めています。興味のある方は、ぜひご連絡ください!

team.kanmu.co.jp

【Go Conference 2024】プロポーザルに通ったカンムのエンジニアが準備したこと、通った後にやったことʕ◔ϖ◔ʔ

ソフトウェアエンジニアの@sho-hataです。 2024年6月8日(土)開催のGo Conference 2024に、ソフトウェアエンジニアの私と@bisho-joの2人が登壇します。また、カンムはブロンズスポンサーとして協賛します。

gocon.jp

カンムが提供している バンドルカードPool のバックエンドは主に Go で開発しており、スポンサーとして継続的に協賛させていただいております。

tech.kanmu.co.jp

tech.kanmu.co.jp

このイベントを通して Go コミュニティの発展に寄与できればと思っています。

セッションに登壇します ʕ◔ϖ◔ʔ

バンドルカード ソフトウェアエンジニアのbisho-jo氏は、

"Guide to Profile-Guided Optimization: inlining, devirtualining, and profiling"

というタイトルで、Goの公式コンパイラの最適化についてのshort(20 min)セッションを行います。

Go1.20で追加されたPGO(Profile-Guided Optimization)が、関数のインライン展開と脱仮想化という2つの最適化手法にどのように影響を及ぼすか解説してくれるそうです。私はひと足先に練習でセッションを聴きましたが、とても興味を惹かれる面白い内容でした。 何よりbisho-jo氏が楽しそうに発表しており、技術を愛するGeek色が感じることができてとても良かったです。

Poolのソフトウェアエンジニアの私は、

"GoのLanguage Server Protocol実装、「gopls」の自動補完の仕組みを学ぶ"

というタイトルで、Goのコーディング支援機能を提供するgoplsの自動補完機能についてのshort(20 min)セッションを行います。

VSCodeのGo拡張機能のデフォルトバックエンドにもなっているgoplsの補完機能は、スマートかつ適切に補完候補を提供してくれます。今回はその裏側で動く泥臭く堅実な処理を、参加者の皆さんと共にのぞき見ようと思います。

プロポーザルに通るためにやったこと、通った後にやったことʕ◔ϖ◔ʔ

直近のGo Conferenceでは、個人応募のセッションに加えてGoルドスポンサーのセッションという形で、継続的に登壇させていただいておりました。ですが近年のGoコミュニティの盛り上がりもあり、今年はGoルド以上のスポンサーの抽選が熾烈な争いに。惜しくも今年はGoルド以上のスポンサーの抽選に外れてしまいました(いや、カンムの近年の運の良さが異常だったとも言える... ʕ◔ϖ◔ʔ)。

今まで奇跡的にGoルド以上のスポンサーに通っていたので、震撼する面々
今まで奇跡的にGoルド以上のスポンサーに通っていたので、震撼する面々2

セッションで登壇するために、プロポーザル採択という狭き門をくぐる戦いが始まりました。 ここからは、Go Conferenceにプロポーザルを通したい方に少しでも良い知見が得られるよう、自分を含めカンムメンバーがやってきたことを書きます。

プロポーザル締切に間に合うように発破をかけあう

まず、セッション登壇に意欲のあるメンバーが集まるSlackチャンネルを作り、期限を設定して発破をかけあう体制が作られました。

個人的な経験で何度も痛感していますが、プロポーザル資料を作ったり発表資料を作ったりする作業はどーーーーーしてもギリギリになりがちです。そして毎回、締切前に急いで考えて、推敲する時間がなくそのまま投稿..ということになりがち。 誰かがちょっとづつでもプロポーザルを書くのを進めているのを見ると頑張ろうとなります。これはやって良かったことでした。

プロポーザルの相互レビューをする

締め切りが近づいてきたら、たとえ推敲できてなくてもとりあえず下書きを放流し始めました。

放流すると、カンムメンバーは的確なコメントをくれるので、どんどんシェイプアップされて良い内容に。自分一人で煮詰まって考えるより、他の人と考えたほうが産みの苦しみはかなり減りました。 プロポーザル内容は、Go Conferenceの審査基準をベースに、聴く人が興味を持ってくれるか・新規性・独自性はあるかなどの観点をかなり重視していました。 過去のGo Conferenceのプロポーザルを読んだり、ネタ探しの例を読んだり。

そして、自分なりに納得したところで、プロポーザルを提出。あとは祈るのみです。

発表練習

プロポーザルの結果が発表され、カンムからは2人登壇できることに。 これは正確な情報ではなく噂で耳にしたのですが、プロポーザル応募は100を超えたとか。狭き門でのこの結果ということで、とても嬉しい結果でした。

発表が決まったということで、登壇に向け資料作りを開始。ここでも、登壇前に発表練習をする時間を設定して締め切りを作ることで、登壇の3日前には最低限のクオリティを担保した発表ができるようにしました。

発表練習はカンムのエンジニアに集まってもらい、聴講してもらいました。ここでもメンバーは的確なフィードバックをくれるので、シェイプアップされてより良い内容に。 ちなみに2人そろって3分オーバーでした。まあ当日にはなんとかなるだろう...

おわりに

ここまで、Go Conferenceのプロポーザルに通るために&通ってからカンムメンバーでやってきたことを書きました。 久しぶりのオフライン開催ということで、オフラインならでの熱気、盛り上がり、そして今回のテーマである「一期一会」を体験できることをメンバー一同楽しみにしています。

当日、Abema Towersでお会いしましょう!

イベント当日は会場にスピーカーの2人がいます!会場スポンサーのサイバーエージェントさん、ありがとうございます🙌

  • カンムやバンドルカード・Pool について聞いてみたい!
  • 登壇していたエンジニアと話してみたい!
  • Go についてわいわい話したい!

team.kanmu.co.jp

などなど大歓迎です!当日、登壇者の2人はスピーカーTシャツを着て会場を練り歩いている予定なので、お気軽に話しかけてください!本当に!(大声)

Poolのソフトウェアエンジニアを募集しています

ソフトウェアエンジニアのhataです。

Poolはソフトウェアエンジニアの募集を公開しました。

ソフトウェアエンジニア(フロントエンド)- Pool / 株式会社カンム

今回の記事では、Poolというサービスを開発する面白さや、直近抱えている課題について紹介します。

Poolについて

pool-card.jp

Poolは投資と決済が一体となった、国内でもあまり類を見ないサービスで、値動きを気にせず資産運用ができる特徴と、投資した金額をVisaカードの利用可能額として使える特徴を持っています。これによりカードを使いつつ資産形成していけるという、他にはない投資体験ができることを目指しています。

2022年6月にPoolをリリース後、3ヶ月で累計投資金額が1億円を突破。その後も、サービス開始以降、正常運用率100%(※11)で着実に運用実績を積み重ね、継続的にユーザーの方に使っていただいております。

Pool は投資というより「置いてある」に近い感じ | ユーザーインタビュー vol.1 | Pool [プール]

Poolが目指している世界

カンムは「お金の新しい選択肢をつくる」というミッションを掲げています。一般的な投資サービスでは、投資資金と日々のお買い物は別々に管理されており、投資資金を急な出費に使うにはハードルがあります。Poolでは、「投資をしながら日常の買い物もできる」という、今までなかった投資・決済体験を「お金の新しい選択肢」として提供することを目指しています。

これまでは銀行が預金・融資によって支えていた間接金融が資金の流れの中心でした。現在は自社のバンドルカード事業が主な投資先ですが、投資先事業の拡充を予定しています。Poolを通じて、資金を増やしたい人に対しては新たな投資体験を、お金を必要とする会社等に対しては新たな資金調達の手段を作り、新しい資金の流れを広げてまいります。

Poolの開発の面白さ

決済と投資を組み合わせたユニークなサービスの開発に、「このサービスがどうあるべきか」の議論から携わっていただくことができます。 金融サービスはユーザーにとって難しい、複雑、といったイメージを持たれることが多く、その専門用語や複雑な手続きがしばしばハードルとなっています。しかし、そんなハードルを越えるために法律や規制といった要素を考慮しつつ、ユーザーにとってわかりやすい体験にするにはどうしたら良いのかをイチから考えることは非常にやりがいがあります。

また、そのハードルの越え方も非常にユニークです。Poolは投資した金額をVisaカードの利用可能額として使えるという特徴を持っていますが、これは既存の法的なスキームを組み合わせて実現しています。既存の法的なスキームを整理・組み合わせて、新しい体験を提供していくのは、カンムの特徴のひとつです。

そのため、開発者としてアプリのユーザー体験について議論をする際も、「文言・表現を変える」「導線を変える」以外に、「この法律をこう整理して、このようにしていけば課題をクリアしつつ、新しいことができるのではないか」という選択肢が常にあるのが面白いです。"法律"や"制度"など他の領域であれば足踏みしてしまうところに対しても、より良い改善を仕掛けることができるというのは、エンジニアにはハックマインドにも通じるところがありなかなか面白いのではないかと思っています。

「投資をしながら、買い物もできる」 さながらユーザーにとっての小さな銀行のような、既存のサービスとは全く違う新しい体験を自分たちでデザインし、他に参考となる先例がない中でウンウン言いながら作り上げていくことができるのは、他にはない醍醐味だと思います。

ソフトウェアエンジニアを必要としています

カンムは2023年に三菱UFJ銀行の子会社となり、Pool事業も今後さらなる事業拡大を目指していきます。事業拡大に伴い、開発速度を上げて改善サイクルを素早く回すことが必要です。今後もっと成長するプロダクトを支えるために、エンジニアリソースをより拡充し、投資していきたいフェーズにあります。

しかし現状4名のソフトウェアエンジニアがフロントエンドからバックエンド、インフラ、PCI DSS(クレジットカード業界の情報セキュリティ基準)をはじめとしたガバナンス対応まで幅広く対応しており、目指したいサービスの改善・成長速度に届いていないのが現状です。

技術スタックについて

Poolで使用している技術スタックについては、下記を参考にしてください。 ネイティブアプリのフロントエンドはTypeScript・React Nativeで開発しており、APIサーバー、決済システムといったバックエンドはGoで開発しています。

ソフトウェアエンジニア(フロントエンド)- Pool / 株式会社カンム

さいごに

Poolは決済と投資という2つの領域に跨ったサービスです。現状調べた限り同じようなサービスは国内にはなく、ユーザーにとっては新しい体験となります。 「なにこれすごい、初めてみた」という体験を追求しつつ、金融サービスという性質から、ユーザーが安心してサービスを利用できるようなUI/UXを両立していかなければなりません。 金融サービスの特性を理解しつつも、いちユーザーとしてフレッシュな視点でサービスを見つめ、固定観念にとらわれない新しい体験を一緒に議論し、作り上げていただけるエンジニアを求めています。

金融未経験の方や、直近の課題に関してすべて精通していなくても問題ございません。少しでも興味が湧いた方は、採用ページよりご連絡をお待ちしております。また、「話を聞いてみたい」といったカジュアル面談もお待ちしています。

Poolチーム エンジニア以外の職種インタビュー記事

note.com

note.com


  1. 2024年4月までに運用が終了した全21ファンドのうち、運用終了時点のお客様の出資持分を全額返還及び予定分配金額をお支払いできる状態になった割合です

カンムの機械学習インフラの今 2023 年版

こんにちは。ソフトウェアエンジニアの新田です。こちらは カンム Advent Calendar 2023、8日目の記事です。 昨日はデザイナー torimizuno さんによる バンドルカードの Google Pay デザイン でした。今年のバンドルカードの目玉リリースの1つであるスマホタッチ決済(Google Pay)のデザインについて説明されていて、凄く面白いです。

今回は、カンムの機械学習のインフラ周りについて話します。実はカンムのテックブログでは2年半前に同じテーマの記事があります。この内容からいくつかアップデートがあるので、今回はその差分を重点的に拾っていこうと思います。

tech.kanmu.co.jp

また、自分は入社してそろそろ一周年で、前回の記事は入社前に読んでいました。今回の記事では、入社前ではわからなかったところもあえて注目して取り上げてみたいなと思います。

Big Picture

機械学習インフラ全体図

Data Preparation

BigQuery データウェアハウスの刷新

クラウドの構成は以前同様、プロダクトは AWS をメインに使いつつ、データ基盤は Google Cloud の BigQuery に集約する構成は一緒です。しかし BigQuery 基盤自体は以前と違うものになっています。

これまでのカンムのデータ分析環境では、本番環境の DB からレプリケーションしている分析用のリードレプリカを利用していました。一方で、データベースは複数存在しているため、リードレプリカを利用した環境では複数データベースをまたいだデータ分析が難しいという課題がありました。

そこで複数のデータソースを一つの BigQuery によるデータウェアハウスに集約することを念頭において基盤を再構築しました。これによって複数のデータソースを横断した分析ができ、事業KPI をより精緻なかたちで算出できるようになるなど、データによる意思決定の精度が上がっています。

例に漏れず機械学習の学習データセットも、この新しい BigQuery 基盤を使うように変更しています。

Data Preparation

以前の構成では Embulk を利用していた BigQuery へのデータの取り込み周りも、新しい基盤では異なります。

カンムではプロダクトのデータベースは AWS Aurora を使っています。そこでエクスポート機能を使って S3 にデータをエクスポートします。

docs.aws.amazon.com

そのあと S3 から Google Cloud の Cloud Storage への転送を Storage Transfer Service で行います。

Cloud Storage に到着したデータを BigQuery の外部テーブルとして扱い、 BigQuery のテーブル・ビューとして整形するパイプラインを整備しています。

いわゆる Transform と呼ばれるこの一連のパイプラインは dbt を ECS Task として実行するような Step Functions で構成しています。 AWS から Google Cloud のリソースの認証には OIDC 方式を採用して鍵ファイルの管理を不要にしています。

このデータ取り込みにおいて、以前の構成の Embulk ではワークロードのリソース調達が必要になっていましたが、新しい構成では完全なマネージドなためその辺りの考慮が不要になったのは嬉しいポイントです。

以前同様、データ取り込みは差分更新ではなく全件洗い替えを日次実行しています。この辺はデータサイズ増加に伴うコスト面などの問題などがでてくると思うのでその際には改善を検討していきたいと思っています。

Training, Serving & Inference

推論モデルの増加

推論モデルは去年までは1つだったんですが、今では3つに増えました。

これまで学習データ不足などから機械学習でアプローチできていなかった課題に対しても、機械学習の推論モデルを徐々に導入できています。

いまのところ全てのモデルにおいて、同じアルゴリズムをベースに学習しています。

1つのリポジトリにて、ライブラリやウェブフレームワーク・ Web サーバ構成などは共通しており、デプロイパッケージは1つのコンテナイメージとして管理しています。

コンテナ実行時に環境変数を渡すことで実行する学習コードや推論コードが切り替わるようにしています。これによりモデルの数が増えてもロジック以外のセットアップが少なく完結できます。

これは利用するアルゴリズムを限定するつもりではなく、いまは1つのアルゴリズムで間に合っているためであり、新たなアルゴリズムが必要になったらその時々に拡張していければいいなという考えです。

推論モデルのデリバリー改善

以前と変わらず、モデルの学習から検証用のエンドポイントへの適用までを Step Functions によって自動化しています。

そのあとのモデルの性能やエンドポイントの動作に問題がないか確認をしたのちに、本番用のエンドポイントに適用する流れも同じです。ただ、この辺の作業が以前までは手動で行われていたのですが、運用するモデルが増加するにあたってこの手作業はトイルになってしまうので自動化しました。

自動化のために2つの GitHub Actions のワークフローを用意しています。

モデル更新のワークフロー

1つ目のワークフロー(図右) はモデルの性能結果を示す Pull Request を作成します。それをチームメンバー達でレビューしマージします。 マージを契機に、 2つ目のワークフロー(図左) が本番エンドポイントに EndpointConfig を適用します。これにより新しいモデルが本番にデプロイされます。

endmame によるデプロイ

2つ目のワークフローはデプロイに endmame という内製の CLI ツールを使って endmame deploy コマンドでデプロイを実行します。 ( えんどまめ と読みます。カンムでお世話になっている ecspressolambroll をリスペクトしています。)

この endmame のコマンド endmame deploy は、設定ファイルを読み込み、新しい EndpointConfig を適用するかたちで、対象の SageMaker Endpoint に更新します。 (sagemaker:UpdateEndpoint をコールし、デプロイが完了するまで sagemaker:DescribeEndpoint で状態をポーリングします。)

処理の流れ

1つ目のワークフローの実行

まず1つ目のワークフロー (図右) はあらかじめ用意した Jupyter Notebook のファイルを papermill を使ってバッチ実行し Pull Request の作成までを行います。

Jupyter Notebook はセルを順番に実行すると以下の処理が実行されるようになっています。 papermill をとおしてそれらの処理を GitHub Actions 上で実行します。

  1. 検証用エンドポイントからモデルの情報を取得し、それから学習結果のログやメトリクスを取得する
  2. 学習結果のログ、メトリクスの内容をセルに出力する
  3. 検証用エンドポイントにテストデータを用いて実際にリクエストしレスポンス内容をセルに出力する
  4. 本番エンドポイントの設定に対応している endmame の設定ファイルを編集する

このワークフローは上流の Step Functions の処理の完了時間からバッファを持たせて GitHub Actions の on.schedule によって自動で実行されるようにしています。また、開発者が任意のタイミングで実行できるように on.workflow_dispatch による手動実行も可能にしています。

このワークフローは以下の処理を行います。

  1. 上述の Jupyter Notebook を papermill によってコピー & 実行
  2. リポジトリをチェックアウトし新規ブランチを作成
  3. 新規に作成されたコピー先の Notebook ファイル と 変更された endmame 設定ファイル をコミット
  4. Pull Request を作成

つまりこの Pull Request のコミット内容は、検証エンドポイントにデプロイされた新しいモデルの性能評価結果および API サーバの動作検証結果がレポートされた Notebook ファイルの追加コミットと、本番エンドポイントに適用する予定の設定ファイルの変更コミットになります。

作成された Pull Request のレビュー・マージ

モデル開発チームはその自動作成された Pull Request の内容 ( Notebook の内容と設定ファイルの変更内容) を確認し、問題なければ承認しマージします。

2つ目のワークフローの実行

マージを契機に2つ目のワークフロー(図左)が動作します。

endmame deploy コマンドを実行して、 Pull Request で変更された設定ファイルを読み込んで実際に本番エンドポイントを更新します。

このワークフローの実行が完了したらデプロイの成否が Slack に通知されるようにしています。

ちなみにですが、1つ目のワークフロー上で、デフォルトの GITHUB_TOKEN によるユーザとして Pull Request を作成してしまうと、その Pull Request 上で GitHub Actions の CI が回らないので注意が必要です。

cf. https://docs.github.com/en/actions/using-workflows/triggering-a-workflow#triggering-a-workflow-from-a-workflow

解決策として GitHub App のインストールとして認証を行い git push や Pull Request 作成の操作等を行うようにしています。

自動化の意図

デリバリープロセスの自動化の意図は「モデルが増えてきた作業時間の増加を短縮したいから」「モデル自体を入れ替えないタイプの変更 (ライブラリのアップデート) なども素早く安全にやりたいから」「強力な権限による手動操作の排除」etc… などがありますが、それに加えて、データセットシフトなどの問題に対する推論モデルのロバスト性を高めるために、性能評価プロセスをより高度なものにしていきたいと考えているためです。

そこで、あらかじめプロセスを自動化しておくと今後のプロセス改善はラクになります。今回構築したフローでは、テンプレートの Notebook ファイルを編集するだけで、それ以降の実行に反映されます。作業手順書のメンテナンスの必要がなくなりますし、手順を誤ると起きてしまうヒューマンエラーを防いでくれます。

評価プロセスの改善は今後力を入れていきたい領域の一つであるため、アップデートがあればまた記事にできたらいいなと思っています。

推論処理の課題

全てのモデルの推論処理は、「モデルをメモリにロードしておき、リクエストを受け付けると推論処理の結果を返すサーバを稼働するアーキテクチャ」 (リアルタイム/オンライン推論サーバ)になっています。

つまりオンライン処理時点で入力特徴量の取得が必要になります。 現状では、呼び出し側のサービスがデータベースからデータを取得・計算して入力特徴量を組み立てて、推論エンドポイントにリクエストを投げています。

推論処理の流れ

図をみて勘の良い方はお気づきかもしれませんが、学習時と推論時に異なるデータソースを用いているため計算の再現性に気を付ける必要があります。モデルの数や特徴量の増減のたびにこの辺りに慎重な実装を要していることや、学習に利用するデータソースの種類を増やした場合に推論時はそれをどのように取得するか… などの考慮事項があり、色々と課題が表出しているため来期は取り組んでいきたい領域の一つです。

Development

ローカルマシンによる開発環境もありますが、重い計算をしたい場合もっと大きなインスタンスが欲しくなるという要望を受け、クラウド上で開発できる環境を整備しています。

SageMaker Studio を使った JupyterLab ベースの実験環境です。いくつか工夫して使っていて、アイドルインスタンスを自動停止する仕組み、 OIDC 連携の仕組み、デバイスフローで GitHub App のユーザアクセストークンを発行してプライベートのリポジトリを操作する仕組み… などです。

実は今回の記事は本当はその辺りの話を書きたかったんですが、この記事の一週間前に SageMaker Studio に大幅なアップデート がアナウンスされて、これまでのものが SageMaker Studio Classic と名称が変更されました。あんまり Classic の話を書いてもな… と思って、今回の記事の内容を方針転換した経緯があります 😇

基本的に JupyterLab ベースの開発ですが、何時間単位となるような長時間の計算は JupyterLab は不向きな場合が多いためその辺りをシームレスにジョブ化できるような開発体験構築も検討していきたいと思っています。

最後に

機械学習のインフラ周りについて、前回の記事からのアップデートを重点的に説明しました。

お察しの方もいらっしゃると思いますが、そこまでモダンな MLOps ! といった構成ではなく、必要なときに必要な自動化や改善をしてきて今のインフラになっています。途中途中で話しているように、まだまだやりたいけどやれてないことや課題などがあります。

入社してみて気づいたのですがカンムは情報の透明性が高いです。事業状況は事業計画が毎月アップデートされていたり、今後の事業方針について役員・ディレクター陣からの説明も随時行われます。

これまでに書いたようにまだまだ課題が山積みなので、事業状況にアクセスできる環境は、計画から先んじて必要な仕組みは何かを考えて次の打ち手を検討するときに非常にありがたいです。

この辺りの課題に向き合って楽しく議論しながら一緒に働いてくれるお仲間を募集しています。

team.kanmu.co.jp