アルパカ証券の裏側 - 開発体制
今回は、アルパカ証券のシステム開発における体制を述べます。
アルパカ証券の開発は要件定義、設計、実装、テスト設計、テスト実施という順に進んでいきます。この中で、全面的に採用しているgRPCが重要な役割を果たしています。
gRPCではproto定義がすべての元となります。そのため開発は以下の順番で行っています。
- 業務などからこういう機能が欲しいという要望が上がる。この後関係者で議論をし要件としてConfluenceにまとめる
- 要件定義を設計担当者が受け取ったあと設計時にgRPCのproto定義を作成します。この時点でどのようなリクエストとリプライであれば要求が満たされるかを議論する
- proto定義からバックエンドのJavaコードとフロントエンドのTypeScriptコードを生成する
- バックエンド側の実装で必要であればDB定義を変更し、コードを生成する
- バックエンドとフロントエンドの両方の実装が完了し、テストが通った後に、proto定義も含めてマージする
特に2のproto定義を重視しています。gRPCではproto定義がすべての元となります。この時点でメッセージの内容を実装にまで踏み込んで議論します。この議論でメソッドやフィールド名などの命名や内容、型などを決めていきます。また、実装をしてみてやはりこのproto定義では要望を満たせない、ということが分かることもありますので、マージの前に適宜proto定義を更新します。
開発の工程管理には、現在Jiraを利用しています。開発の優先順位付けや進捗管理はConfluenceとJiraの連携でダッシュボードを作成しており、いろいろな部署がいまどこまで自分たちの依頼した開発が進んでいるかをひと目で分かるようになっています。
理想的にはバックエンドとフロントエンド両方の開発が完了した時点でマージしてテスト環境にデプロイします。しかし、タイミングによってはずれることもあります。その場合でも、なるべく影響が出ないように、メソッドのガワだけ実装したりします。このあたりはスケジュールと合わせて柔軟に変えています。
GitHub中心のフロー
アルパカ証券の開発はすべてGitHub上で行っています。前回述べた主なリポジトリは以下の4つです。
- proto定義用
- バックエンド用
- フロントエンド用
- インフラ用
proto定義は誰でも見られ、コメントできます。Pull Request(以下PR)上で「Confluenceのこの要件を実現するにはAPIが足りないのではないか」「このパラメータがリクエストに足りないのではないか」などの議論します。このレポジトリが開発の中心となっています。時にはslackへのリンクが貼られたりなど、議論の中心となるレポジトリです。
バックエンド用はバックエンドエンジニアのみ、フロントエンド用はフロントエンジニアのみがアクセスできるようになっています。proto定義で分割しているためお互いの実装の詳細を知る必要がない、という点はありますが、主にセキュリティ上の要件でこれらを分けています。つまり、仮に漏洩した場合でも対象となるレポジトリを最小限に抑える目的です。
インフラ用のレポジトリはSREおよびインフラエンジニアのみがアクセスできます。この中にはk8sクラスタの定義とterraformの定義が格納されています。PRがマージされるとArgoCDなどで自動的にデプロイされますが、これについてはまた後日紹介します。
ブランチ戦略
ブランチ戦略は git flowに従っています。
- featureブランチ: 各機能を開発する。必ずJiraのチケットと紐づくように作成する
- developブランチ: PRをマージする。マージすると自動的にテスト環境にデプロイされる
- releaseブランチ: 最終的な動作確認などのリリースをする準備をする
- masterブランチ: master releaseを行います。masterにマージすると、自動的に本番環境にデプロイする用のイメージタグが付与される
- hotfixブランチ: masterブランチに緊急に修正を行なう場合に使用する
developとmasterへの直接pushはGitHubのBranch Protectionで禁止しています。
Pull Request 開発
開発者はJiraのチケットを見て自分に割り当てられているチケットを開発します。割り当てられていなかったら自分から取りに行きます。自分が作業するとなったらチケットをIn Progressに変えます。属人化を防ぐため、できるだけチケットまたはチケットに紐づくConfluenceはだれでも作業ができるように記載することを心がけています。
featureブランチで開発し、少しでもコミットをしたらGitHubにPushし、DraftとしてPRを作成します。これはローカルPCが動作しなくなりコードを紛失するなどを防ぐためになるべくGitHubで保存しておきたいためと、GitHub上でも作業分担を見たいからです。GitHubとJiraは連携しているため、PRのタイトルにJiraチケット番号を書いておけば自動的にJira側からもGitHubを参照できるようになります。
Jiraに記載されている要件を満たす動作を実装し、テストが通るようになったらコードレビューを依頼します。コードレビューの対応が終わり、レビュアーがApprove(承認)を押したら、マージします。PRはapproveがないとマージできませんので、コードレビューは必須となっています。コードレビューのルールも文章化をすすめており、どのような観点でチェックが行われるかの均一化をすすめています。
Jiraチケット運用
Jiraは進捗管理に使用しているため、Jiraチケットの中に多くの情報を書かなくても良い、としています。 例えばバグ修正チケットなどは、slack上で議論をし、JiraチケットにはそのslackへのURLだけが記載してある、という状態もOKにしています。あるいは要件と紐付いているのであればConfluenceへのリンクが記載されていたりします。
これは、Jiraチケットを起票するのに時間をかけて結局起票されないよりも、起票のハードルを低くして漏れなく追跡していくためです。
コードレビューとマージ判断のルール
コードレビューではまずテストがしっかり書かれているかを見ます。元となるJiraチケットも参照しながら、要件を満たしているか、その要件をテストできているか、を見ます。その上で、変数名、複雑なコードを避ける、適切なコメントが書かれているか、などをレビューします。
過不足無く書かれたテストが通り、コード上の問題がなければマージします。マージをすると自動的に開発環境にデプロイされます。開発者はこの開発環境を自分のアカウントで触りながら動作確認を行います。
code formatter
コードレビューの際に、改行や空白の有無などコードの体裁で時間を取られることは可能な限り避けたいです。Javaコードでは Google Java Format を使用しています。 Google Java Formatterは設定項目がなく、また、Eclipse等からも使えるため便利です。コードレビューの際にはまずフォーマットされているかどうかをチェックします。最初にGoogle Java Formatをファイル保存時にかけるように設定してもらうため、 基本的にフォーマットされているはずですが、仮にフォーマットされていない場合は問答無用で修正してもらいます。commit時にフォーマットする方式もありますが、commit時の時間が増えるので現時点ではファイル保存時のみとなっています。
TypeScriptについては、linterとしてはeslint、formatterとしてはPrettierを使用しています。eslintでは、react/recommended
など基本的なrecommendedなルールを指定しています。
Architectural Decision Record (ADR)
Architectural Decision は、設計および実装上で議論した結果決めたことを整理して保存するものです。 ADRはGitHubレポジトリ内にあり、自由に見ることができます。また、議論が起きるたびに適宜追加していきます。
今まで書いたADRの例としては以下のものがあります。
- ログレベルの定義。どのような時にどのログレベルを使うべきか
- 各テストでデータの初期化を行なう箇所は基底クラスで持つべきか
- gRPCのAPIでCreate/PostのAPIのレスポンスはOK/NGのみ返すか、あるいは作成/更新した内容自体を返すべきか
レビューはこのADRも参照しつつ、ADRに沿っていないことであれば指摘します。もちろん状況は変わりますので、ADRがふさわしくなくなった場合は、更新するADRを新たに追加します。
このようにしてADRとして設計の理念や理由、そしてその途中の議論を残しておくことで、あとから入ってきた人が開発しやすくなります。
CIとテスト
CIとして CircleCI を使用しています。GitHub Actionsも試してみましたが、少なくともこのプロジェクトについてはCircleCIより実行時間が伸びてしまったので、CircleCIのままとしています。GitHubと連携し、PushされるたびにCircleCIが起動し、テストを実行します。テストが通らないとマージできません。
さらに、CircleCIでは、デプロイ用のDocker Imageのbuildも行っています。ここでbuildしたイメージが開発環境などの各環境で使われます。このデプロイフローについてはまた後日説明します。
1回のCIにかかる時間を短く保つことが開発環境での動作確認およびデプロイにかかる時間を減らせます。とはいえ、なかなか減らせずに結構な時間がかかってしまっています。実際30分以上かかる場合があり、ここは課題と思っています。
QAチェック
PRがマージされるたびに更新される開発環境とは別に、staging環境を用意しています。staging環境では、開発者だけでなくQA担当や業務の人が日々動作確認をしています。この動作確認が済んではじめてJiraチケットが完了になります。そして、デプロイに予定されているすべてのチケットが完了にならないと本番デプロイできません。
動作確認では、事前にテスト設計をします。テスト設計に基づいてテストを実施し、スクリーンショットやAPI呼び出しログなどを証拠として保持しています。これを元にテストが通ったかどうかを判断します。
本来は自動でこのテストを行いたいのですが、事前に状態を整えるのが自動では難しく、まだ手動なところがほとんどです。この自動化が今後の課題です。現在、 Autify という自動テストサービスの評価を開始しています。Chromiumなどを利用したソースコードで記載する自動テストから大きく進化しており、リグレッションテストなどは大部分を自動化できそうなポテンシャルを感じています。
デプロイ
すべてのJiraチケットがQAチェックを通って完了になり、リリース承認が降りたらデプロイです。
まずインフラ用リポジトリ内にてPRを作成します。PRにはk8sのmanifestに対する変更が含まれています。あるいは、もしもAWSのリソースに対する変更があれば、Terraformの変更も含みます。
問題なければApproveをした上で、GitHub上でマージします。マージによってArgoCDが動作し、自動的に本番環境にデプロイされます。あとはデプロイが完了するまで待つだけです。
まとめ
今回はアルパカ証券の開発体制について紹介しました。Confluenceで要件を固めた後にJiraで進捗を管理し、gRPCを中心にして実装の詳細を落とし込んでいきます。
誰がどう要件を固めるのか、テスト設計をするのは誰かなど、まだ固まっていない、あるいは実際試してみると想定通りではなかった点が多々あります。常に改善を続けながらみなさまに新しい機能を提供していきたいと思っています。
おまけ
アルパカ証券では一緒に新しい証券システムを開発してくれる仲間を募集しています。ご興味がありましたら、こちらを御覧ください!