Go言語でWebアプリを作りかけて辞めた話

3行で要約

  • golangでwebアプリを作り始めた
  • golangは好きだけど、今回求める要件に合わないことに気付いた
  • 途中まで書いたコードを捨ててRailsで書き直した

私のGoスキル

背景

とあるtoB向け受発注のWebアプリを作ることにしました。
開発者は私1人。私が目指した基準は以下のものです。

  • 少なくとも3年はメンテできること
  • 未来の自分(他人)が読んでも理解できること
    • このアプリとガッツリ付き合う予定ではなく、出来れば保守は違う人に回したいし機能追加とかも控えめにしたい(重要)
  • デプロイとか運用の手間を簡単にしたい
  • ついでに、学んでおくことが有益になりそうな言語がいい

これらを踏まえ、以下の理由からGoを選択しました。

  • シングルバイナリでデプロイ出来る
  • 大体のことは標準ライブラリで出来るイメージ
  • フォーマットとか強制されるのが良い
  • 静的型付けによる安心感
  • Go書いてみたい

選択したフレームワーク

Webフレームワーク

Revelなどのフルスタックフレームワークは選択肢から外しました。

  • シンプルな受発注(CRUD)なのでフルスタックの必要性を感じなかった
    • これは後に読み間違いと気付く
  • Revelがどこまで活発に開発続くか分からない
    • 開発が止まるor人気がなくなると使える人が少なくなるのでメンテ性が下がる
  • Go使っているのに少なからずDSLを覚えないといけないことに抵抗があった

「なるべくnet/httpの使い方から外れないシンプルなやつ」ということでGinを選びました。
Echoとかも試しましたが好みでGinにしました。

O/Rマッパー

Webフレームワークと同様でDSLを覚えるつもりはありませんでした。
数年後も使われているか分からないライブラリのDSLを覚えるのはきつい。
かつマイグレーションの機能とかも不要で、単純にレコードを構造体にマッピングしてもらえれば十分でした。

gormは色々できるし途中まで使っていたけど辞めました。
gormに限らないが、吐かれるSQLを確認しながら書くのが本末転倒な感じだったので。

これも薄いラッパーということでsqlxを採用しました。

ちなみに以前は「マイグレーション機能っていいな」と思っていましたが、ここ数年で考えが変わりました。
ツールとしてのマイグレーションってそんな必要なのでしょうか。
upについてはalterとかの差分のsqlファイルがあれば十分ですし、downについては今のところ使った記憶がありません。
開発者間/デプロイ環境間でスキーマのversionが異なるような時に便利なのかもしれませんが、そういう開発案件に携わったことがなく。

マイグレーションに限りませんが、昔は「このツール便利!」と歓喜して導入しようとしていたのが今は「便利だけど覚えるコストと負債になり得るリスクを考慮するとどうだろう」と考えるようになってきました。
若手の頃に嫌だった「保守的なエンジニア」になりつつあるのかな…とちょっと心配になります。保守的になると新しい技術を覚える意欲を失いそうで。

ライブラリ管理

glideを採用。
これはちょっと採用理由を忘れました…。

その他

システム上必要なライブラリ(go-sql-driver/mysqlとか)以外は入れていません。
テンプレートも標準のtemplateで済ませています。凝った画面ではないので十分です。

Goで書き始めた感想

良かった

  • 良い
    • ライブラリのコードが読みやすい
      • 階層がほとんどフラットだし、importや呼び出しの名前空間が明示的なのも良い
      • 「この関数どこで定義されているんだ」みたいな余計な手間がない
    • 書いていてストレスがほとんどなかった
    • go fmtなども含めて標準ライブラリ/ツールで済むのが良かった
    • シングルバイナリは良い
    • 静的型付けは良い
    • テストコードが同階層にあるのは意外と読みやすかった
  • 良くない
    • テストコードでassertはやっぱ欲しい
    • エラー処理が面倒

私にとってGoは書きやすいし読みやすいものでした。
「書きやすい」 = 「簡単に書ける」ではありません。
「書きやすい」 = 「書き方に迷わない(迷うことが少ない)」です。

コレクション操作などxx言語ならもっと短く簡潔に書けるのに、というケースはありますが不満は感じませんでした。
簡単に書ける言語だと書き方に迷うことがあるし、レビューでも「こう書いた方がシンプル」みたいな横道にそれがちです。
(今回はレビューする/されることはないので関係ありませんが)

同じ名前空間でもファイルが分かれていることに最初は「うっ」となりましたが、慣れたら気にならなくなりました。

不満としては、テストコードでassert系はやはり欲しいと感じました。
私が手を抜いているのか、テストでは基本的に「AとBが一致しませんでした。Aはこの値でBはこの値です」みたいなメッセージが表示できれば十分です。
これを毎回Errorf(...)で書くのはDRYじゃありませんし単純に面倒です。

また、ここ数年ずっとスクリプト言語で過ごしてきた私にはエラー処理はちょっと面倒でした。慣れだと思います。

そんなわけですごく良い感じにコードを書いていたのですが、ちょっと要件に合わない選択をしたことに気付きました。

GoでWebアプリを作る上で足りていないところ

主にセキュリティ周りの考慮が漏れていました。
CSRFの対策とかid/pass方式の認証とか二重サブミット防止とか。
revelだとcsrfプラグインrevel-csrfというのがあるそうですが、ちゃんと調べていません。二重サブミットとかどうなっているんだろう。

システム要件上id/passの認証が必要なのですがginでは対応していません。
ginも他のフレームワークBasic認証やOAuthは対応しているのですが。
さすがにこれらを自前で実装するのは再発明が過ぎます。

また細かいところでは、例えば「環境変数(production/develop/testとか)ごとに処理を切り替えたい」とかの処理も必要でした。
典型的なところではDBの接続先を切り替えることです。
環境変数を取ってきたりgin.Mode()から取り出したり出来ることは出来るんですが、自分で書いていくと結局オレオレフレームワークへ近付きます。

ここに至り、最初の基準で設けた「シンプルに」「DSLに依存したくない」というのは諦めました。
システムの機能がシンプルでも開発環境や非機能要件では色々必要だった、という見積が抜けていました。
とはいえ私の調査不足なだけで実は解決案は転がっているのかもしれません。

開発初期のスピード感と対応範囲、情報量および開発者の多さから、選ばれたのはRailsでした。
当初の目論見と真逆です。

GoでWebアプリを作りかけた感想まとめ

  • Goは心地よい(not楽しい)
  • (今の自分が)Goで何か作るならバックエンドかバッチか認証や更新操作が必要ないWebアプリ
  • 新しい言語をやる時はやはりその道の先輩がいないと相談できなくて厳しい