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
59 changes: 54 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -338,20 +338,25 @@ YAML File
↓ (parsing)
ChangeTemplateFileContent
↓ (preview building)
TemplatePreviewChange
TemplatePreviewChange (unified)
↓ (loaded task building - template lookup from registry)
TemplateLoadedChange
AbstractTemplateLoadedChange
├── SimpleTemplateLoadedChange (for AbstractSimpleTemplate)
└── SteppableTemplateLoadedChange (for AbstractSteppableTemplate)
↓ (execution preparation)
TemplateExecutableTask
TemplateExecutableTask<T>
├── SimpleTemplateExecutableTask (calls setStep())
└── SteppableTemplateExecutableTask (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 loaded change (`core/flamingock-core`)
- `TemplateExecutableTask` - Executes template with dependency injection (`core/flamingock-core`)
- `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`)

### Discovery Mechanism (SPI)

Expand Down Expand Up @@ -430,6 +435,50 @@ Templates are validated at compile-time to ensure YAML structure matches the tem
- `io.flamingock.api.annotations.EnableFlamingock` - strictTemplateValidation flag
- `io.flamingock.api.template.AbstractChangeTemplate` - template base classes

<<<<<<< Updated upstream
=======
### Template Class Hierarchy (Loaded & Executable)

At the **Loaded** and **Executable** phases, templates are split into type-specific classes:

**Loaded Phase:**
```
AbstractTemplateLoadedChange (abstract base)
├── SimpleTemplateLoadedChange (apply, rollback)
└── SteppableTemplateLoadedChange (steps)
```

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

**Type Detection:** Happens in `TemplateLoadedTaskBuilder.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

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

**Apply Phase:**
- Iterates through steps in order (0 → N-1)
- Sets `applyPayload` on template instance before executing `@Apply` method
- `stepIndex` tracks current progress (-1 before start, N-1 after all steps complete)
- On failure at step N, `stepIndex = N` (points to failed step)

**Rollback Phase (on apply failure):**
- Rolls back from current `stepIndex` down to 0 (reverse order)
- Sets `rollbackPayload` on template instance before executing `@Rollback` method
- **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.

### Dependency Injection in Templates

Template methods (`@Apply`, `@Rollback`) receive dependencies as **method parameters**, not constructor injection:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright 2025 Flamingock (https://www.flamingock.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.flamingock.api.annotations;

import io.flamingock.api.template.AbstractChangeTemplate;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Marks a class as a Flamingock change template and configures its execution mode.
*
* <p>All template classes must extend {@link AbstractChangeTemplate} and be annotated with
* this annotation to specify whether they process single or multiple steps.
*
* <p><b>Simple templates</b> (default, {@code steppable = false}):
* <pre>
* id: create-users-table
* template: SqlTemplate
* apply: "CREATE TABLE users (id INT PRIMARY KEY)"
* rollback: "DROP TABLE users"
* </pre>
*
* <p><b>Steppable templates</b> ({@code steppable = true}) process multiple operations:
* <pre>
* id: setup-orders
* template: MongoTemplate
* steps:
* - apply: { type: createCollection, collection: orders }
* rollback: { type: dropCollection, collection: orders }
* - apply: { type: insert, collection: orders, ... }
* rollback: { type: delete, collection: orders, ... }
* </pre>
*
* <p><b>Steppable rollback behavior:</b>
* <ul>
* <li>On failure, previously successful steps are rolled back in reverse order</li>
* <li>Steps without rollback are skipped during rollback</li>
* </ul>
*
* @see AbstractChangeTemplate
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ChangeTemplate {

/**
* When {@code true}, the template expects a {@code steps} array in YAML.
* When {@code false} (default), it expects {@code apply} and optional {@code rollback} at root.
*
* @return {@code true} for steppable templates, {@code false} for simple templates
*/
boolean multiStep() default false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,20 @@


/**
* Abstract base class for change templates providing common functionality.
* Base class for creating Flamingock change templates.
*
* <p>This class handles generic type resolution and provides the common fields
* needed by all templates: changeId, isTransactional, and configuration.
* <p>Extend this class and annotate with {@link io.flamingock.api.annotations.ChangeTemplate}
* to create custom templates. The annotation's {@code steppable} attribute determines the YAML structure.
*
* <p>For new templates, extend one of the specialized abstract classes:
* <ul>
* <li>{@link AbstractSimpleTemplate} - for templates with a single apply/rollback step</li>
* <li>{@link AbstractSteppableTemplate} - for templates with multiple steps</li>
* </ul>
* <p>Use {@code @ChangeTemplate} (default {@code steppable = false}) for simple templates with
* single apply/rollback fields.
*
* <p>Use {@code @ChangeTemplate(steppable = true)} for steppable templates with multiple steps.
*
* @param <SHARED_CONFIGURATION_FIELD> shared configuration type (use {@code Void} if none)
* @param <APPLY_FIELD> apply payload type
* @param <ROLLBACK_FIELD> rollback payload type
* @see io.flamingock.api.annotations.ChangeTemplate
*/
public abstract class AbstractChangeTemplate<SHARED_CONFIGURATION_FIELD, APPLY_FIELD, ROLLBACK_FIELD> implements ChangeTemplate<SHARED_CONFIGURATION_FIELD, APPLY_FIELD, ROLLBACK_FIELD> {

Expand All @@ -42,7 +46,10 @@ public abstract class AbstractChangeTemplate<SHARED_CONFIGURATION_FIELD, APPLY_F
private final Class<ROLLBACK_FIELD> rollbackPayloadClass;
protected String changeId;
protected boolean isTransactional;

protected SHARED_CONFIGURATION_FIELD configuration;
protected APPLY_FIELD applyPayload;
protected ROLLBACK_FIELD rollbackPayload;

private final Set<Class<?>> additionalReflectiveClasses;

Expand Down Expand Up @@ -111,6 +118,16 @@ public void setConfiguration(SHARED_CONFIGURATION_FIELD configuration) {
this.configuration = configuration;
}

@Override
public void setApplyPayload(APPLY_FIELD applyPayload) {
this.applyPayload = applyPayload;
}

@Override
public void setRollbackPayload(ROLLBACK_FIELD rollbackPayload) {
this.rollbackPayload = rollbackPayload;
}

@Override
public Class<SHARED_CONFIGURATION_FIELD> getConfigurationClass() {
return configurationClass;
Expand Down

This file was deleted.

Loading
Loading