【連載 第2回】freeeカード Unlimited での非同期通信の設計と実装

金融チームでエンジニアをしているimamuraです。freeeカード Unlimited の開発の裏側について紹介する連載の第2回目になります。freeeカード Unlimited がどのようなサービスなのかは第1回目の記事*1で紹介していますので、そちらをご覧ください。また、そこで紹介したように freeeカード Unlimited ではマイクロサービスアーキテクチャを採用しており、バックエンドのサービス間は主に非同期で通信しています。

freeeカード Unlimited の構成図
freeeカード Unlimited の構成図

この記事では、サービス間の非同期通信をどのような観点で設計したのかを共有できればと思います。要約するとfreeeカード Unlimited では以下の対応を行いました。

  • メッセージが必ず送信できるようにOutbox Patternを利用して永続化する
  • メッセージの受信の失敗のために再送可能なメッセージにする
  • メッセージの重複を防ぐために受信元で重複排除用のテーブルに保存する
  • システム全体で処理順序が保たれないので、順序に依存しないよう設計する
  • メッセージのフォーマットを統一するためにProtocol Buffersを利用する

非同期通信の概要

非同期通信はプロデューサー(あるいはパブリッシャー、センダー)がメッセージを生成・送信し、コンシューマー(あるいはサブスクライバー、レシーバー)が受信します。そして、双方の仲介となりメッセージを配信するインフラストラクチャをここではメッセージブローカーと呼びます。メッセージブローカーはキューを提供しており、メッセージをバッファリングすることができます。また、複数のコンシューマーがメッセージブローカーを購読することで一対多の通信も実現できます。このため、プロデューサーはコンシューマーが応答可能でなくてもメッセージを送信することができ、どのサービスに送るべきかも関知せずに済みます。つまり、送信元は送信先に関する前提条件を減らすことができるのでサービス間を疎結合にすることができます。

非同期通信の概要
非同期通信の概要

しかし、同期的なサービス間の通信と違って、介在するコンポーネントが増えるので運用の複雑度が高くなります。またメッセージブローカーが起点となり、単一障害点になる危険性もあるので、可用性の高いものを利用する必要があります。freeeカード Unlimited ではメッセージブローカーとして、Amazon SNSとAmazon SQSを組み合わせています。他の候補としてはAmazon KinesisやAmazon MKS(Apache Kafka)がありましたが、当面の考えられる取引量と必要十分な機能を鑑みて選定しました。しかし、この記事では特定のメッセージブローカーではなく、非同期通信に関する一般的な課題にフォーカスしてお伝えできれば思います。

通信時の失敗を想定する

同期的な通信と違って、メッセージの送信側(プロデューサー)側と受信側(コンシューマー)の処理のタイミングは異なるので、それぞれの失敗のケースを想定する必要があります。

送信時の失敗

まず、プロデューサーがメッセージブローカーへの送信に失敗する場合を考えます。通常、送信するメッセージはアプリケーションでのデータの作成や変更を伴った結果、発生します。例えば、freeeカード Unlimited で決済が行われると画面に取引の内容が表示されます。その場合、決済サービスで残高を変更して、ユーザー向けのカード管理サービスに取引内容を含むメッセージを送信します。

しかし、以下の図のようにコミットしてからメッセージを送信し失敗した場合、メッセージを伴う操作はコミットされているにも関わらずメッセージは失われます。

メッセージの喪失
メッセージの喪失

Outbox Pattern

送信時のメッセージの喪失への対策としてOutbox Patternがあります。これは、メッセージを伴う処理とメッセージの永続化を同一トランザクションで行い、別のプロセスがプロデューサーとしてメッセージを送信する方法です。メッセージはアプリケーションと同じデータストアで管理され、メッセージを伴う処理とメッセージの永続化の原子性が保証されます。メッセージが保存されるテーブルをここではOutbox(送信トレイ)テーブルと呼びます。この送信用の別プロセスは、Outboxテーブルから未送信のメッセージを読み取り、送信に失敗してもリトライします。そして、送信に成功した場合は送信済みステータスにします。処理後にレコードを削除しないことで、受信側で再送が必要になった場合もステータスを戻すだけで済みます。

Outbox Pattern
Outbox Pattern

送信側で保存されたメッセージを読み取って送信するには、アプリケーション側でポーリングするか、トランザクションログを読み取るCDC(Change Data Capture)を利用するなどの方法があります。CDCはOutboxテーブルにコミットした際にトランザクションログを検知し、Outboxのメッセージを取得しメッセージブローカーに送信します。Outboxテーブルをポーリングする方法は無駄なクエリが発行される一方で、CDCは設定用の実装が必要になる場合があります。サービスのリリース時はシンプルな設計から始めたかったので現在はポーリングする方法で送信しています。

受信時の失敗

次にコンシューマー側が、メッセージブローカーからの受信に失敗する場合を考えます。以下の場合が考えられます。

  1. そもそもメッセージが受け取れない
  2. 受け取ったメッセージを処理するとエラーになる

1は、クラウドサービスの障害やコンシューマーの負荷などで、受信とその処理ができない場合です。しばらくの間、メッセージブローカーでメッセージがバッファリングされているので、リトライで対応できます。例えば、Amazon SNSはメッセージの保持期間は最大14日*2になります。しかし、2はメッセージのフォーマットが不正だったりアプリケーション側の根本的なバグの場合です。これはリトライするだけでは解決できません。そのため、修正後にメッセージを再送する必要があります。

そして、freeeカード Unlimited ではプロデューサー側で送信メッセージをOutboxテーブルで永続化しているので、そのメッセージを未送信のステータスに戻すことで再送することができます。つまり、送信元のサービスとOutboxテーブルのIDが分かればメッセージを特定し再送することができます。そのため、Amazon SNSのメッセージのメタデータ*3に以下のようなサービス名とOutboxテーブルのIDを必ず付与するようにしています。そして、処理に失敗しリトライ上限を超えた時にその内容を通知し、処理に失敗したメッセージの保存用のキュー(Amazon SQSのデッドレターキュー*4)に移します。この再送の仕組みは自動化できる余地があるものの、実際にサービスを運用しながら発生頻度や原因に応じて対応していくつもりです。

メッセージの再送
メッセージの再送

通信するメッセージを設計する

重複排除

仮にメッセージブローカーがメッセージごとにIDを割り当て重複排除をサポートしていたとしても、必ずしも一回だけメッセージが送られること(exactly-once)を保証できるとは限りません。受信後にコンシューマーやネットワーク、メッセージブローカーの障害で、受信確認が受け取られずに再度メッセージが取得できてしまう場合もあります。例えば、Amazon SQSはメッセージを受信しても自動的にキューから削除しません。処理が終わり次第、削除のAPIをコンシューマーが呼び出す必要があります。しかし、それに失敗すると他のコンシューマーが再度取得してしまいます。他のメッセージ配信サービスでも、ほとんどがexactly once(必ず1回の配信)ではなく、at least once(少なくとも1回の配信)を保証しています。

また、コンシューマーの失敗時に再送する場合のように、同じメッセージが複数回配信されることを許容する必要があります。そのため、コンシューマー側では以下のいずれかの対応が必要になります。

  1. 冪等な処理にする
  2. メッセージの重複を確認し、2回目以降の処理を行わない

1の冪等とは、1回目の実行結果と2回目以降の実行の結果が変わらない性質のことです。例えば、利用停止中のカードの停止は冪等な操作になります。しかし、冪等にできない操作もあります。例えば、カード利用時にユーザーへ通知する操作などは、複数回通知されることになります。したがって、そのようなメッセージハンドラは重複したメッセージを検出し破棄する必要があります。2のメッセージの重複確認はコンシューマー側で処理済みのメッセージを永続化しておき実行時に確認するなどの手段があります。

freeeカード Unlimited では、必ず同一のトランザクションでメッセージを処理し受信したメッセージを重複排除用のテーブルに保存しています。このテーブルには、再送の場合と同じように送信元のサービス名とOutboxテーブルのIDをユニーク制約としています。これによって、同じメッセージを処理してコミットするとユニーク制約によってロールバックされるようになります。もちろん、外部のサービスへのネットワーク通信などロールバックできない処理には注意しなければなりません。

メッセージの重複排除
メッセージの重複排除

順序に依存しない

メッセージとそのメッセージハンドラを設計する際に、メッセージの処理の順序に依存しないようにする必要があります。確かに、メッセージブローカーが提供するキューによってはFIFO(先入れ先出し)のキューを提供しており、メッセージの受信の順番が送信の順番と一致するように保証されます。しかし、メッセージキューの順序が保証されたとしても、システム全体で保証されるわけではありません。

例えば、それぞれのメッセージが複数のスレッドから送信される場合は、順序を保つために互いに実行順序を制御しなければなりません。また、受信側も同様にメッセージを受け取ってからDBにコミットする順番を制御する必要が生じます。

先に送られたメッセージが後にコミットされる
先に送られたメッセージが後にコミットされる

順序を意識する必要がある場合は、メッセージ自体に順序を示す情報(タイムスタンプやシーケンス番号など)を持たせ、コンシューマー側で調整することも可能です。freeeカード Unlimited でもマイクロサービスの境界を決める際に、全てのメッセージを洗い出して順序に依存しない非同期通信が可能かを確認しながら設計しました。

フォーマットの統一

同期・非同期に関わらずサービス間で共通のインターフェースを使って通信する必要があります。同期通信の場合と同様に非同期通信でも、Protocol Buffers(protobuf)を使ってメッセージのフォーマットを共通化しています。前回の記事*5で紹介したようにfreeeカード Unlimitedではモノレポを採用しており、protobufを同じリポジトリ内で管理しています。

例えばサービスAでカードが利用され、その明細をユーザーが確認できるように以下のようなメッセージでサービスBに連携します。

message MessageA {
  int64 company_id = 1;
  google.protobuf.Timestamp date = 2;
  string merchant_name = 3;
  uint32 amount = 4;
}

サービスAで以下のようにprotobufが生成した構造体をJSONにして送信します。

import (
    ...
    "google.golang.org/protobuf/encoding/protojson"
)

m := &MessageA{...} // protobufから生成されたメッセージの構造体
data, err := protojson.Marshal(m) // proto.MessageをJSON形式の[]byteに変換

そして、サービスBで以下のようにメッセージをJSONからデシリアライズします。

import (
    ...
    "google.golang.org/protobuf/encoding/protojson"
)


m := &MessageA{}
err := protojson.Unmarshal(data, m) // []byteのメッセージを指定のproto.Messageに変換

このようにサービス間で通信する場合は、メッセージのフォーマットの共有方法やシリアライズ・デシリアライズのプロトコルを決める必要があります。

まとめ

非同期通信はマイクロサービス間を疎結合にする有効な手段です。一方で、関係するコンポーネントが増え通信の失敗の箇所が増えます。それらを想定して対策したりリカバリー可能にする必要があります。freeeカード Unlimited の開発もここでは全て言及できませんでしたが、並行処理でどのようにコンシューマーを実装するか、どのようなアラートの設定にすべきかなど試行錯誤する機会はたくさんありました。そして、リリース後の通信量や要件の変更に応じて改善をしていかなければなりません。しかし、アーキテクチャを考える上でサービス間の通信は避けて通れない論点なので非常にチャレンジングなプロジェクトだと思っています。

次の連載記事は、freeeカード Unlimited の開発開始時に金融チームに異動したtabachainさんの「EMから再度エンジニアに戻り新規プロダクト開発に挑戦して学んだこと」になります!


金融チームでは、一緒に「freeeカード Unlimited」を開発する仲間を募集しています。 ベンチャー企業であるfreeeの中でも更にスタートアップ色が強い金融チームで、スモールビジネスの資金繰りにイノベーションを起こしましょう! https://freeecommunity.force.com/jobs/s/detail/a4l2r000000CaUpAAK

基盤サービスにおけるユーザーストーリ作成のポイント

ある程度事業規模が大きくなると、新規サービスを立ち上げたりマイクロサービスによるプロダクトの分割を行い、各サービスで共通に利用する機能を切り出したサービス、基盤サービスを立ち上げるタイミングがいつか来ることがあるかと思います。基盤サービスはCynefin Framework(クネビンフレームワーク)における複雑系と呼ばれるタイプの課題を取り扱う場合が多いですが、今回の記事はそういったエンドユーザーからは少し遠い基盤サービスを立ち上げ、どのように課題を設定し、MVPを定めていくかというポイントについて書いていきたいと思います。

こんにちは。ichyことTakeru Ichiiです。以前「私のスクラム開発。もしくはペペロンチーノの作り方。」という記事を書かせて頂き、おかげさまでペペロンチーノのつくれぽをTwitterで拝見したりしてニヤニヤしておりました。ペペロンチーノはシンプルですが奥が深く、インターネット上では様々な流派があるので、ぜひ皆様にはいろんなペペロンチーノを試して、自分の思い描く素晴らしいペペロンチーノを編み出していただき、つくれぽお待ちしております。さて、前回ペペロンチーノだったので今回はボロネーゼの作り方を、と思ったのですが、Blogの担当者から「料理のレシピはいらない」との言伝を頂いておりますので、私のもう一つの趣味である鉄道から大崎駅りんかい線の配置に関する歴史を紐解こうと思います。と思ったのですが、これも担当者からだめとのことなので不承不承ではありますが、お仕事に関連するお話をしようかと思います。

基盤サービスの課題の特徴

皆さん、アジャイル楽しんでいますか?かくいう私はなかなか苦しんでいます。実は私はfreeeでは珍しく専任スクラムマスターという立場で、いくつかのチームのスクラム開発をサポートさせていただいているのですが、その一つにある基盤を支えるためのチームがあります。このチームはごく最近作られ、チーム人数はまだ少ない状態でfreeeのサービスを支えるべく新たな基盤サービスを立ち上げているところです。基盤とは、分かり易い例でいえば認証認可などの「様々なサービス固有で持つものではなく共通で使われるサービスとして運用されるもの」を指しており、このような責務を持つサービスをfreeeでは基盤サービスと呼称しています。

さて、新しくチームを立ち上げるときに必要なのはもちろんそのチームが行う仕事です。大体の新しいチームというのは何か課題感を持って立ち上がることが多いと思いますが(なんとなくチーム作って何作ろうかというのも稀にありますね)、課題感からすぐに何をすればいいかと言うのをすぐに決められる課題と、決められない課題というのがあります。例えば、ユーザーが直面している問題とその解消方法がわかっていて、解消方法を実現するために何かをつくるというわかりやすい状態が前者ですが、ユーザが直面している問題はわかるものの解消方法がわかっておらず、解消方法を定めるために様々な調査から始める状態が後者です。この後者の問題はMore Effective Agileでも紹介されているCynefin Framework(クネビンフレームワーク)でいえば「複雑系」と言われる問題です。この問題は

  • 問題は把握されているが、理解されていない
  • 解決に至るための質問すべき内容が全てわかっているわけではない。解決策を得るためには実験が必要。失敗は織り込み済み。不完全なデータに基づいて決定をくださなければならない場合が多い。

が特徴の問題です。More Effective Agileではこの複雑系の問題解決にはOODA(ウーダ)ループを使うと良いというアドバイスがあります。このループは

  • Observe(観察)
    • 現在の状況を観察し、関連する外部情報も観察する。
    • その上で明らかになる状況の側面を観察し、環境とどのように関わり合うのか観察する。
  • Orient(方向づけ)
    • 観察の結果を自分が置かれている状況と関連付ける
    • 観察した情報が自分たちにとって何を意味するかを判断し、利用可能な選択肢を特定する。
    • 様々な先入観を取り除く機会を提供する。
    • 理解を深めて優先順位の変更も実施する。
  • Decide(意思決定)
    • 方向づけによって特定された選択肢にもとづいて決定をする。
  • Act(行動)
    • 意思決定にもとづいて行動する。
    • 行動した結果や影響をもって再び観察から始める

を実施するループで、PDCAのような性質も持っています。特徴は戻ったり省略も可能で、

  • 方向づけから観察にもどれる。
  • 意思決定から行動を伴わない場合は、観察にスキップする。
  • 観察や方向づけにおいてが既知のパターンが発生した場合は直接行動することができる。

といったことも可能です。このOODAループはまさにアジャイル開発の方法のベースに近い性質を持っており、スクラムもこのループに当てはめることがだいたいできると思います。

さて、前提が長くなってきましたが、私が先程書いた新しい基盤サービスをたち上げるチームはまさにこの複雑系の特徴を持った課題に立ち向かうことになります。つまり、問題は把握しているが、解決方法がわからないのです。そこでこの基盤チームにもアジャイル開発を導入することにしました。まず我々は新しい基盤を作る上で、新しい基盤を使うサービスの調査を行いました。これはOODAループのObserve(観察)に当たる状態ですね。我々が担当する基盤サービスというのはエンドユーザーが直接触るUIはほぼなく、多くはエンドユーザーに対して間接的に影響があるものになります。このようなサービスの場合、エンドユーザーがどのように使うかというのはそれほど重要でない場合が多いのですが、今回の場合はエンドユーザーのUXに影響を与えることが考えられたため、まずはエンドユーザーを起点としたユーザーストーリーを集めることにしました。

ユーザーストーリーを作る

さて、ユーザーストーリーに関しては知っている方もいるかと思いますが、改めて説明していこうと思います。ユーザーストーリーとはシステムを使用する人の視点に立って説明される機能または能力の説明のことを言います。代表的なフォーマットとしては

  • ユーザーの種類
    • 誰々 として
  • 利益
    • 何々 をできるために
    • 何々 するために
  • 目標・要求
    • 何々 したい
    • 何々 を望んでいる

のように書かれます。例えば

freeeを利用したことのないユーザー として freeeを利用 するために サインアップ したい」

という感じです。

そこで、我々はまずある一つのサービスに絞ってそのサービスのPjMやPdM、エンジニアにインタビューやヒアリングを行い、我々が作ろうとしている基盤サービスとその周辺のユーザーストーリーを集めました(記事の最後に付録でユーザーストーリーの要点をまとめてあります)。多くのストーリーを集め、我々が解決したい課題が徐々に浮き上がって来たので、早速それを解決するための実装に取り掛かりたいところですが、焦ってはいけません。その解決しようとしている課題は本当に解決しなければいけない課題なのでしょうか。そしてその課題を解決するための実装する内容は本当に正しい解決案なのでしょうか。

顧客が本当に必要だったもの、MVPの考え方

「顧客が本当に必要だったもの」という言葉をご存知でしょうか。ニコニコ大百科にも記事があるぐらいには有名で、SNSとかでもへんなブランコの画像はちょいちょい見かけます。端的に言えば、「顧客は自分が欲しい物を満足に説明することはできないし、それを提供する側もわからない」というものです。受託開発をベースにしたかのような画像ですが、我々事業会社も例外ではありません。エンドユーザーがこれに困っている、だからこうしてほしいといったものは本当にエンドユーザーのほしいものを表しているとは限らないです。そこで我々はOODAループを回す必要があります。エンドユーザーが本当に欲しい物を提供するために、一度サービスを提供して使って頂き、再び観察する必要があります。しかし、一度に大きな単位でその機能を提供するのはとても危険です。大きな単位での開発を行ってしまうともし我々が間違った方向に進んでいる場合にその修正も大きくなり、場合によっては修正が不可能な状況にも陥ることになります。このリスクをなるべく小さくするために、小さい単位の開発を実施するアプローチを取ることになります。

MVP(Minimum Viable Product)と呼ばれるリーン開発ではおなじみの手法があります。「最小限の利用可能な商品」と直訳できるこの手法は、いかに失敗のダメージをへらすかという観点のマネジメント手法です。The Minimum Viable Product and Incremental Software Development | Effective Software Designという記事の絵はとてもわかりやすいMVPの例です。この記事ではピラミッドを作る方法には2つあり、それぞれ段階的なプロセスですが、以下の特徴があります。

  • 土台からピラミッド組み上げていく方法
    • 土台から徐々に組み上げていき、最後のステップでピラミッドが完成する。
  • 小さなピラミッドを組み上げそれを徐々に大きくしていく方法
    • 一番最初に小さなピラミッドが完成し、各ステップでは徐々に大きいピラミッドが完成する。

先程の話をこのピラミッドで例えてみましょう。顧客(王?)がどのようなピラミッドを要求し、満足するかは顧客本人がフィードバックを行う必要があります。事前に数値的に満たさなければいけない仕様があるのであればそれを満たすピラミッドを作る必要がありますが、それ以外の要件は実際に体験してみないとわからないものです。土台からピラミッドを作り、完成したところで、それが大きく顧客の期待からずれているものを作るより小さな必要最低限の仕様を満たしたピラミッドをつくり、そこから付け足すほうが遥かに失敗のダメージが減ることは直感的にも納得が行くところかと思います。これがMVPの目指すところです。

基盤の視点でユーザーストーリーを作り直し、MVPを定める

さて、話はもどり、我々は基盤に関連するエンドユーザーのユーザーストーリーを集めることができたので、このユーザーストーリーからMVPで実装するコアの価値を抜き出し、基盤サービスが最初に取り掛かるファーストリリースの範囲を決定することになります。ユーザーストーリーを分析し、コアの価値を定め、作らなければいけないものを決め、ついに我々エンジニアが自らの技術力を持ってやっと作る作業に入れる!と思われるでしょうが、ちょっと落ち着きましょう。基盤サービスで作らなければいけないものを決めました。でもその作らなければいけないものは、本当に全部作らなければいけないのでしょうか。そもそもその作らなければいけないものは本当にすべてが作らなければいけないものなのでしょうか。

混乱するような表現になってしまいましたが、今回のように先にエンドユーザーのユーザーストーリーを作り、MVPを抜き出してしまうとこれを実現するための機能を網羅した基盤サービスを作らなければならなくなってしまうという気持ちになってしまいます。先程MVPの話をしましたが、これはエンドユーザーだけに当てはまる話ではなく、基盤サービスにも当てはまる話です。これは、基盤サービスを利用する各種プロダクトが基盤サービスにもとめているものが実はまだわかっていないという前提で考える必要があるからです。基盤とはいえ、基盤に要求する要件はfreeeサービスを開発している開発者自身も正しくは把握できていない場合が多いです。

なので、基盤サービスでやらならければならないのは本当にこの時点ですべて作る必要があるのか、この機能は本当に必要なのかというのを再度確認する必要があります。つまり、先程のユーザーストーリーはエンドユーザーを起点としてfreeeのサービスが実現するストーリーを考えましたが、今度は、エンドユーザーが使うfreeeサービスを起点として基盤サービスが実現するストーリーを同じように考える必要があります。例えば以下のようなユーザーストーリーです。

個別のfreeeサービス として ユーザーが存在するかどうか検査 するために ユーザーのメールアドレスからユーザー情報を取得 したい」

そして個別のfreeeサービスを起点として基盤に対するユーザーストーリーを考えたらそのMVPを更に掘り下げる必要があります。今回の我々の例の一部と取ると、その基盤サービスを使うためには各freeeのサービスそれぞれが何かしらのマスタデータを必要としているという仕様があり、次のようなユーザーストーリーが導かれたとしましょう。

個別のfreeeサービスの開発者 として 基盤サービスの利用を するために 事前に個別のfreeeサービスの初期情報を登録 したい」

このユーザーストーリーを実現するために我々基盤サービスはそのマスタデータを入力するためのAPIが必要であると考えます。今回の基盤サービスはfreeeのほぼすべてのサービスに導入されていくことになるので、この初期情報を入れるAPIを作っておけば運用チームの手を借りることなく利用を開始できるようになるとても便利なAPIになります。しかし考えてみましょう。その機能はコアの価値でしょうか。基盤サービスを利用する前提の作業は厳密に言えばコアの価値とは違います。しかも、この機能は最初の一回だけやればいいのであれば、一旦は運用チームで引き取って行うという方法も取れます。このユーザーストーリーは一旦MVPから外し、次回以降の開発で実施したほうが良いでしょう。これよりも、我々が提供する基盤サービスがコアの価値を発揮できるか検証できる開発に注力したほうが効果的です。

まとめ

さて、今回はエンドユーザーのユーザーストーリーから基盤サービスにおけるユーザーストーリー作成のポイント、そしてMVPに焦点をあてていきました。エンドユーザーを起点としたユーザーストーリーから基盤サービスの要件を作る際は、エンドユーザーのMVPと同様に、基盤を使うサービスを起点として基盤サービスのユーザーストーリーを作り、MVPを探っていくというところが今回のポイントでした。ユーザーストーリーやMVPといった概念はリーン開発やアジャイル開発では割と知られていることではありますが、実践すると意外に難しかったりすることでもあります。特にMVPや小さくリリースするといった考え方は(自分を含めた)エンジニアは誤解をしている部分が多かったりします。もし過去上手くいかなかったなーと思ったら、ぜひこの記事を読んでもう一度Tryしてみてはいかがでしょうか!自らOODAループを回して成長していきましょう!

付録: ユーザーストーリーの要点

付録で私がユーザーストーリーのチケットを作るときの要点をまとめたリストを皆様に共有しようかと思います。ぜひご活用ください。また、うちはこういうところに気をつけてるよ!みたいなTipsがありましたらぜひTwitterでこの記事のURLとともに教えて下さい!

  • タイトル(ユーザーストーリー)は以下の要素
    • (必要がアレば)前提
      • 前提として<誰々>は<何々>をしており…、
    • ユーザーの種類
      • <誰々>として
    • 利益
      • <何々>をできるために
      • <何々>するために
    • 目標・要求
      • <何々>したい
      • <何々>を望んでいる
  • 当該ストーリーがさらに分解できないか検討する
    • 1スプリントで終わるかどうかを基準にしてみてもいいかも
  • ストーリーの詳細を明確にする
    • 要求が正確に記載できてるか、タイトルの用語にブレがないかをチェックする
    • INVESTガイドラインにそってるか?
      • independent(独立している)※ゆるい条件
        • 特定の順番で実装する必要がない。
      • Negotiable(交渉可能)
        • 全ての詳細を記述しないで、開発者とビジネス側で交渉可能な状態
        • 詳細化は後の受け入れ基準でおこなう
      • Valuable(価値がある)
        • ビジネス的に定量化できる価値
      • Estimable(見積もり可能)
        • 開発者が見積もりできる程度の具体性
      • Small(小さい)
        • 1,2人の開発者が一回のイテレーション(スプリント)で実装できる大きさ
      • Testable(テスト可能)
        • ビシネス側がストーリ完成のテスト可能か
  • ストーリーの受け入れ基準を定義する
    • ここでタスクとして何が必要かを明らかにする
  • ストーリーを見積もる
    • 見積もりできないときはDoR(準備完了の定義:後述)でスパイクを検討する
      • スパイクとはストーリーを見積もるために必要なメタストーリー
      • ライブラリの仕組みがわからないなどの場合の調査などが当たる
      • 書き方的には「<何々>のストーリーを見積もる」というタイトルで表される。
  • 準備完了の定義(DoR)を明確にする
    • 準備が整う前にチームが要求の開発作業に進めないようにする

参考文献

古事記輪読会をやってみた

こんにちは、livaです。普段はPSIRTでセキュリティエンジニアをやっています。アドベントカレンダーでは毎年記事を書いているので私が日頃何してるかはそちらを読んでいただければと思います。

developers.freee.co.jp

developers.freee.co.jp


さて、今回は輪読会の話です。現在毎週金曜日夜に古事記輪読会を主催しています。「古事記?技術書の古典のことかな?」って思った方、不正解。歴史書の古事記です。

ja.wikipedia.org

「技術書でもない本を輪読する?なぜ?」という疑問は当然だと思います。そのあたりは経緯も含めて話していきたいと思います。

なお、今回技術的な話は一切出てきませんのでご了承ください。


目次


なぜ始まったか

普段は技術書の輪読会を主催していますが、どうカジュアルに緩く進めても「仕事っぽくなってしまう」ことを課題に感じていました。そこで「本業からズレたものを社内輪読会のネタにやってみてもいいんじゃないか?」と思いつきました。主催の完全なる趣味に走り、それに興味を持った参加者と一緒にわいわいやることで仕事感はなくなり緩いコミュニケーションも取れるだろうと。昨今チームをまたいだコミュニケーションも減りつつあるのでこれは1つの手段として使えるかもしれないと感じました。

なぜ古事記を選んだか

「みんなで読むと解釈違いが出てきておもしろそう」と感じたことです。古事記は文字だけではなく状況を思い浮かべて読まないとわけがわからない部分も出てくるので個人の想像力によって補完しています。この想像力は個人間で解釈違いが出てくると考えました。

古事記を読む最初のきっかけとなったのは地学でした。私は火山学が好きで普段からも火山に関することを色々と調べています。天文学、気象学と並んでとても魅力的な学問だと思っています。

その火山学をベースに置いた小説「死都日本(石黒耀著)」があるのですが、それを読んでいたとき古事記に関する記載がありました。この本では「天孫降臨での記載は実は火山の噴火による破壊と再生の話なのではないか?」という解釈がとても興味深く、「自分でも読んでみよう」と1人で読み進めていました。その話をSlackにある自分の分報チャンネルに貼ったところ「輪読会やってみたい」との声があったので始めてみました。

「古事記輪読会やりたい」と声が上がった瞬間
「古事記輪読会やりたい」と声が上がった瞬間
ちなみに余談ですが、「死都日本」では天孫降臨以外でもしばしば古事記が言及されています。興味のある方はぜひ読んでみてください。物語としてもシンプルに面白いです。

使用している本

使用している本は竹田恒泰著の「現代語訳古事記」です。

www.amazon.co.jp

選んだ理由は「Amazonで眺めていたら読みやすそうだったから」という至極単純な理由です。

非常に読みやすく、適度に解説も入っているので良書だと思います。末尾には付録として神武天皇に至る系図も載っています。

輪読会の進め方

あまり凝り固まったことはしていません。輪読会としての体裁を保つ用にメモを1つ用意しておき、その日の担当者が事前に読んだ内容を書いておきます。書けなかった場合はみんなで読みながら書いていきます。

「事前にここまで読んでおく」といったことはせず、集まったその日の進みによって臨機応変に対応しています。この社内輪読会の特徴的なところは「酒が必須」なところです。参加者は「御神酒」と呼称してます。日本神話を題材にしている本を読むので御神酒、単純ですね。輪読会で進めているうちに酒が進んで頭が回らなくなってくるので「このくらいでいいか」となったところでその日の輪読会は終了します。あとは打ち上げと称して好きなだけ酒を飲みます。宴会ついでに輪読会やってるような感じになってます。

この輪読会はメイン担当以外は参加者を絞らず、meetのURLも分報チャンネルに貼り付けて出入り自由にしています。最近はラジオ代わりに聞いてくれる同僚もちらほらと出てきました。「意外と真面目にやってる」と驚かれたときは笑いました。

読んでみての学び

進捗は上つ巻が終わったところです。日本は「万物のものには神が宿る=八百万の神」と言うことが多いですが、そのことを物語るようにどこにでも神が宿っているという描写がそこかしこに出てきます。

神生みや国生みは日本という土地がどう生まれていったかというのが書かれています。とても興味深いですね。このあたりは火山学大好き人間にとっては「どこが噴火してどういった被害が出たのか」なんてことを考える余地があるので非常に面白いです。

国譲りが終わり天孫降臨が終わった頃、神武天皇の東征が始まる頃になると行く先々の有力者や豪族との争いがぼかしながらも書かれていて、大和王権の成立までの過程がわかります。ここを読み進めていたときの参加者の合言葉は「兄弟が出てきたら兄は死ぬ。兄ちゃんかわいそう。」ということでした。「弟より優れた兄は存在しない」と言わんばかりに兄とされた存在は消えていきます。自分は弟がいて兄にあたるのでこの描写を見ると心が痛いです。

神々の行動の無茶苦茶さも目を引きます。詳しくは書きませんが、これは現代価値観で論じてはいけない部分ですね。「あのときはそうだった」と思うことが必要になります。国によっては即禁書レベルじゃなかろうか。「物語として読み、そこに現代の価値観を持ち込まない」というのが古事記(に限らず歴史書全般)を読む上で必須の事になってくると思います。

参考にしているサイト

本だけでは読み解けない部分は國學院大學古事記学センター を参照しています。國學院大學が本腰を入れてまとめただけあって情報のまとまり度合いが素晴らしく、また原文と現代訳の比較もでき、何かと参考にしています。

実際の雰囲気

メモのスクリーンショットから雰囲気を感じ取ってください。

「ことあまつかみ」と呼ばれる概念上の神が生まれた
すべての始まり

伊邪那岐、伊邪那美による神生みの章を読んだときの様子です。
神生みの章

最後に

「主催の趣味で始めた輪読会が果たして盛り上がるのか?」という疑問はあったのですが、回を重ねるごとに盛り上がってます。担当者は古事記の全体像を全く知らないため、読んで推測し、一般的な解釈を調べ、それを話し合って、結果をメモに残していくため、とても学びの多い時間になっていて、本業以外の勉強をするというのもたまにはいいと感じています。

古事記には関連図書も複数あり、終わった後の続編もすでに計画されています。出雲風土記が今の所の候補です。古事記と同様に最古の歴史書である日本書紀になるかもしれません。

おまけ

開発時のPull Requestレビューのコメントにも古事記に触発された文言が書かれるようになりました。

古事記に触発されたLGTMコメント
古事記に触発されたLGTMコメント

社内Slackには神も登録されています。

タケミカヅチ絵文字
建御雷神

【連載 第1回】freeeカード Unlimited の開発の道のり

金融チームでエンジニアをしているimamuraです。freeeカード Unlimited のβ版の提供が今年(2021年)の秋から開始されます。開発自体は半年以上かかっており、そこでの開発の裏側について連載を行います!

連載は以下のようになります。 ※ 日程、タイトルは一部変更になる可能性があります

日程 タイトル 執筆者
9/9 freeeカード Unlimited の開発の道のり imamura
9/16 freeeカード Unlimited での非同期通信の設計と実装 imamura
9/23 EMから再度エンジニアに戻り新規プロダクト開発に挑戦して学んだこと tabachain
9/30 freeeカード Unlimitedでのクリーンアーキテクチャ実践 id:lvmingbei
10/7 新卒一年目からの新規プロダクト開発 sekky
10/14 新規プロダクト&新造チーム&フルリモート:EMに何ができるか? yokota
10/21 カード事業のインフラ作ってみた id:renjikari
10/28 QAがスクラムチームの一員として取り組んでること yumotsuyo

そして、第一回のこの記事では freeeカード Unlimited がどういったアーキテクチャになっており、どのような技術を利用しているのか開発の全体像を共有していきたいと思います。

freeeカード Unlimited とは

freeeカード Unlimited
freeeカード Unlimited

freeeカード Unlimited とは、freeeが提供する法人向けのコーポレートカードです。freee会計に銀行口座を同期している法人であれば、freee独自の与信モデルにより、創業期の法人であっても高い限度額でカードを利用することができます。それによって、代表者の個人信用情報によらず広告費やサーバー代など高額になる支払も可能になります。また、カード決済後すぐに通知され、freee会計に明細をすばやく同期することができるため、タイムリーな月次決算を行うことができます。

現在は、β版のリリースに向けて開発中で、社内で実際に利用(ドッグフーディング)しながら改善を行っています。

サービスのアーキテクチャ

freeeカード Unlimited は以下のようなマイクロサービス群で構成されています。

サービスの構成図
サービスの構成図

Backend For Frontend (BFF)が、クライアント用のAPIを提供し、背後にあるサービス群と通信します。それらは以下のような役割を担っています。

  • カード管理: ユーザーのカードの管理(申込、発行、停止など)をする
  • 決済: カード利用時に残高を確認し決済可能か応答する
  • 請求: カードの利用額の請求処理をする
  • 与信: カードの毎月の利用限度額を決める
  • 通知: 他サービスのイベントに応じた通知をする

各サービスは、主に非同期で通信します。

マイクロサービスの採用

マイクロサービスとは、アプリケーションを個別にデプロイできる疎結合のサービスの集合として構成するアーキテクチャです。そして、今回マイクロサービスをアーキテクチャとして採用した理由は以下が挙げられます。

  • 個々のサービスを小さくして開発しやすくなる
  • 個別にデプロイやスケーリングができる
  • 変更や障害の影響を分離できる
  • データソースを分離できる

もちろん、マイクロサービスを採用しただけで、個々のサービスの開発のスピードが上がったり、障害の分離が実現されるわけではありません。なぜなら適切にサービスを分割しなければ、あるサービスの変更をするために他のサービスの実装を参照したり、変更を加えて一緒にデプロイしたりしなければならない状態に陥るからです。

freeeカード Unlimitedの開発では、アプリケーションが対象とするユーザーに纏わる活動や関心事を洗い出し、どういった概念が登場し、それらがどのように関係しているのか言語化することから始まりました。開発初期の1ヶ月以上の時間を使って、チームで議論しました。

具体的には、イベントストーミングという手法を使ってモデリングをしていくことから始めました。これは開発のメンバーだけでなく決済領域に詳しいPMにも参加してもらって、アプリケーションで発生する出来事を時系列で書き出す作業です。そうすることによって、登場する概念を確認し、それらがどんな出来事が起因して影響を与えられるのかを可視化することができます。その過程で、例外的なケースを質問したり、概念を包括できるまとまりを見つけ出すことができます。

イベントストーミング
イベントストーミングの様子

このイベントストーミングで整理した情報をもとに、サービスの分割案を作っていきました。そして、サービス間で必要となるAPIを整理してサービスの境界を見直していきました。まずはシンプルな設計から始めたかったのでサービスを跨いだトランザクション処理がないようにも設計しました。

サービス間通信

サービスの分割案ができて、それぞれAPIが決まったのであれば、どう通信するのかを決める必要があります。 まず、サービス間の通信には同期と非同期があります。それぞれ以下の特徴があります。

  • 同期
    • リクエスト先のサービスが応答可能である必要がある
    • 処理が終わってレスポンスが返ってくるまでブロッキングされる
    • すぐにリクエストが処理される
  • 非同期
    • メッセージがバッファリングされるため、リクエスト先のサービスが応答可能でなくて良い
    • メッセージ配信サービスが応答可能である必要がある
    • リクエストして次の処理に進める
    • リクエストが処理されるまで時間がかかる

サービス間の同期的な通信は、gRPCを使って実現しています。freeeカード Unlimited ではインターフェース定義語(IDL)である Protocol Buffers (protobuf) から生成されたコードを利用してRPCをしています。後述するように、モノレポでprotobufを管理することで、統一的にRPCのインターフェースを各サービスで共有することができます。

また、BFF以外のサービス間の通信は非同期のパブリッシュ・サブスクライブの通信が中心になっています。各サービスは発生したイベントに応じたメッセージをメッセージブローカー(サービス間のメッセージの仲介をする配信サービス)に送るだけで、それがどのサービスでどう扱われるか関知しません。そうすることによって、サービス間を疎結合にすることができます。そして、メッセージブローカーは Amazon SNS と Amazon SQS を利用しています。

モノレポの採用

freeeカード Unlimited ではモノレポ(Monorepo)を採用しています。つまり、各サービスの実装は一つのリポジトリで管理されています。モノレポを採用した背景としては、リリースするまでの間は少なくとも一つのチームが全てのサービスを並行で開発をする必要があったためです。単一のリポジトリにまとまっていることで、複数のサービスを開発する場合でもCloneは1回で済み、1つのPull Requestで複数のサービスの開発を進めることができます。そしてサービス全体のコードの可視性を高めることができます。

また、protobufを利用してサービス間のAPIを定義した場合、リポジトリがサービスごとに分かれていると、どこで管理し、どう共有するかが課題になります。freeeカード Unlimited ではモノレポで統一的にprotobufを管理し、1つのコマンドで各サービスのディレクトリ配下にコードを生成するようにしています。

モノレポでのprotobufの共有
モノレポでのprotobufの共有

技術スタックと開発プロセス

freeeカード Unlimited のBFF以外のサービスのバックエンドはGoを使って実装されています。採用した理由は、静的型付けやシンプルで理解しやすい言語設計だけでなく、社内でマイクロサービス向けの共通パッケージが用意されているからです。これは社内にいくつもマイクロサービスが存在し、微妙に構成が異なった実装や車輪の再発明を防ぐために提供されました。例えば、ロギングや、エラー通知、SQLの実行、リトライなどのパッケージが用意されています。これによって、ドメインロジックの実装に集中することができました。

また、フロントエンドの実装はTypeScriptとReactを使って実装しています。社内ではReactの共通UIコンポーネントが用意されており、それらを使うことでマークアップやスタイルシートで悩むことなくUIの実装ができるようになっています。

そして、全てのサービスはAmazon EKS上で稼働しています。また、他のプロジェクトと同様にスクラムスプリントごとに開発しており、機能が実装されるとすぐにデプロイしてQAしています。

これらの技術や取組に関しては後の連載で紹介していきます。

まとめ

freeeカード Unlimited はビジネスカードの管理や決済時の即時応答、請求、与信を担うため、高い可用性やデータの整合性が要求されます。そして、決済にまつわる複雑なドメイン知識を理解しながら、設計・実装しなければなりません。だからこそ、常に良い手法や技術を選定し改善していくことができるプロジェクトであると感じています。今回は freeeカード Unlimited の開発に関する全体像をお伝えすることができたかと思います。この後の連載では、より詳細にどのような開発が行われたのか共有していくつもりです。

次の連載は、再び自分から「freeeカード Unlimitedでの非同期通信の設計と実装」について紹介していきます!


金融チームでは、一緒に「freeeカード Unlimited」を開発する仲間を募集しています。 ベンチャー企業であるfreeeの中でも更にスタートアップ色が強い金融チームで、スモールビジネスの資金繰りにイノベーションを起こしましょう! https://freeecommunity.force.com/jobs/s/detail/a4l2r000000CaUpAAK