migrating-motoko について
migrating-motokoは、Motoko Actorのアップグレード時に状態を移行するスキルです。永続フィールドやデータ構造を変更する開発者向けに、互換変更による暗黙の移行と、Actorに付ける式による明示的な変換を説明します。
主な機能
- 01暗黙・明示の移行要件を区別
- 02移行関数でActorの状態を変換
- 03フィールド名や型の変更例を提示
- 04移行関数の入出力規則を説明
- 05ビルドとチェック手順を提示
活用シーン
- 01アップグレード時の永続フィールド名変更
- 02フィールド型や状態構造の変更
- 03コレクション内データの変換
- 04追加フィールドへの初期値設定
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ dfinity/ icskills --skill migrating-motokoインストール後、Claude Code で/migrating-motokoを入力して読み込まれたか確認します。
詳細
インラインアクター移行
アクターに付加した移行式を使って、キャニスターのアップグレード時にアクターの状態を移行します。各アップグレードで使用できる移行関数は最大1つです。
migrations/ ディレクトリを使った複数の移行には、代わりに migrating-motoko-enhanced を読み込んでください。
使用する場面
暗黙的な移行(コード不要)
新しいプログラムが古いプログラムと互換性がある場合、ランタイムはアップグレードを許可します。
- アクターのフィールドを追加する
- アクターのフィールドを削除する
- 可変性を変更する(
var↔let) - バリアントのコンストラクターを追加する
- 型を拡張する(
Nat→Int)
明示的な移行が必要
- フィールド名を変更する
- フィールドの型を変更する(例:
Bool→ バリアント、Int→Float) - 状態の構造を変更する(フィールドの分割/統合)
- コレクションの値を変換する
構文
アクターの直前に置く、括弧付きの式:
import Migration "migration";
(with migration = Migration.run)
actor {
var newState : Float = 0.0;
};
または、インラインで記述します。
import Int "mo:core/Int";
(with migration = func(old : { var state : Int }) : { var newState : Float } {
{ var newState = old.state.toFloat() }
})
actor {
var newState : Float = 0.0;
};
インポートしたモジュールが migration フィールドをエクスポートする場合は、短縮形も使えます。
import { migration } "migration";
(with migration)
actor { ... };
移行関数のルール
- 型:
func (old : { ... }) : { ... }— ローカルかつ非ジェネリックで、両方のレコードに永続化可能な型を使う必要があります(関数や可変配列は不可) - 定義域:古いアクターのフィールド(以前のバージョンの名前と型)
- 値域:新しいアクターのフィールド(新しいアクターに存在し、互換性のある型である必要があります)
- アップグレード時のみ実行されます — 新規インストール時は通常どおりイニシャライザーが実行されます
- 移行関数がトラップすると、アップグレードは中止され、キャニスターは古いバージョンのままになります
フィールドの意味
| フィールドの出現箇所 | 効果 |
|---|---|
| 入力と出力 | フィールドが変換されます |
| 出力のみ | 移行によって新しいフィールドが生成されます |
| 入力のみ | フィールドが消費されます(コンパイラーはデータ損失の可能性について警告します) |
| どちらにもない | 引き継がれるか、宣言によって初期化されます |
移行モジュールのパターン
移行は別のモジュールにまとめます。古い型はインラインで定義し、古いコードパスからインポートしないでください。
// migration.mo
import Types "types";
import Map "mo:core/Map";
module {
type OldTask = { id : Nat; title : Text; completed : Bool };
type OldActor = {
var tasks : Map.Map<Nat, OldTask>;
var nextId : Nat;
};
type NewActor = {
var tasks : Map.Map<Nat, Types.Task>;
var nextId : Nat;
};
public func run(old : OldActor) : NewActor {
let tasks = old.tasks.map<Nat, OldTask, Types.Task>(
func(_, task) {
{
id = task.id;
title = task.title;
due = 0;
var status = if (task.completed) #completed else #pending;
}
}
);
{ var tasks; var nextId = old.nextId };
};
};
// main.mo
import Map "mo:core/Map";
import Types "types";
import Migration "migration";
(with migration = Migration.run)
actor {
var tasks = Map.empty<Nat, Types.Task>();
var nextId : Nat = 0;
};
フィールドにはイニシャライザーが必要です — 移行関数はアップグレード時のみ実行されます。新規インストール時はイニシャライザーが使われます。
よく使われるパターン
デフォルト値を指定してフィールドを追加する
old.users.map<Nat, OldUser, NewUser>(
func(_, u) { { u with zipCode = "" } }
)
オプショナルフィールドを追加する
{ task with var assignee = null : ?Principal }
Bool からバリアントへの変換
var status = if (task.completed) #completed else #pending;
フィールド名を変更する
古い名前を消費し、新しい名前を生成します。
func(old : { var state : Int }) : { var value : Int } {
{ var value = old.state }
}
フィールドを削除する
入力でフィールドを消費し、出力から省略します。コンパイラーが警告するため、損失が意図したものか確認してください。
チェックリスト
- 暗黙的(互換性のある変更)か明示的(移行関数)かを決める
- 明示的な移行の場合:
migration.moに古い型をインラインで定義する - 移行の型:永続化可能な型を使った
func (old : RecordIn) : RecordOut - アクターの前に
(with migration = Migration.run)を付ける - データ移行に
preupgrade/postupgradeを使わない -
mops check --fixとmops buildで検証する
参考資料
- Motoko 言語全般のリファレンスと mo:core API については、
motokoを読み込みます --enhanced-migrationを使った複数の移行については、migrating-motoko-enhancedを読み込みますmops check、mops build、ツールチェーンのセットアップについては、mops-cliを読み込みます
コメント
Markdown 対応