#fukabori をきいて Value Object と Value Object パターンについて頭の中を整理

連休の余韻も楽しんだので今日から散歩を再開した。ちょっと前までは「陽の光を浴びなきゃ!」と思って3時過ぎにウロウロしてたけど、これからはもうちょっと涼しい時間帯がいいなと思って、夕暮れ時に散歩しながら fukabori.fm を聴いてた。Value Object のお話。面白いなぁ

kumagi さんの記事はこちら

お絵描き

PoEAA や DDD はだいぶ前に読んだことがあるけど、Value Object を雰囲気で捉えてるからちゃんと見直しておこうと思って、調べたりしながら絵を描いた。こういうことなのかな?

(絵をかくほどでもなかった・・・

Value Object とは?

kumagi さんも書いてる通り

https://martinfowler.com/bliki/ValueObject.html より

Objects that are equal due to the value of their properties, in this case their x and y coordinates, are called value objects.

「プロパティの値で等価比較されるオブジェクト」

Value Object の定義。だから、言語依存の実装のイミュータブルにするみたいな話や、型を持たせたりロジックを持たせると便利って話は、その外側の「Value Object をどう使うか」という話であって 、Value Object 自体ではない

合成物でなければならないか?

ところで、ポッドキャストや kumagi さんのブログの中で

Value Objectの出発点はそもそもcompound(合成物)であって単一の値を包めとは一言も言ってない

ってあるけど、単一の値でも「プロパティの値で等価比較される」という定義を満たすのであれば Value Object として良いと僕は思っている

例えば Vaughn Vernon は「実践ドメイン駆動」の中で、String 型をラッピングした Value Object を使用してユビキタス言語を表現している

"6.1 値の特徴" より

このname をString 型からThingName 型に変更した。

たしかに出発点は合成物だったけど、定義から考えると単一の値でも問題なさそう

Value Object パターン

さて、Eric Evans のドメイン駆動設計

に「値オブジェクト(VALUE OBJECTS)」という節がある。そこには「Value Object を使用したモデリング」について書かれていて

  • 値による同一性

という Value Object の定義以外にも

  • 不変なものとして扱うこと(必須ではない)

というテクニックが書かれている(「実践ドメイン駆動」にはさらに、型安全の利用についてや、副作用のない振る舞いなどについても書かれている)

そして、この書籍はパターン集であり「値オブジェクト(VALUE OBJECTS)」はパターンの名前になっている

だから「Value Object を利用したモデリング」=「値オブジェクト(VALUE OBJECTS)」と呼ぶことができる

なんか違うパターン名にしてほしかった!

話の元

記事でもポッドキャストでも触れてないけど、まぁ、たぶんこの話の発端は、この書籍かな

gihyo.jp

"3.4.2 値オブジェクト" に、こう書いてある:

値オブジェクト(Value Object)とは、値をクラス(型)として表現する設計パターンです。

それと、こちらもかな?

gihyo.jp

Chapter 1 の "「値」を扱うための専用のクラスを作る" に、こう書いてある:

値の種類ごとに専用の型を用意するとコードが安定し、コードの意図が明確になります。このように、値を扱うための専用クラスを作るやり方を値オブジェクト(Value Object)と呼びます。

見て分かる通り、この2冊の書籍では「DDD の Value Object パターン」のことを指している。これを「Value Object 自体」のことを言ってるつもりで聞くと「広すぎっ!」ってなる

だから、書籍の記載は間違ってないんだけど、ただ、個人的には、あんまり勘違いがないように、↓これくらいの表現をするのが好きかなぁ

「値オブジェクトパターンとは、値オブジェクト(Value Object)を使用して、値をクラス(型)として表現する設計パターンです」

ちなみに「実践ドメイン駆動」の中では「値オブジェクトを"使って"モデリングする」とは言ってるけど「このような実装パターンのことを値オブジェクトと呼ぶ」とはたぶん言ってなくて、好きな感じ

まとめ

Value Object って言葉が出てきたらそれが構成要素としての Value Objectのことを言ってるのか、それともパターンとしての Value Object のことを言ってるのか、気をつけて聞こうと思った

あと、自分がしゃべるときは、周りの人が混乱しないように構成要素として扱っておいて、パターンのことを言うときには「Value Object パターン」って言っておこうかなと思った

それともうひとつ。Value Object の定義の違いの話と、Value Object を多用した設計パターンの好き嫌いの話は、別の話だから、区別して聞こうと思うのだった

面白かった。勉強になったー

存在じゃなくて目的から名前を設計するのだー! #ミノ駆動本

かなり良かった

「良いコード/悪いコードで学ぶ設計入門」を読んだ。読む前は特に書くつもりはなかったんだけど、かなり良かったからブログを書くことにした

gihyo.jp

どういう本?

この本は、読みやすくて変更しやすいコードの書き方と設計についての入門書

どのようなコードが悪いコードなのか、そこにどういう問題があるのか。それに対してオブジェクト指向設計で、どのように設計してコードを書けば、より良いコードを書くことができるのかを、分かりやすい例でコードを追いかけながら、学ぶことができる

読むと良さそうな人

2,3年目くらいで「もっと良いコードを書きたい!」とか「どんなコードが良いコードなんだろう?」って考えてる人や

もうちょっと経験があって、機能追加や改修をするときに苦労をしてきて「どんな風に作ればこの苦労を減らすことができるんだろう?」って悩んでる人には、特におすすめ

それ以外でも、今はコードを書くよりもマネージメントをしてる、って人が知識のアップデートをするのにも良さそう。そうすると、チームの誰かが「プロダクトを改善するのに挑戦してみたい!」って言ったときに、サポートしやすそう

ミドルからシニアなソフトウェアエンジニアは、自分の中にあるコードの書き方や設計の考え方との差分を楽しみながら読めば良いと思う

頭の中を見せてくれてるのがありがたい

読みやすくて変更しやすいコードを書くための設計についてめちゃくちゃ考えて実践してる人が、分かりやすくそのノウハウを共有してくれてるのが、とてもありがたい

「あの人の頭の中を開けて見てみたいわー」ってのを実際にさせてもらってる感じ

例が分かりやすい

コードのサンプルがゲームなので、自分にとって新鮮で楽しく読めた。そして、例の内容もコンパクトで要点が分かりやすい。ゲームの例だけじゃなくて EC サイトの例もあるので、飽きずに読み進めることができた

それに、文章も分かりやすくて読みやすい

推し章:第16章 設計を妨げる開発プロセスとの戦い

僕の推し章を紹介しておく。いきなり終わりの方の章なんだけど、この章がいちばん好き

この本は、最初からすごい勢いで「悪魔だー!」とか「退治だー!」とか言ってるから、僕は内心(著者のミノ駆動さんは、実際の現場でどう向き合ってるんだろう?悪いコードを見かけたらすごい勢いで撲滅しようとしてるのかなぁ?でも、そんなの周りがしんどいだけだよなぁ?)とか思いながら読んでたのだけど、この章に答えが書いてあった

そんなことはやってない。よかった。欲を言えば、このことをもっと早く知りたかったw

この章では、そういった悪いコードが生み出される背景となっている開発プロセスに目を向けていて、その開発プロセスをどのように改善すると、より良いコードを生み出せる場になるか、また、どんな風にチームのスキルをあげていくか、について紹介している

Fearless Change を思い出させてくれる感じ

参考: Fearless Changeの48のパターンのチートシートを作りました。 - kawaguti’s diary

だから、この本を読んだからっていきなり「悪魔めー!退治だー!」って叫ぶんじゃなくて、まずは、仲間を集めてスモールステップで手を動かすところからね

開発プロセスやチームづくり・組織づくりのことを考えているチームリーダーやマネージャーも目を通しておくと良さそう

そして、この章の中でも「16.4.3 敬意と礼儀」が特に好き

コードレビューで最重要なのは、敬意と礼儀です。レビューを受ける側への敬意を第一に意識しましょう。技術的な正しさや有用性よりも、まずはともに働く、コードを書く仲間を尊重することです。敬意と礼儀を意識しながら指摘することが、コード品質を高める最短経路です。

ミノ駆動さんレビューのときめっちゃ怖そうとか思っててごめんなさいw

第10章 名前設計

あと、普通に勉強になったのはこの章。この本の中心はここかなぁって感じがした

名前をどうつけるか。それ自体が設計だよねって話

商品が色んな役割を持って大きくなってしまうの、わかるー!現実のモノとソフトウェアのモデルが1:Nになるの、それなー!って気持ちで読んだし

存在ではなく目的から名前を設計することとか、利用規約を読んでみることとか、色んなノウハウが書いてあって、やったほうが良いことも、やらないほうが良いことも、なるほどなぁってなった

あとでもういっかい読んでおこうかなぁ

実は最初は抵抗があった

僕は、コードに対して「悪魔」や「クソコード」みたいに言う人が苦手なので、言葉選びがあんまり好きじゃないなぁって思いながら読んでたのだけど、途中からは「読み物」として楽しむことができた

最後まで読んで分かったのは、その強い言葉が「コード」それも「著者が過去に苦しめられたコード」に対してだけ向けられていて、決して「そのコードを書いた人」や「その環境」には向かってない、ということ

だから、苦手な言葉が並んでても「読み物」として読めたんだなぁ

もし、僕と同じように思って手にしてない人がいたら、手にとってみたら良いんじゃないかな

(「悪魔」や「クソコード」は、僕が苦手なだけなので、とくに使うのをやめてほしいとかは全然思ってないです。ねんのため)

とても良かった!

ということで、頭の中を見せてもらえてとても良かった!

あくまでも、ひとつの設計方針だから、書いてあることを全て鵜呑みにするんじゃなくて、自分の中で咀嚼して、素振りしてみて、手に合いそうだったら道具箱に入れておいて、必要に応じて取り出して使えたら良いかな!

次に読みたい

もし、まだ読んでなかったら、次は「現場で役立つシステム設計の原則」を読むと良いと思う。DB連携や画面まで含めて、どんな風にオブジェクト指向で設計すると、変更しやすいシステムになるか、ってことが書いてある。

gihyo.jp

読んで良かったー!

良い判断したなぁ→5月1日の自分。読んで良かったー!

休日のリファクタリングあそび

いま「良いコード/悪いコードで学ぶ設計入門」を読んでる。今日はその途中で出てきたリファクタリングのサンプルで休日らしく息抜きに遊んでみたのでメモ。とても良い本なので読み終わったら感想を書こうと思ってる。

books.rakuten.co.jp

遊んだコード

おいといた

Commits · bufferings/20220506-refactoring · GitHub

元のコード

第14章にあるサンプル。これを書籍を参考にしながら、自分の好きな方法でリファクタリングする。

public class DeliveryManager {

  public static int deliveryCharge(List<Product> products) {
    int charge = 0;
    int totalPrice = 0;
    for (Product each : products) {
      totalPrice += each.price;
    }
    if (totalPrice < 2000) {
      charge = 500;
    } else {
      charge = 0;
    }
    return charge;
  }
}

既存のメソッドに対してテストを書く

Add test · bufferings/20220506-refactoring@64c2a14 · GitHub

書籍では、先にリファクタリング先の構造を作ってからそれに対してテストを書いてるけど、僕は先に既存のメソッドに対してテストを書いてみたいなと思ったのでそうした。テストメソッド名は日本語でいいかな

class DeliveryManagerTest {
  @Test
  public void 商品の合計金額が2000円未満の場合_配送料は500円() {
    var products = List.of(
        new Product(1, "商品A", 500),
        new Product(2, "商品B", 1499)
    );
    assertEquals(500, DeliveryManager.deliveryCharge(products));
  }

  @Test
  public void 商品の合計金額が2000円以上の場合_配送料は無料() {
    var products = List.of(
        new Product(1, "商品A", 500),
        new Product(2, "商品B", 1500)
    );
    assertEquals(0, DeliveryManager.deliveryCharge(products));
  }
}

ロジックを移動

Move logic · bufferings/20220506-refactoring@5863f31 · GitHub

テストができたので、それを Green にしたままロジックを DeliveryManager から DeliveryCharge に移動

public class DeliveryCharge {
  final int amount;

  public DeliveryCharge(ShoppingCart cart) {
    int charge = 0;
    int totalPrice = 0;
    for (Product each : cart.products) {
      totalPrice += each.price;
    }
    if (totalPrice < 2000) {
      charge = 500;
    } else {
      charge = 0;
    }
    this.amount = charge;
  }
}

DeliveryManager はこうなる

  public static int deliveryCharge(List<Product> products) {
    var cart = new ShoppingCart();
    for (var elem : products) {
      cart = cart.add(elem);
    }
    var charge = new DeliveryCharge(cart);
    return charge.amount;
  }

テスト対象を DeliveryManager から DeliveryCharge に変更

Move test target from DeliverManager to DeliveryCharge · bufferings/20220506-refactoring@6e03cba · GitHub

とりあえずこんな感じで

  @Test
  public void 商品の合計金額が2000円未満の場合_配送料は500円() {
    var products = List.of(
        new Product(1, "商品A", 500),
        new Product(2, "商品B", 1499)
    );

    var cart = new ShoppingCart();
    for (var elem : products) {
      cart = cart.add(elem);
    }
    var charge = new DeliveryCharge(cart);

    assertEquals(500, charge.amount);
  }

DeliveryManager を削除

Delete DeliveryManager · bufferings/20220506-refactoring@ca48d73 · GitHub

いらなくなったので削除

テストをリファクタリング

Refactor test · bufferings/20220506-refactoring@ecf4e3d · GitHub

さっき、とりあえずで動くようにしたテストを、ちゃんと書き直した

  @Test
  public void 商品の合計金額が2000円未満の場合_配送料は500円() {
    var cart = new ShoppingCart();
    cart = cart.add(new Product(1, "商品A", 500));
    cart = cart.add(new Product(2, "商品B", 1499));
    var charge = new DeliveryCharge(cart);

    assertEquals(500, charge.amount);
  }

合計金額の計算を ShoppingCart に移動

書籍にも書いてあるとおりのリファクタリング

Move total price calculation to ShoppingCart · bufferings/20220506-refactoring@366eab5 · GitHub

Stream 使ってみた

  public int totalPrice() {
    return products.stream().mapToInt(product -> product.price).sum();
  }

DeliveryChargeリファクタリング

やっとメインのリファクタリングだね。これも書籍の通り。三項演算子はあんまり好きじゃないので普通に if で

Refactor DeliveryCharge logic · bufferings/20220506-refactoring@b0bf4c6 · GitHub

public class DeliveryCharge {
  private static final int CHARGE_FREE_THRESHOLD = 2000;
  private static final int PAY_CHARGE = 500;
  private static final int CHARGE_FREE = 0;

  final int amount;

  public DeliveryCharge(ShoppingCart cart) {
    int totalPrice = cart.totalPrice();
    if (totalPrice < CHARGE_FREE_THRESHOLD) {
      amount = PAY_CHARGE;
    } else {
      amount = CHARGE_FREE;
    }
  }
}

最後に自分の好みにリファクタリング

Refactor production code · bufferings/20220506-refactoring@7b527ae · GitHub

個人的にはフィールド直接アクセス好きじゃないのでアクセサメソッドをつけた

public class Product {
  private final int id;
  private final String name;
  private final int price;

  public Product(int id, String name, int price) {
    this.id = id;
    this.name = name;
    this.price = price;
  }

  public int id() {
    return id;
  }

  public String name() {
    return name;
  }

  public int price() {
    return price;
  }
}

なんとなく DeliveryCharge にファクトリーメソッドを作った

public class DeliveryCharge {
  private static final int CHARGE_FREE_THRESHOLD = 2000;
  private static final int PAY_CHARGE = 500;
  private static final int CHARGE_FREE = 0;

  public static DeliveryCharge from(ShoppingCart cart) {
    int totalPrice = cart.totalPrice();
    if (fulfillChargeFreeCondition(totalPrice)) {
      return new DeliveryCharge(CHARGE_FREE);
    } else {
      return new DeliveryCharge(PAY_CHARGE);
    }
  }

  private static boolean fulfillChargeFreeCondition(int totalPrice) {
    return totalPrice >= CHARGE_FREE_THRESHOLD;
  }

  private final int amount;

  private DeliveryCharge(int amount) {
    this.amount = amount;
  }

  public int amount() {
    return amount;
  }

}

あとはちょこっとテストを付け足して終わり

楽しかった

休日っぽくふわふわコード書いて楽しかった

ペアプロが苦手でペアワーク

ペアでやろうよー!

チーム内で知識を共有できるように、フルリモートでも一緒に仕事できるように、チームとしてプロジェクトに取り組めるように、「ペアでやろうよー!」ってなって「それいいねー」って思って、最近はペアで仕事をしてる

そして、何年も前からうすうす感じてはいたんだけど、やっぱり、僕はペアプロが苦手だった!ので、ペアプロじゃなくてペアワークしてる

ペアプロ?ペアワーク?

ペアプロ」は「ペアプログラミング」のこと。一緒にコードを書く。リモートワークなのでペアプロするときは、Zoom とかで画面をシェアしながらコーディングしてる

一方「ペアワーク」って言葉は、正式な定義があるわけじゃなくて、自分がそう呼んでいるだけなんだけど「ひとつのタスクを二人で担当する」こと

なんで苦手なん?

パターンを考えてみると、自分と相手が対象のタスクに関して「詳しい」場合と「詳しくない」場合があるとして、こんな感じになってしまう

自分も相手も詳しい場合

お互いに詳しいので、最初に画面を共有して、ラフなコードを書いていって「ここ、こういう感じ?」「あぁいいね」「ここはどうする?」「こういう方針がよくない?」「プロダクトオーナーに聞いてみようか」「そうね」みたいな話をして、方針を揃える

その次に、タスクを小さな単位に分解して「この辺は僕がやるわー」「じゃ、これやるね。終わったときに次どうするか決めようかー」「OK」みたいな話をして、Zoom を終了して、別々に作業を始める

最初に WIP (Work In Progress) のプルリクエストを作って、そこにお互いにコードをプッシュし合う感じ。状況を Slack で随時共有しながら。先月は、僕がユニットテストを書いて、相手がコードを書いた。で、お互いのコードを見ながら Slack 上で「こうした方が良くない?」「あぁ、そうしようか」とかやったかな

自分が詳しくなくて相手が詳しい場合

この場合は、教えてもらいながらやるんだけど、知らないことは調べて身につけたいのだった。ペアプロして、言われた通りに書いたら実装はできるんだけど、色々気になってしまう性格なので。それと、すごく基本的なことを調べてるの見られるの恥ずかしい (´,,•ω•,,`)

最初に「どうやったらいいか知りたいー」って画面共有しながら聞いて「だいたいこんな感じになるよ」って教えてもらって「え、ここどういうこと?」「そこはね・・・」って、開発進められる情報を教えてもらったら「ありがとー!じゃ async ペアでお願いー」って言って「いいよー」って言ってもらって、相手は別の作業をしてたりする

僕は、ドキュメントを読んだり、「こうやったらどうなるんだろう?」っていじってみたり、ライブラリのソースコードを読みにいったりして、「ははーん!」ってなったら、ちょろっとコードを書いて、相手に「sync しよー」って言って画面を見ながら「この辺はこう考えて実装してみたんだけど、どう思う?」「あぁ、そこはこんな風に書いたらいいよ?」「なるほど!ありがとー」みたいにして、プルリクエストを出すときにはだいたい認識が揃ってる感じになってる

一日に一回は sync してるかなぁ

自分が詳しくて相手が詳しくない場合

だいたいみんな知らない部分は色々調べたいみたいで、↑の逆バージョンになってるかな。概要を説明して、この辺のドキュメント読んだら良いよ、って言っといて、随時 sync して認識を揃えながらやってる

どっちも詳しくない場合

これは、つなぎっぱなしでその技術のことを調べたりもするし、30分後に!って別々に調べて、あとで調べたことを共有したりする

という感じ

なので、ペアでコードを書くというより、ペアでタスクを担当してる感じで仕事をしてる。僕らは async ペアリングとか 🏓 ピンポンペアリングって呼んでたりする

おしまい!今日は、刺し身を食べようと思ってる!

GitOps とデプロイ

昨日はトランクベース開発とデプロイについて書いたので

bufferings.hatenablog.com

この勢いで GitOps とデプロイも書いてしまうー。先に言っておくと、自分は GitOps の経験はない。でも、よさそうだなぁと思う手法なので、機会があれば挑戦してみたい気持ち

GitOps?

GitOps は2017年に Weaveworks の Alexis によって提唱された手法で、Kubernetes を対象としている

Git のリポジトリーに入れてある設定ファイルを Single Source of Truth として、Kubernetesクラスター管理とアプリケーションデリバリーを行う。上記の記事には次の4つの原則が書かれている

  1. システム全体が宣言的に記述されていること
  2. 正規の望ましいシステムの状態が Git でバージョン管理されていること
  3. 変更が承認されたら自動的にシステムに反映されること
  4. ソフトウェアエージェントによって正しさが確認され、相違がある場合には警告されること

つまり、Kubernetesクラスターやアプリケーションの設定を Git で管理しておいて、変更されたら自動的にその設定通りになるようにしておく。ということ

また、(4) にあるように、Git 上で設定が変更された場合だけでなくクラスターやアプリケーション側で何か変更が発生した場合も「Git 上の設定と異なる状態になっている」ことが検知されて常に Git 上の設定とシステムやアプリケーションの状態が一致するようにしておく

今回はこの GitOps の考え方と、アプリケーションのデプロイに関して見ていく

アプリケーション用の2つのリポジトリ

f:id:bufferings:20220410100907p:plain:w600

https://www.weave.works/technologies/gitops/ より)

GitOps の場合は、ソースコードリポジトリー(図の左側にある Git)とは別に、設定用のリポジトリー(図の右下の Config Repo)がある

ソースコードリポジトリーからはコンテナレジストリーへのイメージの push までをしておいて、デプロイは設定用のリポジトリーの更新をトリガーにして実行される

設定用リポジトリーの中身

設定用のリポジトリーには、アプリケーションのデプロイ設定を入れる。この記事に紹介されてるのが分かりやすい↓

全ての環境の設定を入れてこんな感じのディレクトリになる。本番環境用の設定は会社のルール上、別で管理しないといけない場合もあるかもしれないね

f:id:bufferings:20220410162252p:plain:w200

生の YAML だとちょっとめんどくさいので Kustomize や Helm を使って管理すると良いと思う

この設定ファイルを更新することでデプロイを行う。例えば、本番環境用のイメージタグを更新することで本番環境に新しいイメージをデプロイすることができる

push 型と pull 型

設定用のリポジトリーからのデプロイは、ソースコードのビルドがないのでシンプル。単純に設定リポジトリーの main ブランチに設定の更新がマージされたら Kubernetes に反映されるようにしておいたら良い

その、反映の手法には push 型と pull 型がある。GitOps では pull 型の方がおすすめされているように感じる

push 型

  • 設定リポジトリーの更新をトリガーにして CI サービスから Kubernetes に対して変更を apply する

pull 型

  • Kubernetes の Operator が設定リポジトリーをポーリングしておいて、現在 Kubernetes 上で動いているリソースとの差分を検知したら、設定に合わせて Kubernetes に変更を適用する

push 型は簡単で分かりやすいし実装もしやすいけど、次のような課題がある

  • Kubernetes への書き込み権限を CI サービスに渡さないといけないこと
  • クラスター内を手で書き換えたりした場合に、設定と実際のクラスターの状態に差分が生じてしまうこと(つまり、原則の4が満たされない)

その点 pull 型だと

  • Kubernetes 上で動く自分たちの Operator から操作できるので、CI サービスに権限を渡さなくても良い
  • クラスター内の状態と設定ファイルとの差分を検知して、設定に合うように更新することができる

CircleCI のブログに、CircleCI と ArgoCD を使った pull 型の GitOps の記事があるのでよかったらチェックしてください!

GitOps と CI/CD

設定リポジトリを更新したら push 型または pull 型でデプロイされるとして、じゃあそこまでどう繋げば良いんだろう?というのを考えてみる

コンテナイメージ

まず、コンテナイメージ。The Twelve-Factor App にもあるように、外部から設定を与えることで同じイメージがどの環境でも動くように作っておきたい。てか、これは GitOps に関係なくやっておきたいことだな

デプロイ用のリポジトリーが別になっていて僕がいいなぁと思うのは、プロモーションが簡単に実現できるところ。ステージング環境にデプロイしてテストしたものと全く同じコンテナを本番環境にデプロイする、というプロモーションが設定ファイルのイメージタグを更新することで簡単に実現できる

全く同じイメージを使うのは、前回までに説明したソースコードリポジトリーを使ったデプロイだとなかなか難しい。デプロイするときに別のコミットが作られる場合が多いので、コードは同じだとしてもイメージは異なるものになってしまう

CI

次に CI。Git-flow でもトランクベース開発でも構わない。CI の仕事は「デプロイ可能なイメージをコンテナレジストリーに push する」ところまでになる

なので、Git-flow で release ブランチや main ブランチをデプロイしたいなら、それらのブランチが更新されたときにイメージを push しておけばいいし、トランクベース開発なら常に main のコンテナイメージを push しておけばいい

CD

そして CI からの CD。自動化できる場合は CI が終わったらビルドされたイメージのタグを使って設定ファイルを自動で更新したり、ちょっとレビューを入れておきたい場合は変更のプルリクエストを作成するようにしておけば良さそう

こんな感じになるのかなぁ?

トランクベース開発で、main を常時デプロイしたい場合は

  • main の CI の最後に、自動で設定ファイルを変更してデプロイ(↑で紹介した CircleCI の記事はこのパターン)

トランクベース開発で、デプロイのタイミングを調整したい場合は

  • デプロイしたいときに設定ファイルをそのイメージタグに更新してデプロイ

GitLab Flow みたいにやりたい場合は、例えば

  • main が更新されたら自動でステージング環境の設定ファイルを変更してデプロイ
  • プロモーション用のジョブでも作っておいて、ワンクリックでステージングの設定からプリプロダクション環境へ反映したり、プリプロダクションから本番環境へ設定を書き換えたりする

Git-flow で特定のブランチなどからデプロイしたい場合は

  • んー。ワンクリックデプロイのジョブのパラメーターでブランチ名を渡して、そのブランチの最新のコミットのコンテナイメージのタグで設定ファイルを更新することでデプロイ。かなぁ

(追記)ちなみに、Weave Cloud だとコンテナレジストリーの変更を検知して、設定リポジトリーの更新をしてくれるようにできるみたい。そんで、その設定変更を検知して、pull 型で変更を反映するっぽい。さすが便利ね

GitOps とデプロイ

デプロイ用のリポジトリーを別にしてしまうことで、ソースコードのブランチ戦略を考えるときのデプロイ周りの難しさが切り離されるのが面白い。さらに、その設定リポジトリーがデプロイの履歴になるのも良い

複数環境をひとつのリポジトリーに入れると、設定の共通部分などをまとめて管理できて便利だけど、例えば本番環境だけを急遽ロールバックしたい場合には、単純に昔の状態にもどしただけだと他の環境も元に戻ってしまうなぁとは思う。まぁ、だいたいの場合は、本番のイメージタグだけ昔に戻せばいいと思うので、ひとつでいいのかな

そんな風に便利そうだなって思うと同時に、管理するリポジトリーが増えることになるのと、CI と CD のつなぎこみも必要になるのとで、本当に必要かなぁ?ってのは考えたい。今のところ自分のチームは、トランクベース開発で常時本番環境にデプロイされている状態なので、それで満足している

というところで、Git とデプロイのお話はおしまい!あとは、頭の整理を兼ねてまとめを書きたい気持ちが少しあるくらい。ではー

トランクベース開発とデプロイ

前回は Git-flow とデプロイについて書いたので

bufferings.hatenablog.com

今回は、トランクベース開発とデプロイについて考える。LinkedIn, Facebook, Google などもトランクベースの開発をしてるんだね

トランクベース開発は Git じゃなくてもいいと思うんだけど、この記事では Git 前提で考える

トランクベース開発?

幹(trunk)となるブランチにみんなが小さな変更を加えていくブランチモデル。寿命の長いブランチを作らないようにすることで、マージでつらい思いをしなくて済むようになって、ビルドも壊れないようにできて、ヤッター!というもの

trunkbaseddevelopment.com

Git-flow みたいに develop ブランチで開発をするんじゃなくて、main ブランチで開発をする。main ブランチを常にビルドが通る状態にしておいて、そこからリリースするスタイル

ただ、main が更新されたら自動でリリースしないといけないというわけではない。自動でリリースしてもいいし、自分の好きなタイミングでリリースしてもいい。単に、main からいつでもリリースできるようにしておけばいい

f:id:bufferings:20220409142646p:plain:w400

https://trunkbaseddevelopment.com より)

幹となる青いブランチにみんなで変更を加えていく。下側にあるグレーの部分は、寿命の短い(長くても2日くらいの)フィーチャーブランチを作ってプルリクエストを出してマージする

一番シンプルな形だと、フィーチャーブランチも作らずに main に直接コミットするみたい。モブプロだと、その形がいちばん合ってそうね

トランクベース開発のバリエーション

GitHub Flow や GitLab Flow や、今回調べてて知ったぐるなびの GitFeatureFlow もトランクベース開発だなって思う。それぞれ、ひとつのブランチを幹にするのは一緒で、デプロイの方法が違う。ので、その辺りを見ていこうかな。最初にざっくりそれぞれの紹介から

GitHub Flow

GitHub Flow – Scott Chacon

「Git-flow は良いけど必要以上に複雑だから GitHub では違うやり方で開発してるよ!」って紹介されたフロー。ほぼトランクベース開発の図のままで、違うのは「main にマージされたらすぐデプロイしよう!」ってところかな。↑の記事では Hubot でデプロイしてる。

そういえば、今だと GitHub でプルリクエストを出して開発を進めるのは普通になってるけど、2011年だとまだそこまで Git もプルリクエストを使った開発もあんまり浸透してはなかった頃なのかなぁ?と思ってツイッター見てみたら、僕は2010年・2011年ぐらいに Git の勉強始めたみたい。ほほー

GitLab Flow

Introduction to GitLab Flow | GitLab

GitHub Flow 良いんだけど、デプロイ周りについては触れられてなくて迷うから、こんな感じが良いと思う!って GitLab がその辺りにガイドラインをつけたフロー

各環境用ブランチを持つことを推奨してる。例えば、development ブランチ(これが trunk)を常にデプロイ可能にしておくんだけど、本番環境へは production ブランチにマージされたときにデプロイされるようにしておく

こうすることで、デプロイのタイミングをコントロールすることができる。これが、前回の記事で触れた「develop と main だけの Git-flow は GitLab Flow と同じ」ってこと

f:id:bufferings:20220409163437p:plain:w400

https://docs.gitlab.com/ee/topics/gitlab_flow.html より)

複数の環境を持つ場合は、こんな感じになる↓

f:id:bufferings:20220409163928p:plain:w600

https://docs.gitlab.com/ee/topics/gitlab_flow.html より)

上から下に流れるようにすることで staging でテストされたものだけが pre-prod に、pre-prod で確認されたものだけが production にデプロイされるようにできる

GitFeatureFlow

ぐるなびの開発チームではこんな風にやってるよ!って紹介されたフロー

GitFlowは使わない!シンプルな「GitFeatureFlow」を紹介します - ぐるなびをちょっと良くするエンジニアブログ

特徴としては「main にマージする前にフィーチャーブランチからデプロイして動作確認ができるようにしてるよ!」ってところ。各環境用のブランチを持つのは GitLab Flow と似てるけど、それを main から反映させるんじゃなくて、フィーチャーブランチの段階で使ってる

f:id:bufferings:20220409164249p:plain

https://developers.gnavi.co.jp/entry/GitFeatureFlow/koyama より)

自分がやってたやつ

と、ここまで書いて、自分もなんか過去に書いたなぁって思って、みてみたらあった

bufferings.hatenablog.com

Git-flow だとちょっと複雑で、GitHub Flow だとシンプルすぎるからって言って、フィーチャーブランチだけの開発をしてたみたい。ブランチ名を指定してワンクリックデプロイできるようにしてたから、そのフィーチャーブランチをステージング環境にデプロイして問題なければ main にマージって流れか。GitFeatureFlow とやりたいことは同じだね

f:id:bufferings:20160205004713p:plain

デプロイ

ということで、トランクベース開発の場合のデプロイは

GitHub Flow のように

  • main にマージされたら自動で本番環境にデプロイされる

GitLab Flow のように

  • 各環境用のブランチがあって、main から順にそれぞれのブランチが更新されたら自動でデプロイされる

GitFeatureFlow のように

  • フィーチャーブランチから各環境用のブランチが更新されたら自動でデプロイされる

名前がないけど僕がやってたみたいに

  • ブランチ名とデプロイ先を指定してワンクリックデプロイをする

かな。GitFeatureFlow と僕のやってたフローは、トランクベース開発というよりは、軽量の Git-flow かもなぁ。フィーチャーを安定させてから main にマージするから、フィーチャーブランチの寿命が長そう

CircleCI では?

前々回の記事 で紹介した CircleCI の僕がいるチームのやり方は GitHub Flow に近くて

  • 小さな部品単位で main にマージして、main が更新されたら本番環境にデプロイされる

のだけど main にマージする前に確認したい場合は

  • canary ブランチにマージすることでカナリア環境にデプロイされる

というのをやってる

デプロイ周りのそれぞれの特徴は?

デプロイとしてシンプルなのは GitHub Flow の「main が更新されたら本番環境にデプロイされる」

必要になるのは CI を充実させること、フィーチャーフラグなどを使用してリリース前の機能を隠しておくこと、かな。細かな部品を常時統合しながらデプロイするので、開発のスピードはすごく速いし、未統合の機能も少なくて良い。一方で、デプロイ前にQAを実施するようなプロセスは挟まないので、そういったプロセスが必要な場合は別の方法で取り組む必要がある

GitLab Flow だと各環境にデプロイして確認してから本番環境にデプロイすることができる

なので、ステージング環境などでじっくり確認することができる。注意が必要なのは、本番環境などに HotFix が必要な場合に、すでに development ブランチが先に進んでいる場合は production ブランチにどう反映させるかを考える必要があるところかな。それと、デプロイのための流れはひとつだけなので、複数の機能の開発を並行して進めてそれぞれを動作確認したい、というのは難しい

GitFeatureFlow だと、複数の機能をそれぞれ別のフィーチャーブランチとして開発して、それぞれ別の環境にデプロイするということも可能

だけど、フィーチャーが大きくなると結局 Git-flow で発生していたようなマージの難しさがでてくるから、小さめのフィーチャーとして寿命の短いブランチにしておく方が良さそう。また、複数の機能を並行して開発しているときの環境ブランチは、機能Aをデプロイして動作確認したあとに機能Bをデプロイして確認すると、結局それは本番環境のコードとは異なるものになるので注意が必要そう。なので、ブログでも紹介されてるみたいにたまに main から作りなおしたら良さそう。それか、force push で更新してもいいのかも

こう見てみると

GitHub Flow がいちばん合うのは、GitHub や GitLab や CircleCI みたいに開発系のウェブサービスだと思う。ある程度固まった単位でのリリースが必要な場合は GitLab Flow は使えそう。だけど、複数の機能を並行して開発していく場合は GitFeatureFlow みたいな軽量の Git-flow が良さそうで、もっとリリースをかっちり管理したい場合は、Git-flow が良さそう

今回はこんなところかなー。次回は GitOps とデプロイ、を書いてこのシリーズは終わりかなー

追記

書いた

bufferings.hatenablog.com

Git-flow とデプロイ

前回 は継続的にデプロイしてるよって話をしたので、その流れで今日からちょっと Git を使った開発フローに対するデプロイについて考えてみたいと思う。まず最初はやっぱり Git-flow からかな。と、その前に

前置き

  • 自分は CircleCI だとどうなるかなぁとか考えながら書いてるけど、どの CI サービス・ツールを使っても大丈夫
  • 自分の頭の中にあるのはウェブ系の自社サービススマホアプリとか組み込みとかは経験がないから分かんない
  • どんな風にテストを実行するかみたいな話も面白いけど、今回は CI のことは忘れてデプロイだけ考える
  • どのフローが良い・悪いという話ではない

Git-flow

ということで Git-flow 。この絵は有名ですね

f:id:bufferings:20220408002139p:plain:w400

どこが始まりなんだろう?って思ったら2010年のこの記事みたい:

2020年に注意書きが追加されてて「(意訳)継続的デリバリーしてるならもっと軽量のフローの方がいいかも。バージョンをつけてる場合や複数バージョンを管理する場合には Git-flow は合ってるかも。自分で考えて決めてね!」って書いてある。良い。

今日のブログの図は↑の記事から引用してます

基本の流れ

基本の流れはこう

  1. develop ブランチで開発
  2. リリース可能な状態になったら main ブランチにマージ

なので main ブランチは基本的にリリース可能な状態になってる

f:id:bufferings:20220408002501p:plain:w300

基本の流れのデプロイ

この場合のデプロイは自動でやるならこうかな:

  • develop ブランチに変更がプッシュされたら自動でステージング環境にデプロイ
  • main ブランチに変更がマージされたら自動で本番環境にデプロイ

というのが考えられる。これは、たぶん次回に触れると思う GitLab Flow と同じということになる(develop→development, main→production)

でも、Git-flow を採用したい場合って、デプロイのタイミングは自分でコントロールしたいんじゃないかなぁ。なので、例えば、本番環境へのデプロイを自分でコントロールしたい場合は

  • クリックすると main ブランチからデプロイされてタグが打たれる

または

  • リリースタグを打つとデプロイされるようにしておいて、main ブランチにリリースタグを打つ

になるかなぁ。develop からのデプロイもコントロールしたい場合は、同じようにしたらいい。Git-flow で、かっちりやりたい場合は、個人的にはワンクリックデプロイが好きかな。まとめるとこう

  • develop ブランチからワンクリック(またはタグ・自動)でステージング環境にデプロイ
  • main ブランチからワンクリック(またはタグ・自動)で本番環境にデプロイ

release ブランチを使う流れ

と、ここまでは develop と main だけを使った流れだったけど、次は release ブランチを使う流れ

QA とかしてる間に、次の開発を進めておきたいなぁってときは、release ブランチを使う。自分が Git-flow を採用する場合は、release ブランチを使いたいってときだと思う。その場合の流れはこう

  1. develop ブランチで開発
  2. 開発が終わって安定したら release-* ブランチを作成
  3. release ブランチで QA を実施してリリース可能な状態になったら main ブランチにマージ

release ブランチが作成されたら、develop は次の開発に入れる

f:id:bufferings:20220408010520p:plain:w400

release ブランチを使う流れのデプロイ

この場合のデプロイはこんな感じ

  • develop ブランチから自動(ワンクリック・タグ)で開発環境にデプロイ
  • release ブランチからワンクリック(タグ・自動)でステージング環境にデプロイ
  • main ブランチからワンクリック(タグ・自動)で本番環境にデプロイ

開発環境なら自動が楽でいいかなと思うので自動を一番最初に、それ以外は自分でコントロールしたいからクリックを一番最初に書いておいてみた。タグについては、本番環境以外では僕は使わないことが多いかな。タグだらけになってしまうから。

hotfix ブランチを使う場合

  • hotfix-* ブランチを main から作成して動作確認が終わったら main にマージ

この場合、hotfix ブランチからどこかの環境にデプロイして確認をしたいと思うので

  • hotfix ブランチからクリック(タグ)でステージング環境にデプロイ

みたいにやりたい

デプロイまとめ

まとめると、Git-flow を使う場合に僕の好きなのはこういうデプロイだな

  • 開発環境
    • develop ブランチが更新されたら自動でデプロイ
  • ステージング環境(複数ある場合もある)
    • ブランチを選択して(develop・release・hotfix・main)ワンクリックデプロイ
  • 本番環境
    • main ブランチからワンクリックデプロイして自動でバージョンタグを打つ

Git-flow いつ使おう?

良いところ

release ブランチを使わないなら GitLab Flow ってことになるから、release ブランチまで含めて考えると、Git-flow の良いのは、release ブランチで変更をとめてしっかりテストをして安定させることができるところかな。だから Git-flow が合うのは、リリース前にしっかりテストを実施して、そのテストと並行で次の開発が進んだり、複数のリリースを管理したりする必要がある場合かなと思う

難しいところ

Git-flow が必要な環境だと、ブランチの寿命が長くなることが多い。ので、マージを頑張らないといけない。特に複数バージョン管理してる場合は大変そう。それと main や develop に変更が入った場合は、逆向きのマージをする必要があるけど、コンフリクトしないように早めにやっておいた方が良いと思う

いつ使おう?

開発プロセスが複雑な環境では Git-flow が合うと思う。とても便利。

でも本来はそこまで複雑にしなくていいはずなのに、 Git-flow を使うためにプロセスを必要以上に複雑にしてしまう場合があるので、そこだけ気をつけたいかな。「本当にこの Git-flow が自分たちの環境で一番いいのかな?もっとシンプルな開発プロセスや Git のフローでいけるんじゃないかな?」というのは考えたら良さそう

はい、ということで Git-flow のデプロイについてはこれくらい。次は、気が向いたときにシンプルな方のフローを見てみようと思う

続き書いた

bufferings.hatenablog.com