Findy Engineer Lab

エンジニアの"ちょい先"を考えるメディア

GoとRubyの言語特性を理解して構築した“いいとこどり“アーキテクチャとは?

f:id:poniki:20220124185154p:plain ファインディでは「次に学びたい言語」として注目集めるGoについて理解を深める『注目の言語Go、開発現場でどう使われる?活用企業の現場に聞く』を開催しています。

11月25日には第3回目を開催。ウォンテッドリーの竹野さん、マネーフォワードの櫻さんをお招きし、Goの特徴やメリット、採用理由について伺いました。モデレーターはファインディの大原が務めます。

パネリスト
竹野 創平さん/(ウォンテッドリー株式会社 開発チーム アーキテクト)[@Altech_2015]
2016年にウォンテッドリーに入社。プロダクトのグロースや機能開発・基盤開発を行なったのち、プロダクトの責任者としてプロダクトマネジメント・エンジニアリングマネジメントを行う。現在は開発チーム全体のアーキテクトとして、ソフトウェア・技術・組織の設計に携わっている。

櫻 勇人さん/(株式会社マネーフォワード バックエンドエンジニア[@ysakura_]
2018年11月に株式会社マネーフォワードに入社。 バックエンド、インフラ、アーキテクトのスキルを活かし、マネーフォワード クラウド会計Plusのマイクロサービスの開発運用をリード。社内でのGoの教育や導入サポートも担当。現在はマネーフォワード クラウドでPublic APIを推進するべくAPI基盤を開発中

モデレーター
大原 和人/(ファインディ株式会社) [@kaacun]
2012年に株式会社オールアバウトに新卒エンジニア1期生として入社。サーバのクラウド移行やDevOpsの推進、サイトリニューアルの開発などを担当。その傍ら、週末に3社ほどのスタートアップの開発や立ち上げの手伝いを行う。2017年11月よりFindyに正社員1人目として参画。

高パフォーマンスを求めてGoを導入、マイクロサービス化

まずはお二人がGoを採用する前後のアーキテクチャについて紹介してくださいました。

ウォンテッドリーの竹野さんは、すべてRailsで開発していた2015年から、Go導入後の2019年の違いを共有します。

竹野さん:2015年がちょうど最初の Go のサーバーが導入される直前くらいの時期になります。この時、プロダクトとしては会社訪問サービスの『Wantedly Visit』とビジネス用グループチャットアプリ『Wantedly Chat』を開発していました。

アーキテクチャとしては、それぞれのプロダクトに対応するRailsサーバーがあり、別で認証用のRailsサーバーがあるという構成でした。合計で3つのサーバーがありますが、各プロダクトチームはほぼRailsでモノリシックに開発をしているので、マイクロサービスではなく、マルチサービスと考える方が適切かなと思います。

その後、新規プロダクト『Wantedly People』の立ち上げがあり、並行処理や機械学習など Ruby では難しい要件があったため、複数の言語を併用するマイクロサービス・アーキテクチャを採用しました。プロダクトチームが複数のサーバーを持つようになった、というのが強調しておきたい重要な変化ですね。

このときの開発経験を踏まえて、直近はコアとなるドメインは Rails で開発しつつ、切り出しやすいコンポーネントは Go や Python で開発するようなスタイルに落ち着いています。

複数の言語を使っていると開発者への負担が大きいように思うかもしれませんが、API は言語に依存しない Protocol Buffers という形式で記述しているため、呼び出し側は内部実装を意識しない開発ができています。

参考記事:Wantedly における Go 導入にまつわる技術背景
マネーフォワードの櫻さんは『マネーフォワード クラウド会計Plus』におけるGoの導入前後の違いを説明します。

櫻さん:2018年くらいまではRailsが開発言語の中心となっている会社でした。Webは基本的にRailsで一部の基盤はJavaを使っていました。

そこからユーザーが増えるにつれてデータ量が急増、性能の問題が出てきました。特に帳簿を作成する処理は重くなりやすいのもあり、Goでマイクロサービス化を図ってきました。

一つのプロダクトの開発チームが一つのサーバーを見ていた世界から、一つのプロダクトの開発チームが複数のサーバーを見ていく世界に。マイクロサービス化の流れは『マネーフォワード クラウド会計Plus』に限った話ではなく、他の開発組織でも進めています。

以下の図の通り、帳票APIはgRPC、マスタAPIはREST、最近では共通基盤APIもGoで書いています。

プロダクトや組織の変化に合わせ、マイクロサービスへの移行とGoの導入を進めてきた二人。大原がより具体的にGoの選定理由を聞きます。

竹野さんは「Railsに足りない部分をGoで補えると感じた」と語ります。

竹野さん:Rails は動的型付け言語なので素早く動くものを作りやすいですよね。また、それを活かして優秀な O/R マッパーも作れる。Goは静的型付け言語であり、動くものを作るまで時間はかかるが Ruby より堅牢に作れるし、パフォーマンスも高い。

それぞれの技術を適用できる領域が異なることが、新しい技術を導入する上で重要だと考えていて、Go はその辺りの立ち位置がハッキリしているのが使い続けている要因です。

Goでの開発向きだったマイクロサービスとして『Link Tracker』と『Notifications』を挙げます。

竹野さん:『Link Tracker』は、リンクを URL として渡すと、それを踏んだことをロギングするような URL を生成して管理してくれるマイクロサービスです。やることは単純なのですが、その分パフォーマンスは求められる。

こういった機能は汎用性があるので、そもそもマイクロサービスとして作っておけば別のプロダクトからも利用できるのでできればそうしたい。

ゴリゴリと新しい API を生やすようなものでもないので、Go でちゃんと作っておいたことで、性能面はもちろんのこと、堅牢性やメンテナンス性の観点で良かったと思っています。

『Notifications』は、サービスのお知らせ機能に相当するマイクロサービスです。

これはAWS の『Amazon SNS』をバックエンドに持っていて、内部ではパフォーマンスを高めるために並行処理をするなど複雑なことをやっています。この辺りは Go の使い所ですね。

また、マイクロサービスの話になってしまいますが、コンポーネントを分けておけば「お知らせ」という機能が落ちてもメインの機能が落ちないため、フォールト・トレランス(障害分離)ができるのが嬉しい点です。

続いて、櫻さんもGoを選定した理由について詳しく共有します。技術要件を満たせるだけでなく、社員のモチベーションや採用などの理由もあったと言います。

櫻さん:性能の観点では、高速化でき、可用性も欲しかったので、静的型付け言語が適当かなと思っていました。

なかでもGoは、社内に元々Goを扱っていてモチベーションの高い人がいましたし、社外でもシェアを獲得していました。モチベーションや情報のアクセスのしやすさという点からも、Goがいいのではと採用に踏み切った形です。

ウォンテッドリーと同じく、RailsでスピーディーにWebを作れる部分はRailsを使う。性能やカッチリした開発が求められる部分はGoで書いています。

誰でも読みやすい素朴でシンプルな言語仕様が魅力

Goでの開発体験を大原がたずねると、櫻さんは「誰でもキャッチアップしやすい言語のシンプルさ」を挙げ、竹野さんも「言語仕様の素朴さ」を挙げました。

櫻さん:『A Tour of Go』や入門書に目を通せば、大体読めるくらいシンプルで、開発者が言語仕様についてキャッチアップしやすい。

また、インターフェースで動的型付け言語に近いこともできる。いいとこ取りを目指せるのも特徴ですね。あとは周辺ツールがGoで完結するのも開発体験としては優れているなと思います。

竹野さん:久しぶりにGoを読みにいっても、イディオムがなく読める素朴さはいいですよね。覚えておく必要のある情報が少なくて済む。

先程の『LinkTracker』のように一度作ったら頻繁に機能追加をしないマイクロサービスなどは、Goで書いておくと、何かあったときに誰でも中身を理解しやすいので嬉しいですね

そんなGoを採用してよかったこととして、櫻さんは「gRPCとの接合での苦労はあったが、ユーザーに処理の速さという価値を届けられたのでは」と語ります。

竹野さんも「途中の基盤整備のコストは結構あったが、導入することでアーキテクチャ設計で取れる選択肢は増えた」と言います。

竹野さん:Goを採用せず、すべてRailsだけで書いていたら、アップグレードなど大変だっただろうと想像できますし、そもそも同じ粒度のアーキテクチャにはならなかったと思います。

Goは機能が必要最低限に抑えられているので、小さい機能を維持しやすく、アーキテクチャ上の選択肢が広がりました。

関連して「組織にGoの知見を広げていく際の難しさ」についても話題に上りました。二人とも一定の壁はあると指摘します。

櫻さん:他の言語からGoに移るのは思ったよりも壁がありました。特にGoには継承のように、JavaやC#なら当たり前にある仕様が存在しないことがある。

また、Railsの様なフルスタックな言語を書いてきた人からすると、書く量が多すぎたり、何からやればいいかわからなかったりするんです。

一方で、興味を持っている人やモチベーションの高い人は非常に多い。マネーフォワードで初心者向けの勉強会を企画すると沢山人が集まります。心理的障壁はそれなりにあるけれど関心は高い印象です。

竹野さんも「書きたい人が多いのは組織に広げていくうえで利点だと思う」と語ります。そのうえでRailsで開発してきたメンバーの多い組織ならではの壁を教えてくれました。

竹野さん:Ruby は非常に柔軟な言語なので、例えばあまりテスタブルな設計にしていなくてもモックしてテストができちゃったりします。そういったものに慣れた状態から、静的型付け言語のプログラミングスタイルを覚えてもらうには、一定の時間と労力が必要でした。具体的な取り組みとしては、地道にコードレビューをしたり、Goの優れたコードを読む会をやっていましたね。

Railsとの使い分け、書く量が増えがち…Goの苦労話

その他、導入にあたっての苦労話を大原がたずねると、櫻さん、竹野さんともにRailsからGoに移行したことから、両者の使い分けが話題に上りました。

竹野さん:早い段階で諦めたのですが、導入当初は期待値が膨らんでいてRailsと同じくらい速く開発できることを求めてしまっていて、それが難しかったですね。例えばRailsだと「Active Record」というO/Rマッパーが相当優秀だったりする。そういうものの代わりとしてGoを使おうと思わず、使い分ける方がいいのかなと。

櫻さん:私も同意です。RailsをGoでリプレイスしなくてよかったなというのが現時点での結論です。

特に配列に対する操作だと、Goにはフィルターやマップなど関数型の様な考え方がなく、書く量が増えがちな気がします。初期の立ち上がりの速さを重視するならRailsというのは非常に共感します。

言語仕様がシンプルゆえに書く量が増えるという点も両者が同意していました。

櫻さん:言語がシンプルなのでコードが多くなりがちですよね。初期に書いたコードは読みづらいものもあり、後にリファクタしました。的確なパッケージを作ったり、使いやすいライブラリを活用したりすることが大切だと思います。

また、言語仕様はシンプルですが、シンプルを組み合わせて、複雑なものを作るというのは大変。何もない状態からパズルを組み立てていく面白さがあるように思います。社内には、まだ決まったお作法やスタンダードもないので、みんなで探っている感じがありますね。

あとはGoroutine(ゴルーチン)を使った並行処理が正常処理されず、内部リソースが圧迫されて障害が起きてしまうなど、Goならではの悩みがありますかね

竹野さん:Goは素朴に書けるのがいいところです。ですが、実世界のサーバーは複雑なので、複雑なものを素朴に書いても複雑にしかならない。無理に抽象化しなくていいですが、抽象化が必要ないわけではないですし、設計が必要です。

Rails だと「MVC」のようなアーキテクチャが予め存在しているのでその想定している複雑性の範囲内では上手く作れるのだけど、それを超えるとデフォルトの構成が邪魔になってくるというのがあります。

一方で Go はそういうデフォルトものが存在していないので、良くも悪くもプログラマーが設計しないといけないし、設計すれば上手くやれるというのがあると思います。

ライブラリ選定、アーキテクチャで気をつけていること

それぞれが駆使しているライブラリやフレームワーク、選定のポイントについても語り合いました。

櫻さんは「要所要所でライブラリを使っている」と語ります。

櫻さん:Goは標準パッケージが優秀なので、標準パッケージでも色々なものを作れます。ですが、そこそこ書く量が多くなってしまうし、初めは理解しづらさがあるので、ライブラリを使うことが多いです。

具体的にはWebのルーターとして『Echo』、DBアクセスに『sqlx』などを使っています。選定については、初期は調べて評価の高いものを試してみる方針でした。中盤以降はチーム全体の慣れ、やりたいことに応じて選んでいます。

竹野さんは『SQL Boiler』などのライブラリに加え、自社で開発した『grapi』も挙げました。

竹野さん:データベースレイヤーはRDBのときは『SQL Boiler』を使っています。これは『sqlx』より少し上のレイヤーのライブラリで、データベースのスキーマファーストという点では『Active Record』と同じなのですが、スキーマからの構造体の生成を実行時にやるのではなくコード生成で行うので Go の読みやすさが維持されています。

一方でサーバーは自前で『grapi』というフレームワークを作っています。これは gRPC / Protocol Buffer を元にしたコード生成を中心に API の開発にレールを敷くもので、デフォルトのディレクトリ構成を提供するとともにそこへのコード生成も API 定義から行います。たくさんサーバーがあると、実装の構成が同じでないと辛いので、こういったフレームワークを差し込んでいます。

加えてGoのサーバー内部のアーキテクチャについて気をつけていることも聞きました。

ウォンテッドリーではクリーンアーキテクチャを採用していますが「クリーンアーキテクチャのなかで何をデザインするか」を忘れないよう意識しているそうです。

竹野さん:クリーンアーキテクチャの記事は多いですし、手段として知っておくことに意味はあります。一方で、本当に必要になるまでは無理に取り入れなくてもいいとも思います。

クリーンアーキテクチャはドメインを設計するために技術的詳細を分離するという整理を提供してくれますが、逆に言えばクリーンアーキテクチャがやってくれるのはそこまでです。本来大事なのは、そのなかでどういうドメインモデルを考案し、どのような操作は許容して、どのような操作は許容しないか、といったことだと思います。

なので、アプリケーションの要件に向き合いながら色々リファクタリングをしてみて、しっくりきたら使うくらいのスタンスでいるのではないかと感じます。

自分たちの場合、色々サーバーがあるので最も複雑なものに耐えられるようにクリーン・アーキテクチャ・ライクなディレクトリ構成を雛形にしていますが、厳密にそれに従う必要はなく適度にショートカットして良いようにしています。

櫻さんは「どのくらい抽象化するかは気をつけている」と言います。

櫻さん:RDBなどへのデータアクセス層と、handler層、その間のロジック層は最低限分離して、増やしすぎないようにしています。ガチガチに分けすぎると書く量が増えすぎてしまいますから。

言語特性のいいとこどり、ポリグロットな将来設計

最後に、今後の構成をどうしていきたいか展望を語っていただきました。

櫻さんは「RailsとGoを掛け合わせた構成は変わらない想定」とのこと。

櫻さん:言語特性のいいとこどり、Polyglotな考え方は変わらない予定です。メインのWebシステムはRails。新規サービスはRailsで素早く開発し、マイクロサービスや基盤で性能や安定性に優れたGoで開発するといった形です。

具体的には極端に遅いサービスをGoでリプレイスして高速化したいですね。あとマイクロサービスの分割の設計をもう少し見直したいです。

Goの教育についてもシニアのGoエンジニアがQ&Aやレビューしながら学習していけるような仕組みを構築しているところです。

竹野さんはGraphQLの導入を進めており、今後の展望を示します。

竹野さん:システム自体は今後も Polyglot なマイクロサービスで構成していくつもりです。一方で、ウェブアプリケーションやモバイルアプリケーションとシステムの間の通信には GraphQL の導入を進めています。

現状の構成だとシステム側の API 設計が、どのような単位でデータを取得するかというアプリ側の要件からどうしても影響を受けてしまい、そのためのコミュニケーションも必要になるという課題があります。

GraphQL はこれらの責務をアプリ開発者の方に移します。前段にゲートウェイとして GraphQL サーバーを置いてシステムのデータをアプリ開発者が自由に読み書きできるようにすることで、プロダクト開発を加速させていきたいと考えています。

Findyでは、Goに限らず注目の技術にまつわるイベントを定期的に開催しています。ぜひイベント情報をチェックしてみてください。
https://findy.connpass.com/

また、Findyでは、Goを採用している企業の求人を特集しています。 7社の求人が掲載。 「実務でGoを使いたい!」という方はぜひチェックして、気になる求人には「いいかも」してみてくださいね。
https://findy-code.io/pick-up/articles/golang-beginner