Skip to content

Commit a05d92e

Browse files
authored
refactor: multistep support for ChangeTemplate (#838)
1 parent 0bee4e9 commit a05d92e

43 files changed

Lines changed: 1612 additions & 814 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CLAUDE.md

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -338,20 +338,25 @@ YAML File
338338
↓ (parsing)
339339
ChangeTemplateFileContent
340340
↓ (preview building)
341-
TemplatePreviewChange
341+
TemplatePreviewChange (unified)
342342
↓ (loaded task building - template lookup from registry)
343-
TemplateLoadedChange
343+
AbstractTemplateLoadedChange
344+
├── SimpleTemplateLoadedChange (for AbstractSimpleTemplate)
345+
└── SteppableTemplateLoadedChange (for AbstractSteppableTemplate)
344346
↓ (execution preparation)
345-
TemplateExecutableTask
347+
TemplateExecutableTask<T>
348+
├── SimpleTemplateExecutableTask (calls setStep())
349+
└── SteppableTemplateExecutableTask (calls setSteps())
346350
↓ (runtime execution)
347351
Template instance with injected dependencies
348352
```
349353

350354
**Key Classes in Flow**:
351355
- `ChangeTemplateFileContent` - YAML parsed data (`core/flamingock-core-commons`)
352356
- `TemplatePreviewTaskBuilder` - Builds preview from file content (`core/flamingock-core-commons`)
353-
- `TemplateLoadedTaskBuilder` - Resolves template class, builds loaded change (`core/flamingock-core`)
354-
- `TemplateExecutableTask` - Executes template with dependency injection (`core/flamingock-core`)
357+
- `TemplateLoadedTaskBuilder` - Resolves template class, builds type-specific loaded change (`core/flamingock-core`)
358+
- `TemplateExecutableTaskBuilder` - Builds type-specific executable task (`core/flamingock-core`)
359+
- `TemplateExecutableTask<T>` - Abstract base for template execution (`core/flamingock-core`)
355360

356361
### Discovery Mechanism (SPI)
357362

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

438+
<<<<<<< Updated upstream
439+
=======
440+
### Template Class Hierarchy (Loaded & Executable)
441+
442+
At the **Loaded** and **Executable** phases, templates are split into type-specific classes:
443+
444+
**Loaded Phase:**
445+
```
446+
AbstractTemplateLoadedChange (abstract base)
447+
├── SimpleTemplateLoadedChange (apply, rollback)
448+
└── SteppableTemplateLoadedChange (steps)
449+
```
450+
451+
**Executable Phase:**
452+
```
453+
TemplateExecutableTask<T> (abstract base)
454+
├── SimpleTemplateExecutableTask (calls setStep())
455+
└── SteppableTemplateExecutableTask (calls setSteps())
456+
```
457+
458+
**Type Detection:** Happens in `TemplateLoadedTaskBuilder.build()` using:
459+
- `AbstractSteppableTemplate.class.isAssignableFrom(templateClass)` → SteppableTemplateLoadedChange
460+
- Otherwise → SimpleTemplateLoadedChange (default for AbstractSimpleTemplate and unknown types)
461+
462+
**Note:** Preview phase (`TemplatePreviewChange`) remains unified since YAML is parsed before template type is known.
463+
464+
### SteppableTemplateExecutableTask Apply/Rollback Lifecycle
465+
466+
The `SteppableTemplateExecutableTask` manages multi-step execution with per-step rollback:
467+
468+
**Apply Phase:**
469+
- Iterates through steps in order (0 → N-1)
470+
- Sets `applyPayload` on template instance before executing `@Apply` method
471+
- `stepIndex` tracks current progress (-1 before start, N-1 after all steps complete)
472+
- On failure at step N, `stepIndex = N` (points to failed step)
473+
474+
**Rollback Phase (on apply failure):**
475+
- Rolls back from current `stepIndex` down to 0 (reverse order)
476+
- Sets `rollbackPayload` on template instance before executing `@Rollback` method
477+
- **Skips steps without rollback payload** (`hasRollback()` returns false)
478+
- **Skips if template has no `@Rollback` method** (logs warning)
479+
480+
**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.
481+
433482
### Dependency Injection in Templates
434483

435484
Template methods (`@Apply`, `@Rollback`) receive dependencies as **method parameters**, not constructor injection:
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2025 Flamingock (https://www.flamingock.io)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.flamingock.api.annotations;
17+
18+
import io.flamingock.api.template.AbstractChangeTemplate;
19+
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
/**
26+
* Marks a class as a Flamingock change template and configures its execution mode.
27+
*
28+
* <p>All template classes must extend {@link AbstractChangeTemplate} and be annotated with
29+
* this annotation to specify whether they process single or multiple steps.
30+
*
31+
* <p><b>Simple templates</b> (default, {@code steppable = false}):
32+
* <pre>
33+
* id: create-users-table
34+
* template: SqlTemplate
35+
* apply: "CREATE TABLE users (id INT PRIMARY KEY)"
36+
* rollback: "DROP TABLE users"
37+
* </pre>
38+
*
39+
* <p><b>Steppable templates</b> ({@code steppable = true}) process multiple operations:
40+
* <pre>
41+
* id: setup-orders
42+
* template: MongoTemplate
43+
* steps:
44+
* - apply: { type: createCollection, collection: orders }
45+
* rollback: { type: dropCollection, collection: orders }
46+
* - apply: { type: insert, collection: orders, ... }
47+
* rollback: { type: delete, collection: orders, ... }
48+
* </pre>
49+
*
50+
* <p><b>Steppable rollback behavior:</b>
51+
* <ul>
52+
* <li>On failure, previously successful steps are rolled back in reverse order</li>
53+
* <li>Steps without rollback are skipped during rollback</li>
54+
* </ul>
55+
*
56+
* @see AbstractChangeTemplate
57+
*/
58+
@Retention(RetentionPolicy.RUNTIME)
59+
@Target(ElementType.TYPE)
60+
public @interface ChangeTemplate {
61+
62+
/**
63+
* When {@code true}, the template expects a {@code steps} array in YAML.
64+
* When {@code false} (default), it expects {@code apply} and optional {@code rollback} at root.
65+
*
66+
* @return {@code true} for steppable templates, {@code false} for simple templates
67+
*/
68+
boolean multiStep() default false;
69+
}

core/flamingock-core-api/src/main/java/io/flamingock/api/template/AbstractChangeTemplate.java

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,20 @@
2424

2525

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

@@ -42,7 +46,10 @@ public abstract class AbstractChangeTemplate<SHARED_CONFIGURATION_FIELD, APPLY_F
4246
private final Class<ROLLBACK_FIELD> rollbackPayloadClass;
4347
protected String changeId;
4448
protected boolean isTransactional;
49+
4550
protected SHARED_CONFIGURATION_FIELD configuration;
51+
protected APPLY_FIELD applyPayload;
52+
protected ROLLBACK_FIELD rollbackPayload;
4653

4754
private final Set<Class<?>> additionalReflectiveClasses;
4855

@@ -111,6 +118,16 @@ public void setConfiguration(SHARED_CONFIGURATION_FIELD configuration) {
111118
this.configuration = configuration;
112119
}
113120

121+
@Override
122+
public void setApplyPayload(APPLY_FIELD applyPayload) {
123+
this.applyPayload = applyPayload;
124+
}
125+
126+
@Override
127+
public void setRollbackPayload(ROLLBACK_FIELD rollbackPayload) {
128+
this.rollbackPayload = rollbackPayload;
129+
}
130+
114131
@Override
115132
public Class<SHARED_CONFIGURATION_FIELD> getConfigurationClass() {
116133
return configurationClass;

core/flamingock-core-api/src/main/java/io/flamingock/api/template/AbstractSimpleTemplate.java

Lines changed: 0 additions & 100 deletions
This file was deleted.

0 commit comments

Comments
 (0)