Skip to content

Commit 07bb838

Browse files
authored
refactor(test-support): flamingock.autorun property in springboot (#765)
1 parent d9f7548 commit 07bb838

File tree

3 files changed

+180
-11
lines changed

3 files changed

+180
-11
lines changed

CLAUDE.md

Lines changed: 154 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,4 +256,157 @@ All Java and Kotlin source files must include the Flamingock license header:
256256

257257
## Execution Flow Architecture
258258

259-
**📖 Complete Documentation**: See `docs/EXECUTION_FLOW_GUIDE.md` for comprehensive execution flow from builder through pipeline completion, including StageExecutor, ExecutionPlanner, StepNavigator, transaction handling, and rollback mechanisms.
259+
**📖 Complete Documentation**: See `docs/EXECUTION_FLOW_GUIDE.md` for comprehensive execution flow from builder through pipeline completion, including StageExecutor, ExecutionPlanner, StepNavigator, transaction handling, and rollback mechanisms.
260+
261+
## Templates System (Deep Dive)
262+
263+
Templates are **reusable, declarative change definitions** that enable "no-code migrations". Instead of writing Java classes with `@Change` annotations, developers define changes in YAML files.
264+
265+
### Purpose and Motivation
266+
267+
1. **Reduce code duplication** - Common patterns (create tables, insert data) are standardized
268+
2. **Enable non-developers** - Business analysts and DBAs can create migrations without Java
269+
3. **Declarative over imperative** - YAML is more readable for well-defined operations
270+
4. **GraalVM support** - Templates enable proper reflection registration at build time
271+
272+
### Core Architecture
273+
274+
**Interface**: `ChangeTemplate<SHARED_CONFIG, APPLY_FIELD, ROLLBACK_FIELD>`
275+
- `SHARED_CONFIG` - Configuration shared between apply and rollback (use `Void` if not needed)
276+
- `APPLY_FIELD` - Payload type for the apply operation
277+
- `ROLLBACK_FIELD` - Payload type for the rollback operation
278+
279+
**Base Class**: `AbstractChangeTemplate` resolves generic types via reflection and provides:
280+
- Field management: `changeId`, `isTransactional`, `configuration`, `applyPayload`, `rollbackPayload`
281+
- Reflective class collection for GraalVM native image support
282+
283+
**Key Files**:
284+
- `core/flamingock-core-api/src/main/java/io/flamingock/api/template/ChangeTemplate.java`
285+
- `core/flamingock-core-api/src/main/java/io/flamingock/api/template/AbstractChangeTemplate.java`
286+
- `core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/template/ChangeTemplateManager.java`
287+
288+
### Existing Implementations
289+
290+
| Template | Location | CONFIG | APPLY | ROLLBACK |
291+
|----------|----------|--------|-------|----------|
292+
| `SqlTemplate` | `templates/flamingock-sql-template` | `Void` | `String` (raw SQL) | `String` (raw SQL) |
293+
| `MongoChangeTemplate` | `templates/flamingock-mongodb-sync-template` | `Void` | `MongoOperation` | `MongoOperation` |
294+
295+
### YAML Structure
296+
297+
```yaml
298+
id: create-users-table # Unique identifier
299+
author: developer-name # Optional author
300+
transactional: true # Default: true
301+
template: SqlTemplate # Template class name (simple name)
302+
targetSystem:
303+
id: "postgresql" # Must match registered target system
304+
apply: "CREATE TABLE users ..." # Payload for apply (type depends on template)
305+
rollback: "DROP TABLE users" # Optional: payload for rollback
306+
recovery:
307+
strategy: MANUAL_INTERVENTION # Or ALWAYS_RETRY
308+
```
309+
310+
### Execution Flow
311+
312+
```
313+
YAML File
314+
↓ (parsing)
315+
ChangeTemplateFileContent
316+
↓ (preview building)
317+
TemplatePreviewChange
318+
↓ (loaded task building - template lookup from registry)
319+
TemplateLoadedChange
320+
↓ (execution preparation)
321+
TemplateExecutableTask
322+
↓ (runtime execution)
323+
Template instance with injected dependencies
324+
```
325+
326+
**Key Classes in Flow**:
327+
- `ChangeTemplateFileContent` - YAML parsed data (`core/flamingock-core-commons`)
328+
- `TemplatePreviewTaskBuilder` - Builds preview from file content (`core/flamingock-core-commons`)
329+
- `TemplateLoadedTaskBuilder` - Resolves template class, builds loaded change (`core/flamingock-core`)
330+
- `TemplateExecutableTask` - Executes template with dependency injection (`core/flamingock-core`)
331+
332+
### Discovery Mechanism (SPI)
333+
334+
Templates are discovered via Java's `ServiceLoader`:
335+
336+
**Direct Registration**:
337+
```
338+
META-INF/services/io.flamingock.api.template.ChangeTemplate
339+
→ io.flamingock.template.sql.SqlTemplate
340+
```
341+
342+
**Factory Registration** (for multiple templates):
343+
```
344+
META-INF/services/io.flamingock.internal.common.core.template.ChangeTemplateFactory
345+
→ com.example.MyTemplateFactory
346+
```
347+
348+
**ChangeTemplateManager** loads all templates at startup and provides lookup by simple class name.
349+
350+
### Ordering
351+
352+
Change execution order is determined **solely by filename convention**:
353+
- `_0001__create_users.yaml` runs before `_0002__seed_data.yaml`
354+
- No explicit `order` field in YAML; order comes from filename prefix
355+
356+
### Transactionality and Rollback
357+
358+
- `transactional: true` is the **default**
359+
- For ACID databases, Flamingock manages rollback automatically via native DB transactions
360+
- Manual rollback is **optional but recommended** because:
361+
- Required for non-transactional operations (e.g., MongoDB DDL)
362+
- Used by CLI `UNDO` operation to revert already-committed changes
363+
364+
### Recovery Strategy
365+
366+
When a change fails and cannot be rolled back:
367+
368+
| Strategy | Behavior |
369+
|----------|----------|
370+
| `MANUAL_INTERVENTION` (default) | Requires user intervention before retry |
371+
| `ALWAYS_RETRY` | Safe to retry automatically (for idempotent operations) |
372+
373+
**File**: `core/flamingock-core-api/src/main/java/io/flamingock/api/RecoveryStrategy.java`
374+
375+
### Dependency Injection in Templates
376+
377+
Template methods (`@Apply`, `@Rollback`) receive dependencies as **method parameters**, not constructor injection:
378+
379+
```java
380+
@Apply
381+
public void apply(Connection connection) { // Injected from context
382+
execute(connection, applyPayload);
383+
}
384+
```
385+
386+
Dependencies are resolved from the `ContextResolver` based on type matching.
387+
388+
### Creating Custom Templates
389+
390+
1. Extend `AbstractChangeTemplate<CONFIG, APPLY, ROLLBACK>`
391+
2. Implement `@Apply` method (required)
392+
3. Implement `@Rollback` method (optional but recommended)
393+
4. Register in `META-INF/services/io.flamingock.api.template.ChangeTemplate`
394+
395+
**Documentation**: https://docs.flamingock.io/templates/create-your-own-template
396+
397+
### GraalVM Support
398+
399+
Templates must declare reflective classes for native image compilation:
400+
- Override `getReflectiveClasses()` in template
401+
- Pass additional classes to `AbstractChangeTemplate` constructor
402+
- `RegistrationFeature` in `flamingock-graalvm` module handles registration
403+
404+
### Evolution Proposals
405+
406+
See `docs/TEMPLATES_EVOLUTION_PROPOSALS.md` for comprehensive proposals including:
407+
- Variables and interpolation
408+
- Dry-run/Plan mode
409+
- JSON Schema validation
410+
- Explicit dependencies (`depends_on`)
411+
- Policy as Code
412+
- And more...

platform-plugins/flamingock-springboot-integration/src/main/java/io/flamingock/springboot/FlamingockAutoConfiguration.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import io.flamingock.internal.util.Constants;
2424
import org.springframework.beans.factory.InitializingBean;
2525
import org.springframework.beans.factory.annotation.Autowired;
26+
import org.springframework.beans.factory.annotation.Value;
2627
import org.springframework.boot.ApplicationRunner;
2728
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2829
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
@@ -45,22 +46,23 @@ public class FlamingockAutoConfiguration {
4546
@Bean("flamingock-runner")
4647
@Profile(Constants.NON_CLI_PROFILE)
4748
@ConditionalOnExpression("'${flamingock.runner-type:ApplicationRunner}'.toLowerCase().equals('applicationrunner')")
48-
public ApplicationRunner applicationRunner(RunnerBuilder runnerBuilder) {
49-
return SpringbootUtil.toApplicationRunner(runnerBuilder.build());
49+
public ApplicationRunner applicationRunner(RunnerBuilder runnerBuilder,
50+
@Value("${flamingock.autorun:true}") boolean autoRun) {
51+
return SpringbootUtil.toApplicationRunner(runnerBuilder.build(), autoRun);
5052
}
5153

5254
@Bean("flamingock-runner")
5355
@Profile(Constants.NON_CLI_PROFILE)
5456
@ConditionalOnExpression("'${flamingock.runner-type:null}'.toLowerCase().equals('initializingbean')")
55-
public InitializingBean initializingBeanRunner(RunnerBuilder runnerBuilder) {
56-
return SpringbootUtil.toInitializingBean(runnerBuilder.build());
57+
public InitializingBean initializingBeanRunner(RunnerBuilder runnerBuilder,
58+
@Value("${flamingock.autorun:true}") boolean autoRun) {
59+
return SpringbootUtil.toInitializingBean(runnerBuilder.build(), autoRun);
5760
}
5861

5962
@Bean("flamingock-builder")
6063
@Profile(Constants.NON_CLI_PROFILE)
6164
@ConditionalOnMissingBean(RunnerBuilder.class)
62-
63-
public RunnerBuilder flamingockBuilder(SpringbootProperties configurationProperties,
65+
public AbstractChangeRunnerBuilder<?,?> flamingockBuilder(SpringbootProperties configurationProperties,
6466
ApplicationContext springContext,
6567
ApplicationEventPublisher applicationEventPublisher,
6668
@Autowired(required = false) CommunityAuditStore auditStore,

platform-plugins/flamingock-springboot-integration/src/main/java/io/flamingock/springboot/SpringbootUtil.java

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,35 @@
1616
package io.flamingock.springboot;
1717

1818
import io.flamingock.internal.core.runner.Runner;
19+
import io.flamingock.internal.util.log.FlamingockLoggerFactory;
20+
import org.slf4j.Logger;
1921
import org.springframework.beans.factory.InitializingBean;
2022
import org.springframework.boot.ApplicationRunner;
2123
import org.springframework.context.ApplicationContext;
2224

2325
public final class SpringbootUtil {
26+
private static final Logger logger = FlamingockLoggerFactory.getLogger("Springboot");
2427

2528
private SpringbootUtil() {
2629
}
2730

28-
public static InitializingBean toInitializingBean(Runner runner) {
29-
return runner::run;
31+
public static InitializingBean toInitializingBean(Runner runner, boolean autoRun) {
32+
return () -> runIfApply(runner, autoRun);
3033
}
3134

32-
public static ApplicationRunner toApplicationRunner(Runner runner) {
33-
return args -> runner.run();
35+
public static ApplicationRunner toApplicationRunner(Runner runner, boolean autoRun) {
36+
return args -> runIfApply(runner, autoRun);
37+
}
38+
39+
private static void runIfApply(Runner runner, boolean autoRun) {
40+
if(autoRun) {
41+
runner.run();
42+
} else {
43+
logger.info(
44+
"Flamingock automatic execution is disabled (flamingock.autorun=false). " +
45+
"Changes will not be executed at startup."
46+
);
47+
}
3448
}
3549

3650
public static String[] getActiveProfiles(ApplicationContext springContext) {

0 commit comments

Comments
 (0)