GraphQL Federationで旧システムへのリクエスト数を増やさないための移行順序 - ぴょこぴょこブログ の続き。
旧システムにリクエストを増やさないようにしながら段階的に新システムに移行する戦略を考えている。
結論としては、次の3ステップを繰り返すことで旧システムにリクエストを増やさないように段階的に移行できると考えている。
- 非エンティティフィールド(scalar, Query, Mutationのフィールド)を新システムに移行する
- エンティティフィールドを
@shareableを付けて新システムに移行する - エンティティフィールドとそのエンティティのフィールドが新システムに移行されたら
@shareableをやめて旧システムから削除し、新システムのみにする
具体例
スキーマが次のようになっていて、Tag エンティティを移行したいとする。
# Itemエンティティ
type Item @key(fields: "id") {
id: ID!
name: String!
tags: [Tag!]!
}
# Tagエンティティ
type Tag @key(fields: "id") {
id: ID!
category: String!
item: Item
}
type Query {
getItem(id: ID!): Item
}
1. 非エンティティフィールドの Tag.category を新システムに移行する
query GetItem {
getItem(id: "1") {
tags {
category
item {
name
}
}
}
}
というクエリが来ると、currentへのリクエストは1回だけ。newへのリクエストも1回だけ。
query GetItem__current_service__0 {
getItem(id: "1") { # Item に辿れる
tags {
__typename
id
item {
name
}
}
}
}
query GetItem__new_service__1($representations: [_Any!]!) {
_entities(representations: $representations) {
... on Tag {
category
}
}
}
2. エンティティフィールドの Tag を @shareable を付けて新システムに移行する
@shareableを付けてcurrentとnewの両方のサブグラフで解決可能であることをRouterに伝える。
type Item @key(fields: "id") {
id: ID!
name: String!
tags: [Tag!]! @shareable
}
1で Tag.category を移行しているので、次のクエリが来た時、Item.tags の解決はnewで行われる。
query GetItem {
getItem(id: "1") {
tags {
category
}
}
}
Routerはリクエスト数が少なくなるように実行計画を立てる。
Tag のフィールドで未移行のものにアクセスする場合は Item.tags の解決をcurrentで行ったほうがリクエスト数が少なくなるならcurrentで解決される。
3. エンティティフィールドとそのエンティティのフィールドが新システムに移行されたら @shareable をやめて旧システムから削除し、新システムのみにする
エンティティフィールドとそのエンティティのフィールドが新システムに移行されたnewのスキーマは次のようになる。
# Itemエンティティ
type Item @key(fields: "id") {
id: ID!
name: String!
tags: [Tag!]! @shareable
}
# Tagエンティティ
type Tag @key(fields: "id") {
id: ID!
category: String!
item: Item @shareable
}
type Query {
getItem(id: ID!): Item
}
こうなるとどのようにクエリしてもcurrentで解決するものがないので全てnewで解決される。 currentは不要になったので削除できる。
注意点
二重管理に注意
@shareable が付いている限りは両方のサブグラフで解決できる必要があるので、その期間はcurrentとnewで実装が二重管理になる。
修正が同期されないと不整合が起きうるので、静的解析ツールを作るなどしてcurrentのロジックの修正がnewに波及するかを判断できるようにしておくのが良いと考えている。
移行ステップの順番に注意
移行ステップの1と2は順番に行われることを期待している。
逆になると、全てのフィールドが移行されるまでRouterはcurrentでしか解決せずビッグバン的なリリースになる。
@shareable が付いていてcurrentで解決できる & フィールドがcurrentにしかないのでRouterはnewで解決するメリットがないと判断する。