ParallelChangeで継続的デリバリーを手にいれる
ParallelChange
継続的デリバリーの練度を上げるため
マーティンファウラー氏がPOSTしたParallel Change
を理解していく
最終的には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)
全て移行が完了した後、新しいバージョンのみを対応したインターフェースに更新する
例では、newCells
をcells
に完全に置き換えることができる
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の明示的なバージョンによる変更
その他
- BranchByAbstractionを実装する場合
Parallel Change の欠点
Parallel Change
にも欠点があるため、補助的な対応も必要
移行フェーズでは、複数バージョンを扱う必要があり、クライアントに混乱を招く可能性が高いため
確実に収縮フェーズを完了させるには「規律」が必要になる。
「規律」としては下記
- 非推奨の注記
- 文書化
- TODOの注記
これらの規律を設けることで、同じコードを扱う開発者やクライアントに対して
どの機能がどのバージョンに置き換わる家庭にいるのかを知らせる術となる。
Discussion