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
155 changes: 154 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,4 +256,157 @@ All Java and Kotlin source files must include the Flamingock license header:

## Execution Flow Architecture

**📖 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.
**📖 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.

## Templates System (Deep Dive)

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.

### Purpose and Motivation

1. **Reduce code duplication** - Common patterns (create tables, insert data) are standardized
2. **Enable non-developers** - Business analysts and DBAs can create migrations without Java
3. **Declarative over imperative** - YAML is more readable for well-defined operations
4. **GraalVM support** - Templates enable proper reflection registration at build time

### Core Architecture

**Interface**: `ChangeTemplate<SHARED_CONFIG, APPLY_FIELD, ROLLBACK_FIELD>`
- `SHARED_CONFIG` - Configuration shared between apply and rollback (use `Void` if not needed)
- `APPLY_FIELD` - Payload type for the apply operation
- `ROLLBACK_FIELD` - Payload type for the rollback operation

**Base Class**: `AbstractChangeTemplate` resolves generic types via reflection and provides:
- Field management: `changeId`, `isTransactional`, `configuration`, `applyPayload`, `rollbackPayload`
- Reflective class collection for GraalVM native image support

**Key Files**:
- `core/flamingock-core-api/src/main/java/io/flamingock/api/template/ChangeTemplate.java`
- `core/flamingock-core-api/src/main/java/io/flamingock/api/template/AbstractChangeTemplate.java`
- `core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/template/ChangeTemplateManager.java`

### Existing Implementations

| Template | Location | CONFIG | APPLY | ROLLBACK |
|----------|----------|--------|-------|----------|
| `SqlTemplate` | `templates/flamingock-sql-template` | `Void` | `String` (raw SQL) | `String` (raw SQL) |
| `MongoChangeTemplate` | `templates/flamingock-mongodb-sync-template` | `Void` | `MongoOperation` | `MongoOperation` |

### YAML Structure

```yaml
id: create-users-table # Unique identifier
author: developer-name # Optional author
transactional: true # Default: true
template: SqlTemplate # Template class name (simple name)
targetSystem:
id: "postgresql" # Must match registered target system
apply: "CREATE TABLE users ..." # Payload for apply (type depends on template)
rollback: "DROP TABLE users" # Optional: payload for rollback
recovery:
strategy: MANUAL_INTERVENTION # Or ALWAYS_RETRY
```

### Execution Flow

```
YAML File
↓ (parsing)
ChangeTemplateFileContent
↓ (preview building)
TemplatePreviewChange
↓ (loaded task building - template lookup from registry)
TemplateLoadedChange
↓ (execution preparation)
TemplateExecutableTask
↓ (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`)

### Discovery Mechanism (SPI)

Templates are discovered via Java's `ServiceLoader`:

**Direct Registration**:
```
META-INF/services/io.flamingock.api.template.ChangeTemplate
→ io.flamingock.template.sql.SqlTemplate
```

**Factory Registration** (for multiple templates):
```
META-INF/services/io.flamingock.internal.common.core.template.ChangeTemplateFactory
→ com.example.MyTemplateFactory
```

**ChangeTemplateManager** loads all templates at startup and provides lookup by simple class name.

### Ordering

Change execution order is determined **solely by filename convention**:
- `_0001__create_users.yaml` runs before `_0002__seed_data.yaml`
- No explicit `order` field in YAML; order comes from filename prefix

### Transactionality and Rollback

- `transactional: true` is the **default**
- For ACID databases, Flamingock manages rollback automatically via native DB transactions
- Manual rollback is **optional but recommended** because:
- Required for non-transactional operations (e.g., MongoDB DDL)
- Used by CLI `UNDO` operation to revert already-committed changes

### Recovery Strategy

When a change fails and cannot be rolled back:

| Strategy | Behavior |
|----------|----------|
| `MANUAL_INTERVENTION` (default) | Requires user intervention before retry |
| `ALWAYS_RETRY` | Safe to retry automatically (for idempotent operations) |

**File**: `core/flamingock-core-api/src/main/java/io/flamingock/api/RecoveryStrategy.java`

### Dependency Injection in Templates

Template methods (`@Apply`, `@Rollback`) receive dependencies as **method parameters**, not constructor injection:

```java
@Apply
public void apply(Connection connection) { // Injected from context
execute(connection, applyPayload);
}
```

Dependencies are resolved from the `ContextResolver` based on type matching.

### Creating Custom Templates

1. Extend `AbstractChangeTemplate<CONFIG, APPLY, ROLLBACK>`
2. Implement `@Apply` method (required)
3. Implement `@Rollback` method (optional but recommended)
4. Register in `META-INF/services/io.flamingock.api.template.ChangeTemplate`

**Documentation**: https://docs.flamingock.io/templates/create-your-own-template

### GraalVM Support

Templates must declare reflective classes for native image compilation:
- Override `getReflectiveClasses()` in template
- Pass additional classes to `AbstractChangeTemplate` constructor
- `RegistrationFeature` in `flamingock-graalvm` module handles registration

### Evolution Proposals

See `docs/TEMPLATES_EVOLUTION_PROPOSALS.md` for comprehensive proposals including:
- Variables and interpolation
- Dry-run/Plan mode
- JSON Schema validation
- Explicit dependencies (`depends_on`)
- Policy as Code
- And more...
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import io.flamingock.internal.util.Constants;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
Expand All @@ -45,22 +46,23 @@ public class FlamingockAutoConfiguration {
@Bean("flamingock-runner")
@Profile(Constants.NON_CLI_PROFILE)
@ConditionalOnExpression("'${flamingock.runner-type:ApplicationRunner}'.toLowerCase().equals('applicationrunner')")
public ApplicationRunner applicationRunner(RunnerBuilder runnerBuilder) {
return SpringbootUtil.toApplicationRunner(runnerBuilder.build());
public ApplicationRunner applicationRunner(RunnerBuilder runnerBuilder,
@Value("${flamingock.autorun:true}") boolean autoRun) {
return SpringbootUtil.toApplicationRunner(runnerBuilder.build(), autoRun);
}

@Bean("flamingock-runner")
@Profile(Constants.NON_CLI_PROFILE)
@ConditionalOnExpression("'${flamingock.runner-type:null}'.toLowerCase().equals('initializingbean')")
public InitializingBean initializingBeanRunner(RunnerBuilder runnerBuilder) {
return SpringbootUtil.toInitializingBean(runnerBuilder.build());
public InitializingBean initializingBeanRunner(RunnerBuilder runnerBuilder,
@Value("${flamingock.autorun:true}") boolean autoRun) {
return SpringbootUtil.toInitializingBean(runnerBuilder.build(), autoRun);
}

@Bean("flamingock-builder")
@Profile(Constants.NON_CLI_PROFILE)
@ConditionalOnMissingBean(RunnerBuilder.class)

public RunnerBuilder flamingockBuilder(SpringbootProperties configurationProperties,
public AbstractChangeRunnerBuilder<?,?> flamingockBuilder(SpringbootProperties configurationProperties,
ApplicationContext springContext,
ApplicationEventPublisher applicationEventPublisher,
@Autowired(required = false) CommunityAuditStore auditStore,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,35 @@
package io.flamingock.springboot;

import io.flamingock.internal.core.runner.Runner;
import io.flamingock.internal.util.log.FlamingockLoggerFactory;
import org.slf4j.Logger;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.ApplicationContext;

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

private SpringbootUtil() {
}

public static InitializingBean toInitializingBean(Runner runner) {
return runner::run;
public static InitializingBean toInitializingBean(Runner runner, boolean autoRun) {
return () -> runIfApply(runner, autoRun);
}

public static ApplicationRunner toApplicationRunner(Runner runner) {
return args -> runner.run();
public static ApplicationRunner toApplicationRunner(Runner runner, boolean autoRun) {
return args -> runIfApply(runner, autoRun);
}

private static void runIfApply(Runner runner, boolean autoRun) {
if(autoRun) {
runner.run();
} else {
logger.info(
"Flamingock automatic execution is disabled (flamingock.autorun=false). " +
"Changes will not be executed at startup."
);
}
}

public static String[] getActiveProfiles(ApplicationContext springContext) {
Expand Down
Loading