背景
プログラミングスクールや夜間インターンで共同開発していた時、Gitの詳しいメカニズムがちっとも理解できずに苦しんでいました。
通常は、実装をある程度理解した上で、モデリングや設計に入るというのが、多くのケースらしいのですが、私の場合には、その逆であることが大多数のため、学習の順番を入れ替えていました。
モデルのマージ
IT2社目にいた頃、先輩と分業してモデルを作成し、運用していたのですが、
マージの際の度重なるコンクリフト
最新のモデルが反映されない。
それどころか、消えてしまうので手作業で更新
などのトラブルが多発していました。
工夫
そこでマイクロサービスのサーガパターンを深く理解した上で、Gitのプロセスに触れてみました。
その結果、非常に似通ったメカニズムであると感じたので、それをここに残します。
Gitとトランザクション管理の類似性
両者の根底には
データセットに対する変更を、一貫性を保ちながら管理する
という共通の思想があります。
Gitのマージは、特にデータベースのオプティミスティック同時実行制御の考え方と非常によく似ています。
ACID特性とのアナロジー
データベーストランザクションの信頼性を保証するACID特性と、Gitのマージには多くの共通点があります。
原子性 (Atomicity)
DB:トランザクションは、「全て成功」するか「全て失敗」するかのどちらかです。
Git:マージは、成功して新しい「マージコミット」が作られるか、コンフリクト(競合)して中断されるかのどちらかで、中途半端にマージされた状態で終わることはありません。
一貫性 (Consistency)
DB:トランザクションは、データベースをある一貫した状態から、別の一貫した状態へと遷移させます。
Git:マージが成功すると、リポジトリは新しい有効なコミット状態へと遷移します。
独立性 (Isolation)
DB:複数のトランザクションが、互いに干渉することなく同時に実行できるように見えます。
Git:開発者は、ブランチという形で、他の開発者の変更から隔離された環境で作業を進めることができます。マージは、その隔離された作業を統合するプロセスです。
永続性 (Durability)
DB:コミットされたトランザクションは、システム障害が起きても失われません。
Git:マージコミットが成功すれば、その変更はリポジトリの履歴に恒久的に記録されます。
決定的な違い
一方で、その目的とアーキテクチャの違いから、決定的な差異も存在します。
分散 vs. 集中
DB:通常、単一の中央サーバーがトランザクションを管理します。
Git:各開発者のローカルリポジトリでマージやコミットが完結する分散型のシステムです。
競合の解決方法(手動 vs. 自動)
DB:トランザクションの競合が検知された場合、通常は自動的にどちらかのトランザクションがロールバック(棄却)され、アプリケーションが再試行します。
Git:マージコンフリクトが発生した場合、Gitは処理を停止し、開発者による手動での解決を要求します。
コードの意味を理解し、論理的な矛盾を解決するのは人間にしかできないためです。
これがまさに、わたしが先輩とモデルのマージする際に、トラブってたものです。
対象(履歴 vs. 現在の状態)
DB:主に現在のデータの状態を管理します。
Git:変更の歴史(コミットグラフ) そのものを管理します。
「いや、もうこれイベントソーシングじゃん」っていう風に思いますよね?
Gitの経験が、サーガの理解を助ける
多くの開発者は、日々の業務でGitを使い、分散システムにおける整合性の問題を、実はすでに体感しています。
わたしのように、抽象から入った方が体にしみこみやすいという人は、
先にサーガのメカニズムから入ってみてください。
ブランチの作成 (git checkout -b feature)
これは、一連の作業を隔離された環境で始めることであり、サーガトランザクションの開始に相当します。
ブランチへのコミット
ブランチ内での個々のコミットは、サーガを構成するローカルトランザクション(例:「注文の作成」「決済の承認」)が成功した状態と見なせます。
マージ (git merge main)
隔離された作業(ブランチ)を、本流(main)に統合する行為です。
これは、サーガにおける全てのローカルトランザクションが成功し、
ビジネスプロセス全体を最終的に確定(コミット)
させることに相当します。
リバートコミット (git revert)
これが最も重要なアナロジーだと思います。
一度マージしてしまった変更を取り消すために、変更を打ち消すための新しいコミットを作成します。
これは、失敗した処理の影響を取り除くために実行される、サーガの 「補償トランザクション」 の考え方そのものです。
ここまでのまとめ
サーガの知識があれば、Gitの操作を単なるファイル管理ではなく、分散された状態を管理するための高度なトランザクション制御のメカニズムとして、より深く理解できます。
両者は
分散環境で、いかにして一貫性を保ちながら変更を統合するか?
という同じ課題に取り組む、いわば概念的な仲間です。
どちらを先に学んだとしても、ぶっちゃけ鶏が先か卵が先か問題ではないでしょうか?
片方の深い理解は、もう一方をマスターするための強力な土台となります。
これは、
モデリングや設計が先か、実装での経験が先か?
問題と一緒だと感じます。
新しい問い
さて、概念がほぼ一緒ということは、
サーガの仕組みと一貫させて、Gitの仕組みに一定のガバナンスを導入できれば、
本来のビジネスプロセスと矛盾したようなマージプロセスが実行されてしまうなどのリスクを防ぎやすくない?
と考えられませんか?
ここにもしも、一貫性がないと、保守がめっちゃしにくいことは自明です。
GitOps
上記は、
「GitOps」「DevSecOps」
といった、現代的なソフトウェア開発プロセスの核心部分だそうです。
なぜガバナンスが必要か
デフォルト状態のGitは、非常に自由なツールです。
誰でも、いつでも、どんな状態のコードでもマージできてしまいます。
これは、ビジネスプロセスで言えば、誰でも、どんな承認もなしに、取引を「確定」できてしまうようなもの。
もしくは、悪用者であっても、何の承認もなく取引し、しかもそれが検知できないみたいなもの。
この自由さが、バグやセキュリティ脆弱性、ビジネスロジックの破壊といった問題を引き起こす原因となります。
サーガの思想を応用したGitガバナンス
サーガがオーケストレーターによって制御されるように、Gitのマージプロセスも、CI/CDパイプラインという自動化された仕組みによって制御(統治)します。
1. mainブランチの保護(トランザクション境界の定義)
仕組み
GitHubやGitLab上で、mainブランチを保護ブランチに設定します。
これにより、開発者はmainブランチに直接コードを書き込む(push)ことができなくなります。
サーガとの対応
これが、変更における 「トランザクションの境界」 を定義する行為です。
全ての変更は、プルリクエスト(マージリクエスト)という、ただ一つの公式なルートを通らなければなりません。
2. CIパイプラインによる「事前チェック」(サーガの各ステップ検証)
仕組み
プルリクエストが作成されると、CI(継続的インテグレーション)パイプラインが自動的にトリガーされます。
このパイプラインは、提案された変更が安全であるかを検証する一連のチェックを実行します。
・ユニットテスト:コードの基本的な動作を検証。
・静的解析:コーディング規約違反や潜在的なバグを検出。
・セキュリティスキャン:既知の脆弱性がないかをスキャン。
サーガとの対応
これは、サーガの各ローカルトランザクションが成功可能か 事前検証 するステップに相当します。一つでもチェックが失敗すれば、マージは自動的にブロックされます。
つまり、サーガの失敗を意味します。
3. コードレビューによる「承認」(ビジネスルールの検証)
仕組み
1人以上の他の開発者がコードレビューを行い、
変更を承認しなければマージできない
というルールを強制します。
サーガとの対応
自動化されたテストでは見抜けない、ビジネスロジック上の矛盾や設計上の問題がないかを、人間の専門家が検証するステップです。
4. マージ後のCDパイプライン(補償の準備)
仕組み
マージ後、CD(継続的デリバリー)パイプラインが動き、ステージング環境へのデプロイや、より大規模な結合テストを実行します。
もしここで問題が発見されれば、マージを取り消す 「リバート」 を即座に行える体制を整えておきます。
サーガとの対応
万が一、間違った取引が確定してしまった場合に、それを取り消す 「補償トランザクション」 に相当します。
リバートと補償トランザクション
この2つは、単なるアナロジー(類似)というよりも、思想的にほぼ同一のものと言えます。
どちらも、
過去の歴史を改ざんするのではなく、間違いを打ち消すための新しい操作を追加することで、全体として正しい状態に戻す
という共通の哲学に基づいています。
共通する思想:「過去を消さずに、打ち消す」
補償トランザクション
目的
完了してしまった取引(例:「口座Aから1万円引き落とし」)の影響を、ビジネス的に取り消したい。
手段
過去の取引履歴を削除するのではなく、「口座Aに1万円入金する」という逆の取引(新しいトランザクション)を追加します。
結果
「引き落とし」と「入金」の両方の履歴が残り、なぜ残高が元に戻ったのかという経緯が完全に追跡可能です。
git revert
目的
完了してしまったコミット(例:「機能Xを追加」)の影響をコードベースから取消したい。
手段
過去のコミットをgit resetのように歴史から消すのではなく、
機能Xの追加を、全て元に戻す
という内容の新しいコミットを追加します。
結果
「機能Xを追加した」というコミットと、「機能Xの追加を取り消した」という両方のコミットが歴史に残り、なぜコードが元に戻ったのかという経緯が完全に追跡可能です。
なぜこのアプローチが重要なのか
この「打ち消すための新しい操作を追加する」というアプローチは、開発プロセス中の
監査可能性と追跡可能性
を保証します。
もし過去の履歴そのものを削除・改変してしまうと、「なぜ現在の状態になったのか」という重要な文脈が失われてしまいます。
補償トランザクションとgit revertは、何が起きて、どう修正されたのかという
「正直な唯一の歴史」 を全て記録に残すことで、システムの信頼性と健全性を保っています。
イベントソーシングの設計思想ですね。
しかも、開発プロセスの健康状態の透明性にも繋がりますよね。
まとめ
この一連のガバナンスを導入することで、Gitのマージプロセスは、単なるコードの統合から、ビジネス要件、品質、セキュリティが保証された変更のみを受け入れる、信頼性の高いトランザクション処理へと進化させられます。
分散トランザクションの必要な現場では、特に必須で取り入れてください。
あと、先にあるべきアーキテクチャの形をデザインしてから、
Gitのマージプロセスを設計しておくべきだったな💦と反省しています。
もしもあの時にすぐにこれに気づけていたら、さらに改善サイクルを回せていたでしょうね。
使い慣れたGit運用ベースでは、その行動習慣の制約によって、アーキテクチャも制限されるでしょう。
先に常にあるべきアーキテクチャの姿をラフスケッチでもいいから描き、
それといまのGit運用が一貫してるか、チェックしてください。