Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 16 additions & 16 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ This is a multi-module Gradle project using Kotlin DSL.
- AuditStore initialization requires full hierarchical context for dependency resolution

**Plugin System**: Extensible architecture via `Plugin` interface:
- Contribute task filters, event publishers, and dependency contexts
- Contribute change filters, event publishers, and dependency contexts
- Platform plugins (e.g., `flamingock-springboot-integration`) provide framework integration
- Initialized after base context setup but before hierarchical context building

Expand Down Expand Up @@ -339,24 +339,24 @@ YAML File
ChangeTemplateFileContent
↓ (preview building)
TemplatePreviewChange (unified)
↓ (loaded task building - template lookup from registry)
↓ (loaded change building - template lookup from registry)
AbstractTemplateLoadedChange
├── SimpleTemplateLoadedChange (for AbstractSimpleTemplate)
└── SteppableTemplateLoadedChange (for AbstractSteppableTemplate)
↓ (execution preparation)
TemplateExecutableTask<T>
├── SimpleTemplateExecutableTask (calls setStep())
└── SteppableTemplateExecutableTask (calls setSteps())
TemplateExecutableChange<T>
├── SimpleTemplateExecutableChange (calls setStep())
└── SteppableTemplateExecutableChange (calls setSteps())
↓ (runtime execution)
Template instance with injected dependencies
```

**Key Classes in Flow**:
- `ChangeTemplateFileContent` - YAML parsed data (`core/flamingock-core-commons`)
- `TemplatePreviewTaskBuilder` - Builds preview from file content (`core/flamingock-core-commons`)
- `TemplateLoadedTaskBuilder` - Resolves template class, builds type-specific loaded change (`core/flamingock-core`)
- `TemplateExecutableTaskBuilder` - Builds type-specific executable task (`core/flamingock-core`)
- `TemplateExecutableTask<T>` - Abstract base for template execution (`core/flamingock-core`)
- `TemplatePreviewChangeBuilder` - Builds preview from file content (`core/flamingock-core-commons`)
- `TemplateLoadedChangeBuilder` - Resolves template class, builds type-specific loaded change (`core/flamingock-core`)
- `TemplateExecutableChangeBuilder` - Builds type-specific executable change (`core/flamingock-core`)
- `TemplateExecutableChange<T>` - Abstract base for template execution (`core/flamingock-core`)

### Discovery Mechanism (SPI)

Expand Down Expand Up @@ -444,20 +444,20 @@ AbstractTemplateLoadedChange (abstract base)

**Executable Phase:**
```
TemplateExecutableTask<T> (abstract base)
├── SimpleTemplateExecutableTask (calls setStep())
└── SteppableTemplateExecutableTask (calls setSteps())
TemplateExecutableChange<T> (abstract base)
├── SimpleTemplateExecutableChange (calls setStep())
└── SteppableTemplateExecutableChange (calls setSteps())
```

**Type Detection:** Happens in `TemplateLoadedTaskBuilder.build()` using:
**Type Detection:** Happens in `TemplateLoadedChangeBuilder.build()` using:
- `AbstractSteppableTemplate.class.isAssignableFrom(templateClass)` → SteppableTemplateLoadedChange
- Otherwise → SimpleTemplateLoadedChange (default for AbstractSimpleTemplate and unknown types)

**Note:** Preview phase (`TemplatePreviewChange`) remains unified since YAML is parsed before template type is known.

### SteppableTemplateExecutableTask Apply/Rollback Lifecycle
### SteppableTemplateExecutableChange Apply/Rollback Lifecycle

The `SteppableTemplateExecutableTask` manages multi-step execution with per-step rollback:
The `SteppableTemplateExecutableChange` manages multi-step execution with per-step rollback:

**Apply Phase:**
- Iterates through steps in order (0 → N-1)
Expand All @@ -471,7 +471,7 @@ The `SteppableTemplateExecutableTask` manages multi-step execution with per-step
- **Skips steps without rollback payload** (`hasRollback()` returns false)
- **Skips if template has no `@Rollback` method** (logs warning)

**Key Design Decision:** Same `SteppableTemplateExecutableTask` instance is used for both apply and rollback (no retry). The `stepIndex` state persists to enable rollback from the exact failure point.
**Key Design Decision:** Same `SteppableTemplateExecutableChange` instance is used for both apply and rollback (no retry). The `stepIndex` state persists to enable rollback from the exact failure point.

### Dependency Injection in Templates

Expand Down
4 changes: 2 additions & 2 deletions RECOVERY_EXAMPLE.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ stages:
The recovery strategy is available throughout the execution pipeline:

```java
// In ExecutableTask (available during execution)
ExecutableTask task = // ... get from pipeline
// In ExecutableChange (available during execution)
ExecutableChange task = // ... get from pipeline
RecoveryDescriptor recovery = task.getRecovery();
RecoveryStrategy strategy = recovery.getStrategy();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@

public class AuditEntryRequest {

private String changeId;
private String stageId;
private String taskId;
private String author;
private long appliedAtEpochMillis;
private CloudAuditStatus state;
Expand All @@ -45,7 +45,7 @@ public AuditEntryRequest() {
}

public AuditEntryRequest(String stageId,
String taskId,
String changeId,
String author,
long appliedAtEpochMillis,
CloudAuditStatus state,
Expand All @@ -63,7 +63,7 @@ public AuditEntryRequest(String stageId,
CloudRecoveryStrategy recoveryStrategy,
Boolean transactionFlag) {
this.stageId = stageId;
this.taskId = taskId;
this.changeId = changeId;
this.author = author;
this.appliedAtEpochMillis = appliedAtEpochMillis;
this.state = state;
Expand All @@ -87,8 +87,8 @@ public String getStageId() {
return stageId;
}

public String getTaskId() {
return taskId;
public String getChangeId() {
return changeId;
}

public String getAuthor() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public class ChangeRequest {
public ChangeRequest() {
}

public static ChangeRequest task(String id, boolean transactional) {
public static ChangeRequest change(String id, boolean transactional) {
return new ChangeRequest(id, CloudTargetSystemAuditMarkType.NONE, transactional);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ public class StageRequest {

private int order;

private List<ChangeRequest> tasks;
private List<ChangeRequest> changes;

public StageRequest() {
}

public StageRequest(String name, int order, List<ChangeRequest> tasks) {
public StageRequest(String name, int order, List<ChangeRequest> changes) {
this.name = name;
this.order = order;
this.tasks = tasks;
this.changes = changes;
}

public String getName() {
Expand All @@ -41,8 +41,8 @@ public int getOrder() {
return order;
}

public List<ChangeRequest> getTasks() {
return tasks;
public List<ChangeRequest> getChanges() {
return changes;
}

public void setName(String name) {
Expand All @@ -53,8 +53,8 @@ public void setOrder(int order) {
this.order = order;
}

public void setTasks(List<ChangeRequest> tasks) {
this.tasks = tasks;
public void setChanges(List<ChangeRequest> changes) {
this.changes = changes;
}

@Override
Expand All @@ -64,11 +64,11 @@ public boolean equals(Object o) {
StageRequest that = (StageRequest) o;
return order == that.order
&& java.util.Objects.equals(name, that.name)
&& java.util.Objects.equals(tasks, that.tasks);
&& java.util.Objects.equals(changes, that.changes);
}

@Override
public int hashCode() {
return java.util.Objects.hash(name, order, tasks);
return java.util.Objects.hash(name, order, changes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ public class StageResponse {

private int order;

private List<ChangeResponse> tasks;
private List<ChangeResponse> changes;

public StageResponse() {
}

public StageResponse(String name, int order, List<ChangeResponse> tasks) {
public StageResponse(String name, int order, List<ChangeResponse> changes) {
this.name = name;
this.order = order;
this.tasks = tasks;
this.changes = changes;
}

public String getName() {
Expand All @@ -41,12 +41,12 @@ public void setName(String name) {
this.name = name;
}

public List<ChangeResponse> getTasks() {
return tasks;
public List<ChangeResponse> getChanges() {
return changes;
}

public void setTasks(List<ChangeResponse> tasks) {
this.tasks = tasks;
public void setChanges(List<ChangeResponse> changes) {
this.changes = changes;
}

public int getOrder() {
Expand All @@ -64,11 +64,11 @@ public boolean equals(Object o) {
StageResponse that = (StageResponse) o;
return order == that.order
&& java.util.Objects.equals(name, that.name)
&& java.util.Objects.equals(tasks, that.tasks);
&& java.util.Objects.equals(changes, that.changes);
}

@Override
public int hashCode() {
return java.util.Objects.hash(name, order, tasks);
return java.util.Objects.hash(name, order, changes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,25 @@

/**
* Represents the cloud orchestrator's decision about what action should be taken
* for a specific task. This enum maintains separation from the internal ChangeAction
* for a specific change. This enum maintains separation from the internal ChangeAction
* to preserve cloud domain boundaries while keeping aligned enum values.
*/
public enum CloudChangeAction {

/**
* The task needs to be applied/applied - cloud orchestrator determined it should run.
* The change needs to be applied/applied - cloud orchestrator determined it should run.
* Maps to ChangeAction.APPLY.
*/
APPLY,

/**
* The task should be skipped as it has already been successfully applied.
* The change should be skipped as it has already been successfully applied.
* Maps to ChangeAction.SKIP.
*/
SKIP,

/**
* Manual intervention is required for this task.
* Manual intervention is required for this change.
* Maps to ChangeAction.MANUAL_INTERVENTION.
*/
MANUAL_INTERVENTION
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public HtttpAuditWriter(String host,
this.runnerId = runnerId;

this.pathTemplate = String.format(
"/api/%s/environment/%s/service/%s/execution/{executionId}/task/{taskId}/audit",
"/api/%s/environment/%s/service/%s/execution/{executionId}/change/{changeId}/audit",
apiVersion,
environmentId.toString(),
serviceId.toString());
Expand All @@ -74,12 +74,12 @@ public Result writeEntry(AuditEntry auditEntry) {
.withRunnerId(runnerId)
.withBearerToken(authManager.getJwtToken())
.addPathParameter("executionId", auditEntry.getExecutionId())
.addPathParameter("taskId", auditEntry.getTaskId())
.addPathParameter("changeId", auditEntry.getChangeId())
.setBody(auditEntryRequest)
.execute();
return Result.OK();
} catch (Throwable throwable) {
logger.debug("Error writing audit [{}] :\n{}", auditEntry.getTaskId(), throwable.toString());
logger.debug("Error writing audit [{}] :\n{}", auditEntry.getChangeId(), throwable.toString());
return new Result.Error(throwable);
}

Expand All @@ -91,7 +91,7 @@ private AuditEntryRequest buildRequest(AuditEntry auditEntry) {
CloudTxStrategy txType = auditEntry.getTxType() != null ? CloudApiMapper.toCloud(auditEntry.getTxType()) : null;
return new AuditEntryRequest(
auditEntry.getStageId(),
auditEntry.getTaskId(),
auditEntry.getChangeId(),
auditEntry.getAuthor(),
appliedAtEpochMillis,
CloudApiMapper.toCloud(auditEntry.getState()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@
import io.flamingock.internal.core.external.store.lock.LockKey;
import io.flamingock.internal.core.pipeline.execution.ExecutableStage;
import io.flamingock.internal.core.pipeline.loaded.stage.AbstractLoadedStage;
import io.flamingock.internal.common.core.task.TaskDescriptor;
import io.flamingock.internal.core.task.loaded.AbstractLoadedTask;
import io.flamingock.internal.common.core.change.ChangeDescriptor;
import io.flamingock.internal.core.change.loaded.AbstractLoadedChange;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
Expand All @@ -58,19 +58,19 @@ public static ExecutionPlanRequest toRequest(List<AbstractLoadedStage> loadedSta
List<StageRequest> requestStages = new ArrayList<>(loadedStages.size());
for (int i = 0; i < loadedStages.size(); i++) {
AbstractLoadedStage currentStage = loadedStages.get(i);
List<ChangeRequest> stageTasks = currentStage
.getTasks()
List<ChangeRequest> stageChanges = currentStage
.getChanges()
.stream()
.map(descriptor -> CloudExecutionPlanMapper.mapToChangeRequest(descriptor, ongoingStatusesMap))
.collect(Collectors.toList());
requestStages.add(new StageRequest(currentStage.getName(), i, stageTasks));
requestStages.add(new StageRequest(currentStage.getName(), i, stageChanges));
}

return new ExecutionPlanRequest(lockAcquiredForMillis, requestStages);
}

private static ChangeRequest mapToChangeRequest(AbstractLoadedTask descriptor,
Map<String, TargetSystemAuditMarkType> ongoingStatusesMap) {
private static ChangeRequest mapToChangeRequest(AbstractLoadedChange descriptor,
Map<String, TargetSystemAuditMarkType> ongoingStatusesMap) {
TargetSystemAuditMarkType domainStatus = ongoingStatusesMap.get(descriptor.getId());
CloudTargetSystemAuditMarkType cloudStatus = domainStatus != null
? CloudApiMapper.toCloud(domainStatus)
Expand All @@ -96,29 +96,29 @@ static List<ExecutableStage> getExecutableStages(ExecutionPlanResponse response,
}

private static ExecutableStage mapToExecutable(AbstractLoadedStage loadedStage, StageResponse stageResponse) {
Map<String, CloudChangeAction> taskStateMap = stageResponse.getTasks()
Map<String, CloudChangeAction> changeStateMap = stageResponse.getChanges()
.stream()
.collect(Collectors.toMap(ChangeResponse::getId, ChangeResponse::getAction));

// Build action map using anti-corruption layer
ChangeActionMap actionPlan = getChangeActionMap(loadedStage, taskStateMap);
ChangeActionMap actionPlan = getChangeActionMap(loadedStage, changeStateMap);
return loadedStage.applyActions(actionPlan);
}

@NotNull
private static ChangeActionMap getChangeActionMap(AbstractLoadedStage loadedStage, Map<String, CloudChangeAction> actionsMapByChangeId) {
Map<String, ChangeAction> actionMap = new HashMap<>();

for (TaskDescriptor task : loadedStage.getTasks()) {
String taskId = task.getId();
CloudChangeAction cloudAction = actionsMapByChangeId.get(taskId);
for (ChangeDescriptor change : loadedStage.getChanges()) {
String changeId = change.getId();
CloudChangeAction cloudAction = actionsMapByChangeId.get(changeId);

// If task not in response, assume it's already applied (cloud orchestrator decision)
// If change not in response, assume it's already applied (cloud orchestrator decision)
if (cloudAction == null) {
actionMap.put(taskId, ChangeAction.SKIP);
actionMap.put(changeId, ChangeAction.SKIP);
} else {
// Use anti-corruption layer to map cloud domain to internal domain
actionMap.put(taskId, mapCloudActionToChangeAction(cloudAction));
actionMap.put(changeId, mapCloudActionToChangeAction(cloudAction));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ private ExecutionPlanResponse createExecution(List<AbstractLoadedStage> loadedSt

Map<String, TargetSystemAuditMarkType> auditMarks = getOngoingStatuses()
.stream()
.collect(Collectors.toMap(TargetSystemAuditMark::getTaskId, TargetSystemAuditMark::getOperation));
.collect(Collectors.toMap(TargetSystemAuditMark::getChangeId, TargetSystemAuditMark::getOperation));

ExecutionPlanRequest requestBody = CloudExecutionPlanMapper.toRequest(
loadedStages,
Expand Down
Loading
Loading