ビジネスプロセスの変更管理における課題
企業システムにおいて、業務フローは頻繁に改訂される重要な要素です。誤ったプロセス定義の適用は、業務停止やデータの不整合を引き起こす可能性があります。Camundaはプロセスのバージョン管理機能を備えており、安全なアップデートとロールバックを実現します。
バージョン管理の仕組み
Camundaでは、各プロセス定義に対して自動的にバージョン番号が割り当てられます。
// 最新バージョンの取得
ProcessDefinition definition = repositoryService
.createProcessDefinitionQuery()
.processDefinitionKey("orderFlow")
.latestVersion()
.singleResult();
int version = definition.getVersion();
String deployId = definition.getDeploymentId();
| 属性 | 説明 | 初期値 |
|---|---|---|
| バージョン番号 | 連番で管理される整数 | 1から開始し、デプロイごとに増加 |
| デプロイ識別子 | グローバルに一意なUUID | ランダム生成 |
| プロセスキー | BPMNファイル内のprocess id | XMLで指定された値 |
| アクティブフラグ | 現在有効なバージョンか否か | 最新版のみtrue |
デプロイ手法
基本的なデプロイ操作
// プロセスと関連リソースの一括登録
Deployment registration = repositoryService.createDeployment()
.addClasspathResource("bpmn/order.bpmn")
.addClasspathResource("dmn/validation.dmn")
.name("注文処理v2.0")
.enableDuplicateFiltering(true)
.deploy();
// 登録結果の確認
List<ProcessDefinition> definitions = repositoryService
.createProcessDefinitionQuery()
.deploymentId(registration.getId())
.list();
段階的デプロイメント
ブルー・グリーン方式
// 新旧並行運用パターン
public class BlueGreenDeployer {
public Deployment perform(ProcessEngine engine, String key, String... files) {
RepositoryService repo = engine.getRepositoryService();
// 新バージョンの登録
Deployment next = repo.createDeployment()
.addClasspathResources(Arrays.asList(files))
.name("BG Deploy: " + key)
.deploy();
// 既存バージョンの一時停止
deactivateOldVersions(repo, key);
return next;
}
private void deactivateOldVersions(RepositoryService service, String key) {
List<ProcessDefinition> all = service
.createProcessDefinitionQuery()
.processDefinitionKey(key)
.list();
ProcessDefinition active = service
.createProcessDefinitionQuery()
.processDefinitionKey(key)
.latestVersion()
.singleResult();
for (ProcessDefinition item : all) {
if (!item.getId().equals(active.getId())) {
service.suspendProcessDefinitionById(item.getId());
}
}
}
}
カナリアリリース
// 小規模テスト投入
public class CanaryDeployer {
public void release(ProcessEngine engine, String key, double ratio) {
// 新規バージョン登録
Deployment deploy = engine.getRepositoryService()
.createDeployment()
.addClasspathResource("bpmn/test.bpmn")
.deploy();
// 乱数によりルーティング
Random rand = new Random();
RuntimeService runtime = engine.getRuntimeService();
if (rand.nextDouble() < ratio) {
runtime.startProcessInstanceByKey(key + "-test");
} else {
runtime.startProcessInstanceByKey(key);
}
}
}
ロールバック機構
デプロイ単位での巻き戻し
// デプロイメント全体の削除制御
public class RollbackController {
private final RepositoryService repo;
private final RuntimeService runtime;
public RollbackController(ProcessEngine engine) {
this.repo = engine.getRepositoryService();
this.runtime = engine.getRuntimeService();
}
public boolean revert(String deployId, boolean forceRemove) {
try {
long activeCount = getActiveInstanceCount(deployId);
if (activeCount > 0 && !forceRemove) {
throw new IllegalStateException(
"進行中のインスタンスが存在します: " + activeCount);
}
return executeRevert(deployId, forceRemove);
} catch (Exception ex) {
log.error("ロールバック失敗: " + deployId, ex);
return false;
}
}
private long getActiveInstanceCount(String deployId) {
List<ProcessDefinition> defs = repo
.createProcessDefinitionQuery()
.deploymentId(deployId)
.list();
long total = 0;
for (ProcessDefinition def : defs) {
total += runtime.createProcessInstanceQuery()
.processDefinitionId(def.getId())
.count();
}
return total;
}
private boolean executeRevert(String deployId, boolean force) {
if (force) {
repo.deleteDeployment(deployId, true, true);
} else {
repo.deleteDeployment(deployId);
}
return true;
}
}
本番環境での運用ノウハウ
事前チェックリスト
| 確認項目 | 詳細 | 検証手段 |
|---|---|---|
| BPMN構文チェック | XML形式の正当性 | Model APIによる解析 |
| データ互換性 | 変更前後の型一致 | テストケース実行 |
| パフォーマンス評価 | メモリ/CPU使用量 | 負荷試験ツール利用 |
| 復旧計画 | 緊急時の対応手順 | スクリプト動作確認 |
監視設定
// デプロイ状況の追跡
public class HealthWatcher {
public void observe(String deployId) {
Map<String, Object> stats = new HashMap<>();
stats.put("running", countRunning(deployId));
stats.put("completed", countFinished(deployId));
stats.put("errors", countErrors(deployId));
stats.put("avgTime", computeAvgDuration(deployId));
if ((Long)stats.get("errors") > limit) {
sendNotification("異常検知: " + deployId);
}
}
private long countFinished(String deployId) {
return processEngine.getHistoryService()
.createHistoricProcessInstanceQuery()
.deploymentId(deployId)
.finished()
.count();
}
}
実務例:ショッピングカートフローの更新
背景
ECサイトの受注プロセスに新たな承認ステップを追加する必要があり、バージョン3.2から4.0へ移行します。
移行手順
// 注文処理のバージョンアップ
public class OrderFlowUpdater {
public void upgrade(ProcessEngine engine) {
RepositoryService repo = engine.getRepositoryService();
// 現行バージョンのバックアップ作成
Deployment snapshot = backupCurrent(repo, "orderFlow");
// ブルー・グリーンデプロイ実施
Deployment newVer = repo.createDeployment()
.addClasspathResource("bpmn/order_v4.bpmn")
.addClasspathResource("dmn/rules.dmn")
.name("Order Flow v4.0 - Blue")
.enableDuplicateFiltering(true)
.deploy();
// 少量トラフィックを新バージョンへ
shiftTraffic(engine, "orderFlow", 0.1);
// 新バージョンの安定性確認後完全移行
if (checkStability(newVer.getId())) {
finalizeTransition(engine, "orderFlow");
} else {
restorePrevious(repo, snapshot.getId());
}
}
private void shiftTraffic(ProcessEngine eng, String key, double percent) {
// トラフィック比率の動的調整ロジック
}
}
緊急復帰プラン
// 即時復旧マニュアル
public class EmergencyRecovery {
public static final Map<String, String> RECOVERY_MAP = Map.of(
"orderFlow-v4", "orderFlow-v3.2",
"payment-v2.1", "payment-v2.0"
);
public boolean activate(String flowName) {
String target = RECOVERY_MAP.get(flowName);
if (target == null) {
throw new IllegalArgumentException("復旧情報未定義: " + flowName);
}
return switchToVersion(flowName, target);
}
}
Camundaの柔軟なバージョン管理により、企業はリスクを最小限に抑えながら継続的なプロセス改善が可能です。適切なデプロイ戦略と監視体制を整えることで、安定した業務運用を維持できます。