マジ価値DeepDiver第一号の1カ月間のチャレンジ(連載 第3回)

こんにちは、freee関西支社でエンジニアをやっていますliaoです。19新卒として入社して、freee人事労務、freeeプロジェクト管理の開発を経て、現在はSREチームに所属してインフラ周りのことをしています。先日の連載記事で巨匠制度の歴史初代・二代目巨匠が考えるエンジニアキャリアについて紹介されました。今回、巨匠制度から進化した若手・中堅エンジニア向け制度のマジ価値DeepDiverに第一号として応募して1ヶ月間チャレンジしてみました。8月に取り組みが終わったので、実際に取り組んだ内容と感想を共有したいと思います。

マジ価値DeepDiverとは

巨匠制度の歴史でも言及されていますが、マジ価値DeepDiverは、若手〜中堅向けの制度です。この制度を通して取り組む人が1ヶ月、圧倒的主体性をもって組織の課題を発見〜解決まで経験してもらい、自分の成長のブレークスルーポイントを創出してもらうことを狙いとしています。

取り組んだ内容

「Performance Deep Dive DB」というお題で1ヶ月間取り組みました。名前からは分かりづらいと思いますが、ローカルやテスト環境から気軽に繋げられるパフォーマンス検証DBの仕組みを作りました。

パフォーマンスの課題感

freeeは個人事業主から上場企業まで幅広く使えるサービスを提供していて、規模にかかわらずストレスなくサービスを使える状態を保たないといけません。しかし、大規模事業所になってくるとデータ量が多いため、特定のページや操作で重い場合があります。 こういった本番環境で起きたパフォーマンス問題を解消するのですが、ローカルで本番と同じ規模のデータを入れるのが難しく、再現が難航することがあります。もしローカルからすぐ繋げられる大規模DBがあればいいのになと思ったのがきっかけでした。

ゴール設定

freeeには「マジ価値」という価値基準がありますが、課題感を踏まえて、価値を最大化できるように、今回のプロジェクトのゴールを以下に設定しました。

  • パフォーマンス問題を起きにくくするための仕組みづくり
    • 既に起きている問題を解消
    • 本番に行く前の問題検知
  • 継続して価値を提供できる運用にしたい
    • 管理者側のメンテしやすさ
    • 利用者側の使いやすさ
    • 手作業をできるだけ減らして自動化できるものを自動化したい

パフォーマンス問題をできるだけなくすためには、既に起きている問題の解消に取り組む一方で、新たに問題が起きないようにしないといけません。そして、運用が煩雑だったり、使いにくくなると結局使われないので、運用面もきちんと考える必要があると思い、仕組みづくりと運用整備をゴールとして設定しました。

課題を解消するためのアプローチ

概要

大規模データが入ったDBをすぐ利用できるように、DBをAWSに乗せて、開発者のローカルマシンから繋げられるようにしました。開発者が個々でローカルでデータを入れることなく、ネット環境があれば使えるようになります。ローカルから気軽に利用できるようになると、検証がしやすくなり、パフォーマンス問題の解消や、開発中の機能のパフォーマンステストが簡単にできるようになります。

実現方法

大規模データの準備

仕組みを実現するために、まず大規模データを作る必要がありますが、設計する時点では本番データをマスキングして使う案と自前でダミーデータを作成する案がありました。本番データの一番のメリットはリアル性です。パフォーマンス問題を再現しやすく、取得するのに手間がかからないメリットがあります。しかし、ローカルから気軽にDBに接続できるようにする想定なので、万一マスキングロジックに漏れがあった場合の漏洩リスクが高いという懸念点が存在しています。自前でダミーデータを用意する案は、最初seedファイルやダミーデータを挿入することに手間と時間がかかりますが、マスキングすることによって変なデータができる問題を回避できるのと、データの量と種類が自由に設定ができるメリットがあります。そして、新規サービスなど、ユーザー数がまだまだ少ない場合だと、本番データがそこまで蓄積していないため、パフォマンス問題に気づきにくいです。将来を見据えて、大量なデータをあらかじめ作っておくと、パフォーマンス問題を未然に防ぐことができます。セキュリティリスクの回避と自由度を追求したいことで、今回はダミーデータを用意する方向で進めていました。 大規模ダミーデータの作成はseed-fu を使って動的に生成しました。seed-fuは、ファイル単位で実行できたり、簡単に書けるようなシンタックスシュガーがあったりと便利です。そして、データが偏って単純化にならないように、値の決定とリレーション関係はなるべくランダムで決めるようにしています。

DBクローンの仕組み

全体システム構成は下図になります。AWS上に今回の大規模DBが置いてあります。右側には大規模データを格納するMaster DB、そして左側には実際開発者が利用するTest DBがあります。AWS Auroraのclone機能を使って、開発者が利用するたびに、Master DBからクローン(Test DB)を作成します。利用するごとに、Test DBが作成され、開発者が同じタイミングで大規模DBを利用しても、お互い影響を受けることなく利用できるようになっています。

f:id:liaoziyang:20211025225107p:plain
概要

運用手法

Master DBはスキーマ更新などでこまめなメンテが必要です。DBスキーマのズレがあるとエラーが出てしまうので、常に最新のスキーマに追従できた方が良いです。毎回手動でmigrationなどを実施するのは手間がかかるので、できるだけ手作業をなくし、自動化を行いました。DBメンテ用のEC2インスタンスを作成し、毎日自動でコードをGitHubから持ってきて、migration等を実施するようにしました。 そして、開発者が利用しやすくするためにTest DBを作成するコマンドを1つにまとめたり、ローカルから繋ぐための作業をすぐできるようにしました。利用し終わった後の片付けも自動で行われるので、ストレスなく利用できるように手順を簡易化しました。

今後の展開

このプロジェクトは検証DB作成とローカル開発での利用にフォーカスして取り組みましたが、検証DBの仕組み自体は他に活用できそうなところがたくさんあると思いました。例えば、CIから検証DBを利用して、パフォーマンスの自動チェックを行ったり、QAでパフォーマンス確認を行ったりするのにも利用できそうかなと感じました。

期間中のタイムライン

以前の巨匠制度は事前にプロジェクトのテーマを公募し、誰がやるかは社内で投票して決める形でしたが、今回のマジ価値DeepDiverではやりたい人が自ら手を挙げて、その後にプロジェクトのテーマを決めました。私の場合のタイムラインはこんな感じです。

  • プロジェクトテーマ選定&DesignDocを書く
    • 4月~6月
    • 月1回壁打ち
  • 技術検証
    • 7月1週目
  • ガッツリ実装
    • 7月2~3週目
  • Doc用意、成果物検証
    • 7月4週目

実際に課題に取り組む期間は1ヶ月間ですが、準備期間が3ヶ月ありました。ただし、その3ヶ月は何をやるかをイメージして、ざっくりやりたいことを書き出すぐらいなので、特にガッツリ時間とっていたわけではありませんでした。月に1回、初代巨匠であるterashiさんとの壁打ちの機会があって、アドバイスいただいたり、どうやって価値を出すかを議論したりすることができ、とても有意義な時間を過ごせました。

7月に入ってからは、普段の業務から離れて、プロジェクトに専念する期間となりました。今回のプロジェクトと関係のないMTGを全部なくすなど、いろいろ施策を打っていただいたので、とても集中できました。プロジェクトのサポートとして、プロジェクト管理開発チームと、DB専門のチームにも入っていただいていて、プロジェクトとは関係のないMTGがない一方、コミュニケーションやFBを得るために、サポートチームとのWeeklyや朝会を開催していました。

得られたもの

  • 理想ドリブン*1でやりたいことにチャレンジできた
  • 主体性を持って、課題発見〜実装〜発表までほぼなんでも自分で決めて実行する経験
  • サポートチームを含め、社内から幅広くFBをもらう機会
  • 普段の業務から離れて完全に没頭できる時間

大変だったこと

  • 1ヶ月ほんとにあっという間だった
  • ほぼ1人でやるので、うまくいかなかったらどうしようというプレッシャーがあった
  • 普段あまりやらない課題なので、チャレンジ要素が多く、最初のキャッチアップが大変

挑戦の機会や、大きな裁量をもらう機会を最大限活かすための進め方やスタンス

  • 1人でもくもくやる時間がほとんどなので、定期的に他の人と話す機会を設けた方良い
  • とりあえずやってみるのが大事
    • 挑戦して失敗しても得られるものがあって、今後にも活かせる経験になるので、良い機会があれば掴みましょう
  • 準備期間で、課題のブラッシュアップをしておく
    • アイデアについて、いろんなチームからFBもらう
    • プロジェクトのゴールイメージを具体的に作っておく
  • 始まる前にスケジュールやマイルストーンを決めておくと進行がスムーズになる
    • 事前に段取りを決めると、進行する際に自己検査ができて、進捗が順調かどうかわかるようになるので、スケジュール通りに終わらせることに役に立つ

最後に

マジ価値DeepDiver第一号として、何も考えずやってみたんですが、やりたいことにチャレンジできて楽しかったです。個人的には、最初から最後まで自分で主体性を持って取り組むことがすこく貴重な経験だと思っているし、今後プロジェクトを任されたときにもこの経験を生かして、取り組んでいきたいなと思っています。今後マジ価値DeepDiver制度からどんな面白い取り組みが出てくるのか、楽しみにしています!

*1:freeeの行動指針のひとつ。理想から考え、現在のリソースやスキルにとらわれず挑戦しつづけるという意味

安定した開発を続けるサイトビジットのRails活用術

こんにちは、DevBrandingのellyです。10月1日に配信した「freee Tech Night 〜安定した開発を続けるサイトビジットのRails活用術」の様子をご紹介します。

サイトビジット社は2021年4月にfreeeの子会社としてグループにジョインしました。『資格スクエア』や『NINJA SIGN by freee』を開発しているサイトビジット社は、サーバーサイドのフレームワークにRuby on Railsを採用しています。

テストカバレッジ96%・ほぼ毎日各ライブラリのupdateを実施・専業のデザイナーなしでのUI構築など、限られたエンジニアの中でどのようにして高速にかつ安全に開発を進めているのか、開発の組織や文化についてお話してもらいました。

f:id:freee_developers:20211026104353p:plain
登壇者集合写真

yokozawa: 写真右上。エンジニアとしてインターンをしていたサイトビジットに2016年に正社員としてジョイン。エンジニアチームのマネージャー。

watakumi: 写真左上。サイトビジットに2020年に新卒入社。NINJA SIGN by freee担当のエンジニア。メソッドやクラスの命名に関心がある。

のぶじゃす (@noblejasper): 写真右下。ラジオパーソナリティ、2017年に中途入社。mixi、ソーシャルゲーム企業でソフトウェアエンジニアを経験し freee に。入社後はエンジニア→エンジニア採用担当→エンジニアと DevBranding を担当。しゃべりたがり。声が大きい。

「リーガル×テクノロジー」で社会のインフラになる

簡単に会社の紹介をお願いします。

yokozawa:私たちの会社はビジョンとしては「リーガル×テクノロジー」で社会のインフラになるというビジョンを持ってサービスを提供しています。現在は資格スクエア、リーガルエンジン、NINJA SIGN by freee(以下NINJA SIGN)の3つのサービスを提供しています。

弁護士や行政書士、司法書士など法律に関わる方に向けて、まずは資格スクエアで教育の部分を担い、そこで資格を取った人がリーガルエンジンを利用して就職、就職した先でNINJA SIGN by freeeを実務で使ってもらうというように、いろんなところでサイトビジットのサービスを使ってもらえたらいいなと考えています。

士業の方の実務・教育・就職を一気通貫でサポートするというサービスを提供しているんですね。開発はどんな体制でやっているんですか?

エンジニア組織全体では約20人で、基本的にはNINJA SIGNを担当しているエンジニアが多いです。NINJA SIGN内では2~5人のチームが5つあり、契約の締結に関わるチームだったり、契約書を管理するチームだったりで、ドメインごとに分かれています。

毎日bundle updateを実施

タイトルにもあるような「安定した開発」のためのポイントはどんなところでしょうか?

yokozawa:bundle updateを毎日やっているということと、テストカバレッジ96%を維持しているところは自分の中でもお気に入りのポイントです。

登壇者写真
とても楽しそうに話すyokozawaさん。そのおかげで終始和やかな雰囲気でした。

bundle updateに関しては、毎日gemの更新のPRをみんなでレビューしてマージするという形でやっています。メジャーアップデートに近い場合は、マージするのは避けて別でチケットを作っておいて、本当にリリースできるのかを別途精査しています。その別チケットも基本的にクリアになって減っていっています。

未対応のgem update案件は担当をどのように割り振っているんですか?

yokozawa:仕組みがあるわけではないです。6割は事業ロードマップの中でチームに割り振られている大きなタスクに取り組んで、4割はミーティングや事業側からの緊急依頼のためにバッファをとっている感じで、気になる人がやるくらいのふわっとしたルールになっています。

それでもちゃんと減ってるのはすごいですね。未対応のチケットに取り組むモチベーションってどういうところにあるんですか?

watakumi:やっぱり最新のものを使い続けるっていうのはエンジニアとして幸せというか、自分もそれに貢献できるっていうところがモチベーションになるんじゃないかなと思います。

yokozawa:あまり自慢する人がいなくて(笑)クールにやっている人が多いです。

テストカバレッジ96%を維持

テストカバレッジを上げるポイントはどういうところですか?

yokozawa:最初のほうは、レビューするときに「あれ、テストは?書いてないですね」っていうコミュニケーションが多かったですね。今は特に何も言わないけどテストがとりあえず入っているような感じになっていて、テストって当たり前にあるよねっていう空気感になっていると思います。

「障害対応や緊急対応でテストまでは書ききれないけど一刻も早くリリースしたい」みたいな場面はどうするんですか?

yokozawa:そういうときはテスト書けとはさすがに言えないので(笑)修正箇所について既存のテストを通っていることを確認した上で、hotfixで出した後にテストを書くようなチケットを作って、そっちで対応してマージするっていう形でやっているので、やっぱりテストとコードはセットであるようなイメージですね。

定期的にカバレッジが下がった、上がったみたいなのを振り返る会議体や制度があるわけではなくて、「テスト書くでしょ」みたいな空気感、文化の勝利みたいなところがありますね。ローンチ前からずっとテストを書いていたので、自然とこういう文化になっていったんだと思います。

watakumi:重要な機能の処理でエラーが起きたときに全部slackに通知がくるようになっていて、それがあるおかげでアクシデントが起こったときにも誰かが早く気付けてすぐに解決できるっていうフローになっているのは安定した開発のポイントかなと思います。監視ツールを使っているものもあれば、直接結果をログとして流しているようなものもあります。

登壇者写真
ポケモン好きのwatakumiさん(登壇中もゲンガーが見切れている)

基本的に平日に使われることが多いサービスだと思いますが、深夜や休日に障害の通知がきたらどのように対応しているんですか?

watakumi:夜中はいまのところ起きていないのでやっぱり安定してるんだなって思います(笑)

yokozawa:たしかに起こされたことはないですね(笑)

watakumi:仮に障害があったとしても特に当番は決まっていなくて、気づいた人がまず動き出して起きていることを発信するとみんなが集まってくれるって感じですね。

みんな自分事化しているんですね。

watakumi:こういうことが起きたときはみんなで対応しよう、というのが日々の振り返りでも会話されているので、そういう風土や文化になっていると思います。

yokozawa:障害があったら、どのように改善して何を学んだかをチーム全体で共有するというのはかなり意識してやっています。

Rails Wayに乗った開発を続けるためのコツ

サイトビジットのプロダクトはRails Wayにきちんと乗って開発しているというのが結構特徴的だと思いますが、そのコツや気を付けている部分などを教えてください。

watakumi:そもそもRails自体がMVCでモデル、コントローラ、ビューでできているフレームワークなので、現行のコードでもコントローラはなるべく薄くしてモデルに責務を寄せていて、それを崩さないようにしています。

社内でRails 警察みたいな人がいるんですか?(笑)

watakumi:警察みたいな人はいなくて(笑)、どちらかというと結構素直な人が多いのでいまあるコードってこういうルールで書かれているからこれを崩さないようにしようと、自然にRails Wayに安定的にのれているのかと思います。

Rails Wayに表記されてない曖昧な部分はどう決めてるんですか?

watakumi:個人の主張で決める人はいなくて、「こういう場合みなさんならどういう風に書きますか?」とSlackで投げかけてNINJA SIGNではこうしようとみんなで決めるというのが基本の流れです。

ある程度大きなプロダクトでMVC 作っているとサービスクラスを作りたくなると思うのですが、それをあまり作ってないみたいな話も聞きました。

watakumi:もちろんサービスクラス自体は存在していて、実装のスピードを意識すると一度サービスクラスに置いちゃおうということはあるんですが、ただその後にアクションはできていて、こういうサービスクラスがあるというのを誰かが議題に挙げて、こういう責務に分けられそうだからこういうモデルに落とし込もうという議論がPRやSlackで投げかけられて改善されていきます。

違う機能を作っているときにすでにあるサービスクラスを使うとなった場合、影響範囲分からないからやめようみたいな話にはならないんですか?

watakumi:もちろんそういうリスクを考慮した議論もあるんですが、テストを書いてるのである程度担保されている前提で落としどころを見つけられていると思います。

Slackやミーティングで全員で議論できる状態が作れているからこそ、影響範囲も整理できるということなんですね。良いチームですね。

サイトビジットの開発文化

みんなで同じ方向を向く秘訣やポイントみたいなものはありますか?

watakumi:コミュニケーションの活発さはうちの文化なのかなというふうに改めて認識しました。絶対こっちが正しいって強く押すというよりはみんなで合意形成した方がいいよねっていうスタンスの人が多いです。

yokozawa:主張する人はもちろんいますが(笑)お互いに話し合って無理にそれを押し通そうみたいな人は1人もいないですね。

サイトビジットの開発文化を明文化する中で賞賛、感謝、相手へのリスペクトみたいなものを伝えたりしています。何かすぐに頭ごなしに否定するのは止めようとか、チャレンジングな失敗はむしろ賞賛されるべきだよねとか、こういうチームがいいなぁというのは話したりしてますね。

watakumi:yokozawaさんのプッシュがかなりあるし、それによって得られるメリットもみんな実感してて、いまの文化につながっているんだと思います。

freeeにジョインすることで生まれるシナジー

freeeグループにジョインして今後やっていきたいと考えていることはありますか?

yokozawa:契約と会計ってやっぱり密接で、契約書には料金が記載されていて、契約書がないとBtoBのやりとりは成立しないので、その辺のシナジーは活かしながら、いままで僕たちがリーチできなかった顧客層にリーチしていきたいと思っています。

スモールビジネスのバックオフィスの担当者はいろんな業務を兼務してる方が多いので、freeeとNINJA SIGNを使って法務も会計も全部効率化できるようなプロダクトにしていきたいと思っています。

watakumi:僕はプロダクトの成長といった観点ではないのですが、freeeのエンジニアのみなさんとぜひ交流していきたいというふうに思っていて、いろんな開発経験とかを聞いて、freeeの知見をNINJA SIGNの成長に繋げていきたいなと思っています。

freeeのエンジニアとサイトビジットのエンジニアで交流会したいですね!

yokozawa:いいですね、BBQしましょう!

イベントの本編はここまでです。この後は「アフタートーク」でお互いの企業、エンジニア組織の理解をさらに深めました。

▶次回freee Tech Night

10/29(金)「freee流、クレジットカードのマイクロサービス設計・構築術」というテーマでお送りします。 freeeがクレジットカードの発行会社となるためにどんな設計をしどのように実装しているのか。そして「そもそもマイクロサービスってどうやって作るんだ?」という状態から、マイクロサービスの分け方、SNS/SQSでの非同期のサービス間通信やモノレポの導入などfreee内でも新しいチャレンジにどのように取り組んだのかお話を聞いていきます。

freee-tech-night.connpass.com

▶今回のイベントのアーカイブ(録画)


www.youtube.com

開発を止めずに Flow を TypeScript に移行する手法

Flow から TypeScript へ

こんにちは、freee人事労務開発チームでエンジニアをしている fiahfy です。
現在、freee人事労務のフロントエンドコードはほぼ TypeScript で記述されていますが、以前は Flow のコードが大部分を占めていました。
今回は、約 1200 ファイル程度あった Flow コードを開発を止めずに TypeScript に移行した話を紹介します。

TypeScript の導入

私が初めて開発に携わった当初、フロントコードは主に Flow で記述されていました。
私自身 Flow を触っていなかったというのと、エディタ周りのツールの影響であまり開発生産性が高くないと感じ TypeScript の導入を行うことにしました。
導入後は新規コンポーネントを TypeScript で作成する様にし、記述のしやすさや Flow との書き方の違い等を確かめました。

TypeScript でコードを書くうちに、型推論が Flow に比べてかなり賢く、エディタ周りの拡張が充実しており、開発のしやすさを感じました。
(Flow の型推論がイマイチと感じた理由には、Flow を最新 version で利用していなかったというのもあったのですが、実際に update を行おうとするとかなりの Error が発生し、更新負荷が高かったというのもあります。)

TypeScript 単体で利用する分には全く問題なかったのですが、一方で Flow と TypeScript を混在した状態で利用する場合は import 周りでの型の受け渡しに問題があります。
以下のような、Flow から TypeScript を import するコードです。

math.ts

export const add = (a: number, b: number) => a + b;

main.js

// @flow
import { add } from './math';  // TypeScript で記述されているコード

const result = add(1, 2) // 引数、戻り値に対して型が効かない

この場合 add 関数が any 型となり、引数、戻り値共に型が効かない状態になってしまいます。
一応、.flow.js ファイルや .d.ts ファイルなどを配置して型情報を提供することで、Flow / TypeScript の言語間を隔てて型情報の共有をすることは可能ではあります。
しかし、この方法は手動による型定義のメンテナンスが必要となり、修正漏れが発生するとかえって型環境全体が信頼できないものとなっていくリスクをはらんでいます。そのため、自動変換による手法を模索することにしました。

変換ツールの検証

Flow コードを TypeScript コードへ変換するツールはいくつか存在し、その中で以下のツールを実際に利用して変換結果を検証しました。

変換結果を検証するために、スタンダードな型パターンを含むサンプルコードで検証を行いました。
サンプルコードを実際に変換してみて結果を検証した結果 babel-plugin-flow-to-typescript が一番求めているような変換をしてくれることがわかりました。
変換自体はこのツールを使えばおおよそうまくいきそうなことがわかりましたが、babel plugin となっておりこのままだと少々利用しづらいため、この plugin を wrap するスクリプトを作成しました。

https://github.com/fiahfy/flow2ts

このスクリプトで Flow コードのファイルを指定し実行すると、同じ階層に TypeScript に変換されたファイルが出力されます。

$ npx @fiahfy/flow2ts src/components/MyComponent.js
Output /.../src/components/MyComponent.tsx

またこのスクリプト内で、機械的に対応できる型の調整を合わせて行い、後述の変換工程の負荷を減らしています。

部分的に変換を始める

このスクリプトによってFlow コードを TypeScript に変換する準備は整いました。
小さなサービスや、すでにほとんど修正が発生しないサービスであれば一定期間開発を止めて一度に変換する手法を取ることができますが、対象のサービスは比較的巨大なサービスで、修正も絶えず発生しているため開発を止めることができません。
そこで部分的に変換を実行していくステップを踏むことにしました。

対象サービスのフロントエンドコードの構成は以下のようになっており、機能ごとに1エントリーポイント = 1ディレクトリ(以降 feature directory と呼びます)となっており、依存は共通モジュールを除くとディレクトリ内だけで閉じている関係にしています。

src/
├── features  # 機能ごとの directory
│   ├── docs
│   ├── employees
│   └── settings
└── packages  # 共通モジュール

このように feature directory ごとに依存性を独立させている構成を取っているため、feature directory 単位でまとめて TypeScript 化を行えば、他の feature directory 内のコードに影響を与えることなく、また Flow と TypeScript の接する部分で型の弱化もすることなく、TypeScript 化を進めることができました。

TypeScript コードから Flow の型定義を生成

ここで問題となってくるのが共通モジュールへの依存による型周りの問題です。
共通モジュールは TypeScript 化が完了するまで、Flow/TypeScript どちらのコードからも参照されることになります。
この問題を解決するには、前述したような型定義ファイルを用意する必要がありますが、実コードと型定義ファイルの二重管理を余儀なくされ、さらに型定義ファイルの更新漏れにより型情報が誤ったものとなってしまう懸念もありました。

そこで共通モジュールを TypeScript に変換後、そのコードから Flow の型定義ファイルを自動生成するツールを作成し、型情報を担保する形を取りました。(同じチームの keik 氏作)

https://github.com/keik/generate-flow-types-from-ts-package

共通モジュールに修正を行った場合は、こちらのツールで型定義ファイルを生成し commit を行う必要があります。
上記の作業だけでは型定義ファイルの更新漏れが発生する可能性があるため、CIの方でも同様に型定義ファイルを生成し、同期が取れていない場合はCIで検知する仕組みを導入しました。

feature directory 単位での変換

共通モジュールの問題が解決したので、次のステップでは feature directory ごとに TypeScript 化を行っていくことができます。

前述の変換スクリプト(fiahfy/flow2ts)により TypeScript 変換自体は簡単に行えるのですが、実際は Flow での型付けが甘かったり、Flow 自体の問題で単純に変換しただけだと大量の TSError が発生します。

変換中は対象の feature directory 上での開発を止めるという選択肢もありましたが、やはり修正が絶えず発生しているサービスのため、以下のようなフェーズで進める方法を取りました。

prepare

変換までの事前準備を行うフェーズです。

変換スクリプトを手元で実行し TypeScript コードの出力結果を見て、Flow コードを調整、再度変換し、変換後に TSError を発生しないようにします。
つまり現状の Flow コード時点ではエラーが起きないが TypeScript 化後に発生する型エラーに対して、変換前後の Flow / TypeScript どちらのコードでもエラーが発生しないよう、 Flow コード時点で修正します。

単純に型のみを修正するだけでいいような Error もあれば、実コードに手を入れる必要のある修正もありました。
エラーの数によっては負荷の高い作業ですが、事前にこの対応を行っておくことで変換作業自体の時間をほぼ取ることなく conflict の発生を防げます。
結果的にはこの作業が移行作業の大半の時間を占めていました。

flow2ts

実際に変換を行うフェーズです。

このフェーズでは conflict の防止や万が一何かあったときの rollback を考慮して基本的には変換以外の修正は行わないようにしました。
具体的には以下のような手順で変換を行っていました。

# 変換
flow2ts <feature_directory>
# eslint autofix
eslint <feature_directory>/ --ext  .ts,.tsx --fix
# format
prettier <feature_directory>/**/*.{ts,tsx} --write
# 変換前のファイルや不要になった型定義ファイル削除
rm <feature_directory>/**/*.{d.ts,js,js.flow}

事前準備が済んでいるとこの作業は数回のコマンドを叩くだけで完了します。

cleanup

変換後のコードを調整するフェーズです。

動作には影響しませんが、変換スクリプトで出力されるコードはコメントがずれてしまったり、改行が意図していない箇所で挿入/削除されてしまうため、目につくところを手動で対応します。
また、prepare フェーズで any 型を使って TSError を回避していた箇所などを直せる範囲で対応します。

まとめ

上記の手順で TypeScript 移行を行ったことにより、日々の機能開発を止めることなく、バグも出さずに移行作業を完了することができました。
移行自体は完了しましたが、TypeScript の型システムの恩恵を最大限に受けるためにはまだまだ課題があります。

  • 変換の工程で一部の型を緩くしてしまった
  • 元々 any 型を多用してしまっている
  • バックエンドとの型の整合性の担保等

freee人事労務では上記の課題も含め、引き続きフロントエンドでより型安全に開発できる環境を整えていきたいと思います。

【連載 第7回】クレジットカードのインフラ作ってみた

どうもSREのid:renjikariです。この記事はfreeeカード Unlimited の開発の裏側について紹介する連載の第7回目になります。

私は直近でfreeeカード UnlimitedのStaging&Production向けのインフラ構築などを担当していました。(id:nekottyo が検証用アカウント作成&整備や検証環境作成といった下地作りをしてくれてました:ty:)

最近ベータリリースを果たしまして実際にお客様がクレジットカードで決済を通せるようになりました。感激ぃ!

さて、このdevelopersブログでぜひインフラについて書いてほしいという依頼を受けまして嬉しい限りなんですが、実のところインフラについて言えば他のfreeeのサービスと大きく違うところはあまりなく、(blogに書いて自慢できるほどではないのですが)freeeでつくっているインフラ構成の一つとしてfreeeカード Unlimitedの裏側を紹介しようかと思います。

freeeカード Unlimitedは子会社での開発のためAWSアカウントがそのほかの自社サービスと別なためインフラ構築についてはクロスアカウント対応に苦労したというところがあるので、それについては知見をかけたらと思っています。

AWS EKSを利用したサービス構成

freeeカード UnlimitedはComputingにAWS EKSを利用しています。まずは概略図を示します。

f:id:renjikari:20211019143513p:plain
構成概略図

これは実際のものとは違いますが似たような構成のサービス群になっています。 freeeカード Unlimitedでは1つのEKSクラスターにweb/カード管理/決済/請求/通知など複数のサービスが乗っているマイクロサービスになっています。 それぞれのサービスにはHTTP(S)で外部から通信するものとクラスター内でRPC通信するものがあります。 自社の他サービスとも連携する必要があり、それは別のEKSクラスター上に存在するサービスと通信する形となっています。

サービスの構成についてもう少し詳しく書くために、一つのサービスを抜き出して少しだけ詳細に書いた図を書きます。

f:id:renjikari:20211019143624p:plain
サービス詳細図

一つのサービスはAWS ALBのロードバランサーで通信を受けたあと、基本的にEnvoy Proxyを通ってサービスのDeployment(Pod)に到達する構成になっています。Envoyをかませてサービスへ通信させるような構成はサービスメッシュぽさがありますが、freeeでは外部からの通信がある商用サービスにおいてIstioを始めとするサービスメッシュを利用した事例がまだ存在せず、このfreeeカード Unlimitedで初めて運用にのせるのはハードルが高かったので採用していません。freeeの各サービスで同じ構成をとっているので社内での標準化のためにもこの構成になっています。 他にもウェブアプリケーションにおいて標準的な構成ではありますが非同期的な処理にはSNS+SQSをCronJobから呼び出したりしています。非同期通信については連載第2回で詳しく書かれていますのでそちらをご参照ください。 また、詳細は省きますが協力会社様との情報のやりとりにS3などを利用したりしています。

ここまで出てきたAWSリソースやKubernetesについてのInfrastructure as Codeについても紹介します。 EKS含むすべてのAWSリソースはTerraformによってコード化されています。中でもEKSの構築にはTerraformのmumoshu/terraform-provider-eksctlを利用しています。これ以外にもAWSのprovider以外のものを利用していくつかの初期設定などを行っています(ex. EKS構築時のaws-authの設定) Kubernetes内のリソースについてはhelm + helmfileでtemplate化しており、ArgoCDによってGitOpsを利用したデプロイを行っています。また、freeeカード Unlimitedだけの話ではなく社内での標準化の話ですが、helmによる社内向けの共通チャート(ex. http-server/grpc-server/application-worker)などを用意しており、Docker Imageのrepositoryやtag、環境変数などのいくつかのvaluesを入力するだけで使えるようなものが用意されています。これによって(SRE以外のアプリケーションエンジニアでも)簡単にアプリケーションを動かせるだけでなく、社内の各サービスごとにmanifestを共通化できSREの運用を楽にしています。

そろそろ話が広がってきたのでマイクロサービスアーキテクチャの章に移りましょう。

マイクロサービスアーキテクチャの採用

freeeカード Unlimitedは 連載第一回の記事でも紹介されています通りgRPCを利用してマイクロサービスアーキテクチャになるよう設計されています。 コード上についてはすでに上記の記事で解説されているので、この章ではインフラ周りについて触れます。

さて、マイクロサービスという言葉が使われるようになってからはすでに久しいですが、freeeでも様々なマイクロサービスが存在しています。 ただし多くのものは大きなモノリスなサービスから一部を切り出したり、新規機能を別サービスとして切り出したりするような構成になっています。 今回のfreeeカード Unlimitedはかなり大きなサービスでありながら最初からマイクロサービスとしてインフラ的に各々が切り離された構成をとっているfreeeにとっての初めてのサービスになったと思います。そういった意味で良い挑戦でした。(とはいえ一つのサービスをどこからどこまでと決めるのは難しいのですでにマイクロサービスと言っても過言ではないサービスはあったというエクスキューズを書いておきます)

マイクロサービス構成をとるにあたってインフラ的にいくつかの選択肢がでてきたところがサービス間の通信設計でした。 再度各サービスの図を貼りますが、

f:id:renjikari:20211019143513p:plain
構成概略図

freeeカード Unlimited外部からの通信は基本的にHTTP(S)でおこないます。考える余地があったのはサービス間のEKS cluster内部の通信で、結果的には(前述のように)すべてのサービスの前段にEnvoy Proxyをおいて、cluster内部のRPC通信は全てEnvoy経由にしました。 他の案としてはマイクロサービスAからマイクロサービスBへK8sのkind:Serviceむけに直接通信する/毎回cluster外にでてAWS ALB経由で通信するというものもあったのですが、以下の理由でEnvoy経由に決定しました。 ① Envoyであれば負荷分散しやすい(Service向けに直接通信する場合HTTP2のコネクションが永続化されるため負荷分散に向いていない) ② 他のサービスの構成を踏襲したい

クロスアカウント

最後にクロスアカウント対応について話します。 とても複雑というわけでは無いのですが、多くの構築作業時にクロスアカウントの対応が必要であり作業が大変でした。 freeeカード Unlimitedがクロスアカウントで連携している図を載せておきます。ここに乗っているのは一部のサービスであり、また詳細は省略されています。

f:id:renjikari:20211019153739p:plain
クロスアカウント連携図

どんなところでクロスアカウントの対応が必要だったかというところを箇条書きにまとめて書きまして、最後の知見共有とします。

  • freeeカード Unlimitedのサービスと他サービスの通信で、以下の対応が必要
    • VPC Peering(transit gatewayにしたい…)
    • Peering ~ SubnetのRoute設定
    • SecurityGroupの設定
  • internal DNSの権限移譲
  • Deploy(Argo CD)Cluster とのクロスアカウントIAMにおいてaws-auth設定
    • Argo CDからKubernetes APIが叩けるようにするための設定
  • 監査対応(詳細省略)

さいごに

冒頭で書くことは特に無いとか言った割に長々と書いた気がします。 自身でもまさかクレジットカード事業のインフラを担当する日が来るとは思わなかったので時間的な制約などもありましたが、プロジェクトに参加できてよかったです。

この記事でも詳細には言及できなかったところもあるので、また機会があったら書こうと思います。とくにArgoCDやCDまわりについては、Cloud Native Days Tokyo にて登壇予定ですので興味があればそちらも合わせてどうぞ(宣伝)

freeeのSREでは一緒にはたらく仲間を募集しています https://freeecommunity.force.com/jobs/s/detail/a4l1000000170inAAA?_ga=2.14218231.1353744525.1634048794-37020062.1554095000