morimorihogeです。ちょっと色々忙しくて死んでますが、深夜の勢いで書いてみます。
ことの起こり
Twitterにてこんな発言を見かけました
この2017年の記事だと、RoRのテーブルのdatetime型にはatをdate型にはonを付けようと書いてあるけど、APIでカラムのデータを返す場合、フロント側としてはdateというサフィックスが付いていた方がやりやすいという意見があって、この辺のベストな感じが気になったhttps://t.co/8MMMHlFvGx
— yotuba@Railsエンジニア (@yotuba_eng) July 1, 2021
元記事(翻訳)はこちら
本件について、Twitterではreplyしてみたのですが、文字数の都合で詳細に書きづらいということもあり、一度自分の意見をまとめてみようということで記事に考えをまとめてみました。
※本記事の内容はあくまで僕の意見であり、社内エンジニア陣の共通見解というわけではありません。この手の設計思想問題はちょいちょい社内でも衝突することがありますし、その際は都度議論しながらプロジェクトごとにコンセンサスを取っています。
そもそも「バックエンド」の中でも命名規則は衝突している
さて、ここで皆さん大好きGitHub GraphQL APIを見てみましょう。
GitHubは言わずと知れたRails backendでいい感じに複雑なこういったケースを議論するのには最適な見本の一つだと思います。
基本的な命名規則については日本語でざっと解説してくれている記事がありましたので詳細はそちらを参考にしてみてください。
さて、では実際の実装としてはどうなっているでしょうか?ここでは分かりやすいということでDateTime型なフィールドに着目してみましょう。
- Commitオブジェクトの仕様を眺めてみると、
authoredDate
やcommittedDate
といったdate
suffixなフィールドがあります(Rails命名規則通りでない) - CommitCommentオブジェクトの仕様を眺めてみると、
lastEditedAt
やpublishedAt
といったat
suffixなフィールドがあります(on Rails way)
あれあれ?天下のGitHubでも命名が統一されていませんね 🤔
果たしてこのCommit.xxxDate
などのdate
suffixはRailsの命名規約違反で良くない仕様なのでしょうか?あるいはCommitComment.publishedAt
などのat
suffixはフロントエンド向けの名前が表記揺れしていて良くない仕様なのでしょうか?考察してみましょう。
※以降の考察は僕が勝手に推察したもので、GitHubの中の人に聞いた情報や公式情報ではありません。鵜呑みにしないで下さい🙏
その名前はどのシステムが命名したものなのか?
そもそもGitHubは今ではソフトウェア開発に関する何でもサービスみたいになっていますが、名前の通り裏ではGitが使われているのは周知の事実かと思います。そして、Gitにはcommitに対してauthor dateとcommit dateという概念があります。
ここではGitの用語としてこれらのDateがあるということが重要なので、この二つがそれぞれ何を意味するのかという話については以下の記事を参照してみてください。
ここで重要なのはこれら二つのDateはGitが命名・作成したもので、GitHub側のバックエンドRailsアプリが命名・作成したものではないということです。
Gitが命名・作成した用語については基本そのままの用語でAPIに出す方が、システムを一気通貫して同じ用語を使うことができるという点で設計上のメリットがあります。なのでここはdateという名前がそのままAPIに出ているのですね(あくまで推測です)。
それでも意味を分かりやすくするために少し命名を弄ることもある
それならそのままのGit用語をAPIにも出しているのかというと、実はそうでもありません(ここがやや複雑)。
もう一度よく見てみましょう。
- Git用語:author date, commiter date
- GitHub API: authoredDate, committedDate
はい。author -> authored、commiter -> committedの名前置き換えがありますね。これらはGit用語ではなくGitHub用語に変更されています。
これは完全な推察になりますが、GitオブジェクトのデータをGitHub側のシステムに取り込む際にGit用語そのままだとやや意味が不明確なので、より明確な命名にしようとして一捻りしたのではないかと思います。
こうしたシステム間結合時の再命名は、意味的には利用システムにとって分かりやすくなりますが、その分システムを跨いだときに一つのものが複数の名前を持ってしまうというシステム横断の表記揺れ問題を引き起こします。カオス。
ここまでの内容で各システム用語対応の現状をまとめてみましょう。
Git用語 | GitHubシステム内部用語(憶測) | GitHub GraphQL API |
---|---|---|
author date | authored_date | authoredDate |
committer date | committed_date | committedDate |
at
系フィールドも合わせて見てみる
さて、では次にat
で終わる系フィールドも同じ表にまとめてみます。CommitComment.publishedAt
についてはそもそも(既に存在するコミットに後付するという意味での)コミットコメントという仕様自体がGitにはないので、GitHubが命名したものだと推測できます。
Git用語 | GitHubシステム内部用語(推測) | GitHub GraphQL API |
---|---|---|
author date | authored_date | authoredDate |
committer date | committed_date | committedDate |
- | published_at | publishedAt |
- | last_edited_at | lastEditedAt |
さて、これで現在の命名について整理ができました。
もし外部APIフィールド名をdate suffixに統一すると?
こうしてみてみると、この対応表は以下のようにまとめられるのではないかと思います。
- GraphQL Schemaだけ見てみると、dateとatの表記揺れがあってちょっと気持ち悪い
- GitHubのシステム内部用語がそのままGraphQL APIに出ているので、そこの対応は分かりやすい
- Git用語が微妙にGitHub用語に変換されているが、意味的に分かりやすくするレベルの差に留まる
では、もしこれを元Tweetにあったように「フロント側から分かりやすいようにdate suffixに統一」してみるとどうなるでしょうか?
Git用語 | GitHubシステム内部用語(推測) | GitHub GraphQL API |
---|---|---|
author date | authored_date | authoredDate |
committer date | committed_date | committedDate |
- | published_at | publishedDate |
- | last_edited_at | lastEditedDate |
ちょっとわかりにくいので、全部snake_caseに直して単語レベルだけで比較してみます。
現行のGitHub APIでは以下の通りです。
Git用語 | GitHubシステム内部用語(推測) | GitHub GraphQL API |
---|---|---|
author_date | authored_date | (←と同じ) |
committer_date | committed_date | (←と同じ) |
- | published_at | (←と同じ) |
- | last_edited_at | (←と同じ) |
date suffix変換を噛ますとこうなります
Git用語 | GitHubシステム内部用語(推測) | GitHub GraphQL API |
---|---|---|
author_date | authored_date | (←と同じ) |
committer_date | committed_date | (←と同じ) |
- | published_at | published_date |
- | last_edited_at | last_edited_date |
はい。GraphQL APIを見るフロントエンド側からは分かりやすくなりましたが、システム全体での用語統一という観点では名前が増えました。
このようにフロントエンドとバックエンドで違う名前を使うようになると、お互いの使う用語が異なるので仕様のやり取りをする際常に用語管理テーブルを挟まねばならず、脳のリソースを持って行かれてしまいます。
GitHub APIの仕様から推測した命名規則
というわけで、ここまでのことから推測できるGitHub(及びGitHub API)の命名規約をまとめてみると、システム全体として、
- GitHubバックエンドアプリが作成し管理するフィールドについてはRailsの命名規約に従う
- 外部のシステム(Git等)が扱うデータを取り込んで利用するフィールドについては外部のシステム側の命名規約に従う。ただし、意味的に分かりにくいものを多少修正することはある
- (GraphQL)APIにはGitHubバックエンドアプリから見える名前をそのまま利用する
というポリシで命名しているのではないかという推測が立てられました。
また、ここまでの中では挙げてきませんでしたが、そもそも今回のGraphQLのようなスキーマを持つAPIの場合、命名のうちデータ型を想起させる部分の単語については型の定義情報を合わせて見ればDateTimeと分かるんだからいいだろうという事情もありそうです。
この設計思想のバランス感覚、僕は割と良い落とし所ではないかと感じました。自分が設計するときも参考にしたい。
まとめ的なもの
結局の所この辺りは設計上のトレードオフで、唯一の正しい正解がない中でbetterなものをプロジェクトごとに探す、ということなのではないかと思います。
こういった複数システム横断するものを設計するときは、一部だけを切り出して見たときの整合性に着目するのではなく、全体として見たときに設計思想が統一されているかを重視するのが大事です。
今回、フロント側だけの視点では「suffixがdateに統一されていないのは使いにくい・不整合だ」と感じたのかもしれませんが、システム全体としては date
suffixとat
suffixが混じっている方が統一されているという見方もできるわけです。
もしどうしても使っているフレームワークなどの事情から自分達に合わせた命名を使いたいということであれば、それはそのシステムの中で変換を噛ますという手もあります。RailsならActiveSupport#alias_attribute
がありますし、他の言語・フレームワークでもこの手の外部APIと内部APIの命名をbridgeするような機能は大抵備えているでしょう(最悪setter/getterを作れば良い)。
そもそもAPIというもの自体異なるシステム間を繋ぐものなので、違う言語やフレームワーク同士を繋ぐ以上ある程度規則上の不整合はどうしても発生します。こういったときに「ここがこうなってるのはこういう事情によるものです」と納得のいく説明ができるかが設計上大事な部分であり、こういったものを積み重ねたものが「設計思想」になっていくのだと僕は思います。
ここからが本当の地獄だ(ゴゴゴゴ・・・・・
うん、きれいに説明できたな、ヨシ!で終わっても良かったのですが、さらなるカオスをチラ見せします。
GitHub APIのCommitオブジェクトにはpushedDate
というものが存在します。
これまでの解説に従うと「GitHub側で付けた命名ならat suffixのはずだから、これはGitのプログラム内用語かな?」という推測になるのですが、Git自体にpushed_dateという概念はありません。ナゼダー 😇😇😇😇😇
そもそもGitにおけるpushはリポジトリ間でコミットやブランチを転送するコマンドであり、いつ転送したかという情報はGitの興味の対象ではない、という事情からだと思われます。
元々Gitそのものは分散型リポジトリであり単一のoriginサーバーを前提としていないこともありますし、pushed_dateをもしGitデータ構造の中に持ってしまった場合、各所のリポジトリによってpushed_dateが違うとバイナリレベルでの互換性が保てない(保持しているpushed dateだけが違うリポジトリ間でpushしたときにconflictしてしまう)などの問題が発生することが予想されるため、Gitそのものがpushed dateを保持しないのは設計思想上そういうものだと思って良いでしょう。
ちなみにこの問題に説明を付ける候補は僕の中に数個あるのですが、どれも憶測でしかないこともあり、ここではあえて解説しないことにします。
こうした「答えがない中でbetterな正解を探す」のは良い頭の訓練になったり、チーム内の価値観すり合わせに繋がるので皆さんの開発チームなどでZoom飲み会の話題にしてみてもらってもいいかもしれません。
ではでは、色々書きたいことが他にもあるのですがとりあえず今は仕事に戻ることとします。
さらに細かい話をするならば、commiter dateについてはGitの中でも微妙に表記揺れがあり、commit.hなどのソースコード上は**commit** dateですが、環境変数で上書きする際にはGIT_COMMITTER_DATEだったりします。ここではGitが外部向けに公開している(と推察される)名前のcommitter dateの方を正として採用しています