🐺

ParallelChangeで継続的デリバリーを手にいれる

2024/10/04に公開

ParallelChange

継続的デリバリーの練度を上げるため
マーティンファウラー氏がPOSTしたParallel Changeを理解していく

https://martinfowler.com/bliki/ParallelChange.html

最終的にはLibraryクラスのOldインターフェースをNewインターフェースになるよう目指す

Old

class Library {
    private Book[][] shelves;

    public void addBook(int row, int column, Book book) {
        shelves[row][column] = book;
    }

    public Book getBook(int row, int column) {
        return shelves[row][column];
    }

    public boolean isSlotEmpty(int row, int column) {
        return shelves[row][column] == null;
    }
}

New

class Library {
    private Map<ShelfLocation, Book> books;

    public void addBook(ShelfLocation location, Book book) {
        books.put(location, book);
    }

    public Book getBook(ShelfLocation location) {
        return books.get(location);
    }

    public boolean isSlotEmpty(ShelfLocation location) {
        return !books.containsKey(location);
    }
}

ParallelChangeとは

Parallel change, also known as expand and contract, is a pattern to implement backward-incompatible changes to an interface in a safe manner, by breaking the change into three distinct phases: expand, migrate, and contract.
https://martinfowler.com/bliki/ParallelChange.html Parallel Change より

expand and contract(拡張と収縮)とも呼ばれており
インターフェイスに対する後方互換性のない変更を、拡張(expand)移行(migrate)収縮(contract)という3つの異なるフェーズに分けて安全な方法で実装するパターン。

拡張(expand)

拡張フェーズでは、古いバージョンと、新しいバージョンどちらもサポートするように
インターフェースを拡張する

class Library {
    private Book[][] shelves;
+    private Map<ShelfLocation, Book> books;

    public void addBook(int row, int column, Book book) {
        shelves[row][column] = book;
    }

+    public void addBook(ShelfLocation location, Book book) {
+        books.put(location, book);
+    }

    public Book getBook(int row, int column) {
        return shelves[row][column];
    }

+    public Book getBook(ShelfLocation location) {
+        return books.get(location);
+    }

    public boolean isSlotEmpty(int row, int column) {
        return shelves[row][column] == null;
    }

+    public boolean isSlotEmpty(ShelfLocation location) {
+        return !books.containsKey(location);
+    }
}

上記拡張をすることで、新旧どちらも影響を与えずすみます。

移行(migrate)

移行フェーズでは、古いバージョンを新しいバージョンに更新をしていきます。

class Library {
+    private Map<ShelfLocation, Book> books;

+    public void addBook(ShelfLocation location, Book book) {
+        books.put(location, book);
+    }

+    public Book getBook(ShelfLocation location) {
+        return books.get(location);
+    }

+    public boolean isSlotEmpty(ShelfLocation location) {
+        return !books.containsKey(location);
+    }
}

移行フェーズでは、FeatureFlagを使用し、対応するバージョンの切り替え制御をすることが可能です。

収縮(contract)

全て移行が完了した後、新しいバージョンのみを対応したインターフェースに更新する
例では、newCellscellsに完全に置き換えることができる

class Library {
    private Map<ShelfLocation, Book> books;

    public void addBook(ShelfLocation location, Book book) {
        books.put(location, book);
    }

    public Book getBook(ShelfLocation location) {
        return books.get(location);
    }

    public boolean isSlotEmpty(ShelfLocation location) {
        return !books.containsKey(location);
    }
}

ここでIntelliJのリファクタリング機能を使うと機械的に操作できる

継続的デリバリーを行うためのプラクティス

拡張移行収縮という3つのフェーズは、どこでもリリース可能な状態を保てる
つまりParallel Changeは継続的なデリバリーを行う上で有効なプラクティスであると理解しています。

This pattern is particularly useful when practicing ContinuousDelivery because it allows your code to be released in any of these three phases. It also lowers the risk of change by allowing you to migrate clients and to test the new version incrementally.

Parallel Changeの応用

Paralell Changeを応用したパターンについて

コードのリファクタリング

  • メソッドや関数のシグネチャを変更
  • 長期にかけてリファクタリングを行う
  • 公開インターフェースの変更

データベースのリファクタリング

基本的に新しいスキーマに変更する場合、Paralell Changeに従って行う

デプロイメイン

  • カナリアリリース
  • BlueGreenDeployment

Remote API

  • 後方互換性がない
  • 公開APIの明示的なバージョンによる変更

その他

Parallel Change の欠点

Parallel Changeにも欠点があるため、補助的な対応も必要

移行フェーズでは、複数バージョンを扱う必要があり、クライアントに混乱を招く可能性が高いため
確実に収縮フェーズを完了させるには「規律」が必要になる。

「規律」としては下記

  • 非推奨の注記
  • 文書化
  • TODOの注記

これらの規律を設けることで、同じコードを扱う開発者やクライアントに対して
どの機能がどのバージョンに置き換わる家庭にいるのかを知らせる術となる。

Discussion