Goのアーキテクチャとフレームワークについて

社内slackでGoについて質問されて、それなりに長文で回答したのでその内容を加筆修正したものをブログに残しておく。 質問内容としては以下のイメージ。

RubyだとRailsがあり、MVCを利用することになるが、Goだとそこらへんはどうなるのか?
Go初心者なのでGoのモダンなアーキテクチャとフレームワークについて教えて欲しい。

これ系の質問はGo経験者であれば「あーこれなー」と思うだろーし、 Go初心者のときに一度は悩んだことがあるだろう。 なので、個人的な意見を残しておく。 自分の意見が正しいかどうかは自己判断して欲しい。

結論

結論から言うと "要件による" とか "ケースバイケース" になってしまう。 ただ、それだと元も子もないので、 なぜそういった結論になるのかをアーキテクチャとかフレームワークとかGoの観点で掘り下げていく。

アプリケーションアーキテクチャの複雑化とMVCフレームワーク

ここ最近アプリケーションアーキテクチャが複雑になっているなーと感じる。

昔の話というわけではないが、 自分が新卒だった頃は Ruby に限らず PHPPython のようなWebで利用される言語には代表的な(Railsライクな)フルスタックなMVCフレームワークがあり、 それぞれに細かい違いはあれど、開発者はフレームワークのルールに沿って開発する時代だったように思える。 それらのフレームワークがサポートしているMVCアーキテクチャはシンプルでわかりやすい反面、 ファットコントローラー、ファットモデルといった問題がつきまとっていた。

それに対する解決策として、レイヤ構造やリポジトリパターンというMVC以外のアーキテクチャや実装パターンの概念が出てきた。 これが今流行っているクリーンアーキテクチャとかの流れかなと思っている。 正確に言うとこれらもMVCと同様に昔から存在していたが、 自分が見ている範囲でこれらが注目されてネット上の記事を見かけるようになったのは比較的最近かなという感じである。

これらは結果的にMVCで問題になるファットコントローラー、ファットモデルを回避することに貢献した。 クリーンアーキテクチャのようなレイヤ構造はMVCと比較すると層が1つ or 2つほど多くなる傾向にあり、 MVCよりも細かい "関心の分離" を実現する。 これによりファットコントローラーを回避することが可能になる。 リポジトリパターンやドメインレイヤはMVCのMをドメインオブジェクトとデータオブジェクトに分離することで過剰なファットモデルを回避することができる。 当然これらの仕組みも正しく使わなければファットコントローラー、ファットモデルの亜種みたいな実装になってしまい、 いまいち効果がなかったりするのだが、それっぽい解決策の1つではあるだろう。

このようにMVC以外のアーキテクチャや実装パターンが目立つようになると、 単純にフレームワークが用意したMVCをベースにしたモジュール戦略に沿って実装するのではなく、 要件によってある程度手を入れる必要が出てくる。 例えばRailsであれば "サービスレイヤを導入する or しない" とか "ActiveRecordドメインオブジェクトとして利用する or しない" 的なものがそれにあたる。 その結果、アプリケーションアーキテクチャにおいて、 今まで恩恵を受けていたフレームワークならではの便利な仕組みを利用することが難しくなってしまうケースが出てきた。

システムアーキテクチャの複雑化とフルスタックなフレームワーク

昔はWebのシステムアーキテクチャといえばLAMP構成だった気がする。 特にDBはMySQL/PostgreSQLのようなRDBの利用が当たり前だったので、それに対応しているフルスタックフレームワークのORMはとても便利な機能だった。 しかし、現在は要件に合わせてRDB以外のDBを選択することも多くなったり、 非同期通信やキャッシュ戦略なども必要になった。 マイクロサービスアーキテクチャによって API Gateway に代表されるような特定の機能のみを提供する薄い実装も増えてきた。 クラウド環境が提供するSDKを利用するだけで実装がフレームワークの機能を利用しなくて済む(利用できない)ケースもあるだろう。 アプリケーションアーキテクチャの複雑化という観点だけではなく、 システムアーキテクチャの複雑化という観点でもフルスタックなフレームワークならではの便利な仕組みを利用することが難しくなる or そもそもフレームワークでサポートするのが難しい ケースが増えてきたように思える。

マイクロフレームワーク

アプリケーションアーキテクチャとシステムアーキテクチャの複雑化によって、 フルスタックなMVCフレームワークをフル活用することが難しいケースが出てきた。 重厚なフレームワークのバージョンアップに追従するのが面倒に感じることすらあるかもしれない。 こういった流れの中でも相変わらず使い続けられる機能が存在する。 例えば URLとControllerのルーティング、CookieCSRFサニタイズのようなWeb(HTTP)の機能である。 そのため、それらの機能のみを提供するマイクロフレームワークを利用する機会が増えている気がしている。 マイクロフレームワークに足りない機能は各種ライブラリを導入することで補うことができるので、 フルスタックフレームワークと同等の機能を有しながら "特定の機能を部分的に捨てやすくなる", "要件に合わせて柔軟に対応できる" という世界観を実現できる。 当然デメリットもあるのでマイクロフレームワークが優れているというわけではないが、 現実的にフルスタックフレームワーク以外の選択肢が出てきたという点は明らかだろう。

改めて質問内容を振り返る

ここで質問内容を振り返ってみる。 質問内容には "RubyにはRailsがあるからフレームワークアーキテクチャに迷わない" という前提があったが、 個人的には現在のWebにおけるアプリケーション開発ではそうでもないと思っている。 以下の2点はRubyであっても気にする必要があるだろう。

  • 本当に Rails が最適なのか?
  • 仮に Rails を採用するとしても、MVC構成をそのまま素直に利用するだけでいいのか?

なので、そもそも現在におけるアプリケーションアーキテクチャ周りは言語に関係なくそれなりに複雑化しており、 "xxxを採用すればOK" という時代ではなくなっているのだと思う。

pospomeが考えるGoのフレームワーク選定

ここではGoのフレームワークについて考える。 "RubyだからといってRailsが最適なフレームワークとは限らない" という言い方をしてきたが、 Goは比較的フレームワークが乱立している印象を受ける。 なので、実際フレームワークには悩むだろうなとは思う。 Goのフレームワークで自分が知っているのは以下である。

  • gin
  • echo
  • chi
  • revel
  • goyave

当然これ以外にもフレームワークは存在するのでちゃんと調べることをおすすめするが、 まず最初に決めるべきはフルスタックフレームワークを利用するのか、マイクロフレームワークを利用するのか、という点である。 上記の例では gin, chi はマイクロフレームワークであり、revel, goyave はフルスタックフレームワークである。 echo はマイクロフレームワークという認識だが、地味に多機能であり、これ1つで必要なものは揃いそうなのでフルスタックフレームワークに分類してもよいかもしれない。

"フルスタックフレームワーク or マイクロフレームワーク" という観点だと、個人的にはマイクロフレームワークを好む。 理由としては以下である。

  1. アプリケーションアーキテクチャとシステムアーキテクチャの複雑化によってフルスタックフレームワークのメリットが薄れつつある。
  2. 部分的に捨てやすいようにしておいた方が中長期的に柔軟なアーキテクチャ戦略を選択できる。
  3. Goっぽい。

上記の1番目と2番めは説明不要だと思うが、3番目の "Goっぽい" というのはよく分からないと思うので説明しておく。 個人的にGoは "Easyな機能を提供して楽に目的を達成するというよりは、Simpleな機能を組み合わせて複雑さを排除して目的を達成する" という思想があると思っている。 Goの言語仕様がシンプルだったり、機能追加に慎重だったりするのはこういった背景があるものだと思っている。 ここらへんはGoが作られた背景に依存するところなので詳しくは説明しないが、 Goを利用する以上そういった思想に乗っかっておいたほうがよいだろーなと思っている。 自分が一緒に働くエンジニアやOSSを提供するエンジニアもこういった思想を持っている可能性が高く、 言語の思想という部分での何かしらのエコシステムが働く可能性に期待していたりする。 (この思想が自分の勘違いだったら元も子もないが・・)

次に具体的にどのフレームワークを選ぶのか? というのを考える必要があるが、 これは選定するタイミングで各種フレームワークをドキュメントベースで調べて、それっぽく要件を満たせそうなものを触ってみて決めるという感じになると思う。 自分はフレームワークを常に追っているわけではないので、使ったことあるフレームワーク以外は全然分からない。 仮に使ったことがあったとしても、それが常に最適な選択肢とは限らないので、その都度調べるというのが必要になると思っている。 そして、意外と忘れがちだが、Goには標準パッケージに net/http というWebサーバ実装がある。 必要最低限の機能しか提供していないが、十分プロダクトで利用できるので、net/http も選択肢に入るだろう。

自分はマイクロフレームワークが好みということもあり、 具体的なフレームワークでも net/http や chi のような必要最低限の機能を提供するものを好む傾向にある。 なので、こういった薄いやつをピックアップして、それぞれちょっと使ってみて・・・という感じになると思う。 ただ、要件によっては CSRF, Sesson, StaticFile 周りが一通り揃っている echo を利用するかもしれない。

しかし、何度も言うが最終的には "要件次第" である。 例えばモノリスなWebアプリケーションを作るのであれば、 「マイクロフレームワークよりも色々揃っているフルスタックフレームワークを使った方が良さそう」という判断になるかもしれない。 最終的にどれを選ぶかはエンジニアの腕の見せ所である。

pospomeが考えるGoのアーキテクチャ選定

ここではGoのアーキテクチャについて考える。 質問内容が "モダンなアーキテクチャはなにか?" というものだったので、 "モダン" という点でいうと、クリーンアーキテクチャのような何かしらのレイヤ構造を採用することになると思う。 ただ、レイヤ構造にしろ、MVCにしろ、それらの具体的なメリデメを理解し、使いこなせなければ、コードの品質は下がってしまう。 なので、個人的にはモダンかどうかは関係なくて、自分のスキル、チームメンバーのスキル、アプリケーションの要件に沿ったアーキテクチャを選定するのが良いかなと思っている。 例えそれがトランザクションスクリプトであっても、選定理由に妥当性があれば問題ない。 アプリケーションアーキテクチャは最初に決めたものが中長期的に正しいということはほぼないので、 どっちみち状況に合わせて随時軌道修正する必要がある。 そう考えると "アーキテクチャの思想をチームメンバーに浸透させる" とか "アーキテクチャを考えた人が退職しないようにする" といった活動の方がよほど重要である。

相談に対する回答

色々書いてきたが質問の回答としては「まずは要件を教えて下さい」というところから始めることになる。 なので、質問する側は採用するフレームワークアーキテクチャを決めて、 「xxx という理由でフレームワークは xxx にしようと思います。アーキテクチャは yyy という理由で yyy にしようと思います。どうでしょうか?」という具体的な質問をしてもらえると嬉しい。 こういった質問をしてもらえれば「xxx はやめたほうがいいかもしれない」というアドバイスくらいはできると思う。 そして、選定理由が妥当であればフレームワークがginであっても、echoであっても、アーキテクチャMVCであっても、クリーンアーキテクチャであっても問題ないと自分は思っている。 相談する相手が部外者である場合、中長期的に質問者をサポートできるとは限らないので、スポットで自分の意見を強く主張しても意味がない。 その主張を踏襲したアーキテクチャ戦略を中長期的に維持できる可能性は低いと思うので、 その人の意見を尊重した方が良いと思っている。

ちなみに、他人のおすすめを真に受けて脳死状態でフレームワークアーキテクチャを決めると、 開発や運用を通じて発生した問題に対する最適な解決策を考えることができなくなる可能性がある。 なぜかというと「技術選定時は xxx という理由で xxx を選択したが、今は yyy が問題になっているので技術選定時の xxx という前提が間違っていた可能性がある。なので、xxx ではなく zzz という方針に切り替えよう」という考え方ができないからである。 やはりアプリケーションアーキテクチャにおいて思想や方向性といったものは重要である。 相談する相手の回答はそれっぽく聞こえるかもしれないが、ちゃんと自分でも考えるのが重要である。

まとめ

最終的に "要件による" という当たり障りない回答になってしまいました。 ただ、これはフレームワークアーキテクチャに限らないかなと思っています。 ソフトウェア開発において絶対的な正解が存在しないケースというのは多々あるので、 質問する側も回答する側もそこの認識を合わせて議論できると良いなーと思っています。

宣伝

DMMでチーム作りました。 "マイクロサービス x 認証基盤 x ゼロトラストネットワーク x DDD" あたりに興味ある方は是非話を聞きに来てください。

bosyu.me