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
87 changes: 87 additions & 0 deletions inventory-orders-service/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

This is an example project demonstrating Flamingock, a Change-as-Code execution engine. It simulates an e-commerce inventory & orders service that coordinates versioned changes across multiple target systems: MongoDB, Kafka Schema Registry, and LaunchDarkly (used here as an external configuration system, not runtime flag evaluation).

**What Flamingock is:** A Change-as-Code execution engine that safely, deterministically, and auditably applies versioned changes to external systems at application startup. The goal is to ensure that all dependent external systems evolve in a synchronized and auditable way with the application. Think "Liquibase for all external systems, with auditability and safety as first-class constraints."

**What Flamingock is NOT:** Not a database migration tool, not Infrastructure-as-Code (Terraform/Pulumi), not a CI/CD pipeline, not runtime feature flagging.

## Build Commands

```bash
./gradlew build # Build the project
./gradlew run # Run the application (executes pending changes at startup)
./gradlew test # Run integration tests with Testcontainers (no external services needed)
./gradlew clean # Clean build artifacts
```

## Running the Application

**Option 1: With Docker Compose (for manual execution)**
```bash
docker-compose up -d # Start MongoDB, Kafka, Schema Registry, LaunchDarkly mock
./gradlew run # Execute pending changes
```

**Option 2: With Testcontainers (for testing)**
```bash
./gradlew test # Starts all containers automatically via Testcontainers
```

## Architecture

### Flamingock Core Concepts

- **Change** - The atomic unit of change. Defines what system it targets, what version it represents, and how it is applied. Changes are versioned, ordered, immutable once applied, and should be idempotent.
- **Audit Store** - Records execution state (STARTED, COMPLETED). Intentionally separate from target systems to enable safe retries, manual intervention, and cross-system governance.
- **Target System** - The external system being modified. Flamingock does not assume target systems are transactional; safety is enforced via execution tracking.

### This Project's Setup

- Entry point: `InventoryOrdersApp.java` with `@EnableFlamingock` annotation
- Changes discovered from `io.flamingock.examples.inventory.changes` package
- Uses compile-time annotation processing via `flamingock-processor`

### Target Systems (defined in `TargetSystems.java`)
1. **mongodb-inventory** - MongoDB operations (also used as Audit Store in this example; in real setups, the Audit Store may be a dedicated system)
2. **kafka-inventory** - Kafka Schema Registry operations
3. **toggle-inventory** - LaunchDarkly as external configuration system (not runtime flag evaluation)

### Change Naming Convention
In this example repository, changes follow the pattern: `_NNNN__[target]_[description].java`
- Example: `_0001__mongodb_addDiscountCodeFieldToOrders.java`
- Auto-ordered by numeric prefix
- Each change class has an `@Apply` method and may optionally define a `@Rollback` for reconciliation or manual recovery

### Utility Classes (`util/` package)
- `MongoDBUtil` - MongoDB connection string builder
- `KafkaSchemaManager` - Schema Registry REST operations
- `LaunchDarklyClient` - Feature flag CRUD via REST API
- `ConfigFileManager` - YAML config file handling

## Technology Stack
- Java 17
- Flamingock v1.0.0-beta.5
- MongoDB Driver Sync v5.5.1
- Apache Kafka v3.7.0 with Avro v1.11.3
- Confluent Schema Registry Client v7.5.0
- OkHttp3 v4.12.0 (for REST calls)
- Testcontainers v2.0.2 (for integration tests)

## Infrastructure Services (docker-compose.yml)
| Service | Port | Purpose |
|-------------------|-------|-----------------------------------------------|
| MongoDB | 27017 | Target system & Audit Store (in this example) |
| Kafka | 9092 | Event streaming |
| Schema Registry | 8081 | Avro schema management |
| LaunchDarkly Mock | 8765 | External configuration system simulation |

## Testing
Integration tests use Testcontainers to spin up isolated Docker containers:
- `SuccessExecutionTest.java` tests all 7 changes
- Tests verify MongoDB documents, Kafka schemas, and audit log entries
- No external Docker Compose required for tests
4 changes: 2 additions & 2 deletions inventory-orders-service/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ curl http://localhost:8081/subjects/order-created-value/versions

Check the audit logs in MongoDB:
```bash
docker exec -it inventory-mongodb mongosh inventory --eval 'db.flamingockAuditLogs.find().pretty()'
docker exec -it inventory-mongodb mongosh inventory --eval 'db.flamingockAuditLog.find().pretty()'
```

5. **Clean up when done:**
Expand Down Expand Up @@ -380,7 +380,7 @@ After running the migrations, you'll see:
- Orders in MongoDB with discount fields populated
- Two schema versions in Schema Registry (V1 and V2)
- LaunchDarkly Management API calls for feature flag creation/archival via mock server
- Complete audit trail in the flamingockAuditLogs collection
- Complete audit trail in the flamingockAuditLog collection

## Architecture Notes

Expand Down
12 changes: 10 additions & 2 deletions inventory-orders-service/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ plugins {
java
application
idea
id("org.springframework.boot") version "3.2.0"
id("io.spring.dependency-management") version "1.1.4"
}

java {
Expand All @@ -26,7 +28,7 @@ repositories {
group = "io.flamingock"
version = "1.0-SNAPSHOT"

val flamingockVersion = "1.0.0-beta.4"
val flamingockVersion = "1.0.0-beta.5"
logger.lifecycle("Building with flamingock version: $flamingockVersion")

val mongodbVersion = "5.5.1"
Expand All @@ -39,6 +41,9 @@ dependencies {
// Flamingock Dependencies
implementation(platform("io.flamingock:flamingock-community-bom:$flamingockVersion"))
implementation("io.flamingock:flamingock-community")
implementation("io.flamingock:flamingock-springboot-integration")


// Optional: enable GraalVM native image support for Flamingock
// See: https://docs.flamingock.io/frameworks/graalvm
// Uncomment
Expand All @@ -62,8 +67,11 @@ dependencies {
// HTTP client for LaunchDarkly Management API
implementation("com.squareup.okhttp3:okhttp:4.12.0")

// Spring Boot
implementation("org.springframework.boot:spring-boot-starter-web")

// Others dependencies needed for this example
implementation("org.slf4j:slf4j-simple:2.0.6")
// implementation("org.slf4j:slf4j-simple:2.0.6") // Commented out - Spring Boot provides logging

testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.2")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.9.2")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package io.flamingock.examples.inventory;

import com.mongodb.client.MongoClient;
import io.flamingock.community.mongodb.sync.driver.MongoDBSyncAuditStore;
import io.flamingock.examples.inventory.util.MongoDBUtil;
import io.flamingock.internal.core.store.CommunityAuditStore;
import io.flamingock.targetsystem.nontransactional.NonTransactionalTargetSystem;
import io.flamingock.targetystem.mongodb.sync.MongoDBSyncTargetSystem;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FlamingockConfig {

@Bean
public MongoClient mongoClient() {
return MongoDBUtil.getMongoClient("mongodb://localhost:27017/");
}

@Bean
public MongoDBSyncTargetSystem mongoDBSyncTargetSystem(MongoClient mongoClient) {
return new MongoDBSyncTargetSystem(TargetSystems.MONGODB_TARGET_SYSTEM, mongoClient, TargetSystems.DATABASE_NAME);
}

@Bean
public NonTransactionalTargetSystem kafkaTargetSystem() throws Exception {
return TargetSystems.kafkaTargetSystem();
}

@Bean
public NonTransactionalTargetSystem toggleTargetSystem() {
return TargetSystems.toggleTargetSystem();
}


//This could return any of the available community audit stores
@Bean
public CommunityAuditStore auditStore(MongoDBSyncTargetSystem mongoDBSyncTargetSystem) {
return MongoDBSyncAuditStore.from(mongoDBSyncTargetSystem);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@
import io.flamingock.api.annotations.Stage;
import io.flamingock.community.Flamingock;
import io.flamingock.community.mongodb.sync.driver.MongoDBSyncAuditStore;
import io.flamingock.examples.inventory.util.TargetSystems;
import io.flamingock.internal.core.store.CommunityAuditStore;
import io.flamingock.targetystem.mongodb.sync.MongoDBSyncTargetSystem;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import static io.flamingock.examples.inventory.util.TargetSystems.DATABASE_NAME;

@SpringBootApplication
@EnableFlamingock(
stages = {
@Stage(name = "inventory", location = "io.flamingock.examples.inventory.changes")
Expand All @@ -35,21 +35,7 @@ public class InventoryOrdersApp {


public static void main(String[] args) throws Exception {
Flamingock.builder()
.setAuditStore(auditStore())
.addTargetSystems(
TargetSystems.mongoDBSyncTargetSystem(),
TargetSystems.kafkaTargetSystem(),
TargetSystems.toggleTargetSystem())
.build()
.run();

}

//This could return any of the available community audit stores
private static CommunityAuditStore auditStore() {
MongoDBSyncTargetSystem targetSystem = TargetSystems.mongoDBSyncTargetSystem();
return MongoDBSyncAuditStore.from(targetSystem);
SpringApplication.run(InventoryOrdersApp.class, args);
}


Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package io.flamingock.examples.inventory.util;
package io.flamingock.examples.inventory;

import com.mongodb.client.MongoClient;
import io.confluent.kafka.schemaregistry.client.CachedSchemaRegistryClient;
import io.confluent.kafka.schemaregistry.client.SchemaRegistryClient;
import io.flamingock.examples.inventory.util.KafkaSchemaManager;
import io.flamingock.examples.inventory.util.LaunchDarklyClient;
import io.flamingock.examples.inventory.util.MongoDBUtil;
import io.flamingock.targetsystem.nontransactional.NonTransactionalTargetSystem;
import io.flamingock.targetystem.mongodb.sync.MongoDBSyncTargetSystem;
import org.apache.kafka.clients.admin.AdminClient;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
import java.util.Arrays;
import java.util.Collections;

import static io.flamingock.examples.inventory.util.TargetSystems.MONGODB_TARGET_SYSTEM;
import static io.flamingock.examples.inventory.TargetSystems.MONGODB_TARGET_SYSTEM;

@TargetSystem(id = MONGODB_TARGET_SYSTEM)
@Change(id = "add-discount-code-field-to-orders", author = "flamingock-team")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import io.flamingock.api.annotations.TargetSystem;
import io.flamingock.examples.inventory.util.KafkaSchemaManager;

import static io.flamingock.examples.inventory.util.TargetSystems.KAFKA_TARGET_SYSTEM;
import static io.flamingock.examples.inventory.TargetSystems.KAFKA_TARGET_SYSTEM;

@TargetSystem(id =KAFKA_TARGET_SYSTEM)
@Change(id = "update-order-created-schema", author = "flamingock-team", transactional = false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import io.flamingock.api.annotations.TargetSystem;
import io.flamingock.examples.inventory.util.LaunchDarklyClient;

import static io.flamingock.examples.inventory.util.TargetSystems.FEATURE_FLAG_TARGET_SYSTEM;
import static io.flamingock.examples.inventory.TargetSystems.FEATURE_FLAG_TARGET_SYSTEM;

@TargetSystem(id = FEATURE_FLAG_TARGET_SYSTEM)
@Change(id = "add-feature-flag-discounts", author = "flamingock-team", transactional = false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static io.flamingock.examples.inventory.util.TargetSystems.MONGODB_TARGET_SYSTEM;
import static io.flamingock.examples.inventory.TargetSystems.MONGODB_TARGET_SYSTEM;

@TargetSystem(id = MONGODB_TARGET_SYSTEM)
@Change(id = "backfill-discounts-for-existing-orders", author = "flamingock-team", transactional = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static io.flamingock.examples.inventory.util.TargetSystems.MONGODB_TARGET_SYSTEM;
import static io.flamingock.examples.inventory.TargetSystems.MONGODB_TARGET_SYSTEM;

@TargetSystem(id = MONGODB_TARGET_SYSTEM)
@Change(id = "add-index-on-discount-code", author = "flamingock-team", transactional = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static io.flamingock.examples.inventory.util.TargetSystems.FEATURE_FLAG_TARGET_SYSTEM;
import static io.flamingock.examples.inventory.TargetSystems.FEATURE_FLAG_TARGET_SYSTEM;

@TargetSystem(id = FEATURE_FLAG_TARGET_SYSTEM)
@Change(id = "cleanup-feature-flag-discounts", author = "flamingock-team", transactional = false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static io.flamingock.examples.inventory.util.TargetSystems.KAFKA_TARGET_SYSTEM;
import static io.flamingock.examples.inventory.TargetSystems.KAFKA_TARGET_SYSTEM;

@TargetSystem(id =KAFKA_TARGET_SYSTEM)
@Change(id = "cleanup-old-schema-version", author = "flamingock-team", transactional = false)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.flamingock.examples.inventory.order;

import java.util.List;

public record Order(
String orderId,
String customerId,
List<OrderItem> items,
double total,
String status,
String createdAt,
String discountCode,
boolean discountApplied
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package io.flamingock.examples.inventory.order;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/orders")
public class OrderController {

private final OrderRepository orderRepository;

public OrderController(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}

@GetMapping
public List<Order> getAllOrders() {
return orderRepository.findAll();
}

@GetMapping("/{orderId}")
public ResponseEntity<Order> getOrder(@PathVariable String orderId) {
return orderRepository.findById(orderId)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package io.flamingock.examples.inventory.order;

public record OrderItem(String productId, int quantity, double price) {}
Loading
Loading