Skip to content
 
 

Repository files navigation

OpenSpec Workshop: Spec-Driven Persistence Migrations

Duration: Full day · Stack: Java · Spring Boot · Gradle · Testcontainers


What is OpenSpec?

OpenSpec is a CLI tool and set of opencode skills that bring structure to AI-assisted development. Instead of ad-hoc prompts, you work through a defined workflow: explore the problem, propose a change (producing a proposal, design, and task list), apply the tasks, and archive the completed change.

The specs and decisions are captured in files under openspec/, giving AI persistent context across sessions.

The four commands you'll use today:

Command Purpose
/opsx-explore Think through a problem before committing to a direction
/opsx-propose Create a change: generates proposal, design, and tasks
/opsx-apply Implement the tasks from a change
/opsx-archive Finalize and archive a completed change

Install: https://openspec.dev


Narrative Arc

Act 1 — "Just ask the AI" → capable but inconsistent Act 2 — "Give the AI a structured change" → MySQL migration via OpenSpec Act 3 — "The specs remember" → DynamoDB migration, building on archived context

The domain (Employee CRUD) never changes. The persistence does. That's the point.


Prerequisites

  • JDK 21+, Gradle 8+, Docker (running)
  • opencode installed and working
  • openspec CLI installed (install guide)
  • Your usual editor/IDE
  • Pre-pull Docker images before the session to avoid cold-pull delays:
docker pull mysql:8.0
docker pull localstack/localstack

openspec/config.yaml is pre-configured in this repo. Normally it is generated by /opsx-init and tracks project-level context. It is baked in here so you can focus on the change workflow without project initialization.


Act 1 — Vibe Coding

Goal

Experience what AI-assisted development looks like with no structured change.

Setup

The skeleton (your presenter will walk through it):

  • Employee.java — the POJO, fully written
  • EmployeeController.java — all five operations stubbed, in-memory persistence
  • EmployeeService.java — thin pass-through to EmployeeRepository
  • InMemoryEmployeeRepository.javaConcurrentHashMap + AtomicLong
  • No tests exist

Everything compiles and runs. GET /employees returns an empty list.

The exercise

"Use opencode to write tests that would catch a broken persistence layer."

No further instructions. Decide what "catch a broken persistence layer" means to you.


Act 2 — MySQL Migration with OpenSpec

Goal

Use OpenSpec to structure a persistence migration from in-memory to MySQL, implement it, and verify with Testcontainers.

The OpenSpec Workflow

Follow these steps in order. Do not skip ahead.

  1. Create a branch from main, it will be your sandbox for this workshop.
git checkout -b <your-name>
  1. Propose — describe what you want to change
/opsx-propose mysql-migration

Before proposing — glance at openspec/config.yaml. This is what opencode knows about your project going in.

Describe the change like this:

"Migrate the Employee persistence layer from in-memory HashMap to MySQL using JPA, TestContainers for integration testing, contract must be maintained."

Review the generated artifacts:

  • openspec/changes/mysql-migration/proposal.md
  • openspec/changes/mysql-migration/design.md
  • openspec/changes/mysql-migration/tasks.md

Read them. You are the author. opencode is the typist. If anything is wrong or missing, edit before moving on. Commit the artifacts.

  1. Apply — let AI implement the tasks
/opsx-apply mysql-migration

Review each task's output. Run ./gradlew build. Commit when green.

Integration tests will look like this:

@SpringBootTest(webEnvironment = RANDOM_PORT)
@Testcontainers
class EmployeeControllerIT {

    @Container
    static MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0");

    @DynamicPropertySource
    static void props(DynamicPropertyRegistry r) {
        r.add("spring.datasource.url", mysql::getJdbcUrl);
        r.add("spring.datasource.username", mysql::getUsername);
        r.add("spring.datasource.password", mysql::getPassword);
    }

    @Autowired TestRestTemplate http;

    @Test void createAndRetrieve() { ... }
    @Test void getUnknownReturns404() { ... }
    @Test void deleteAndConfirmGone() { ... }
}
./gradlew test
  1. Archive — finalize the change
/opsx-archive mysql-migration

Commit the archive on your branch.


Act 3 — DynamoDB Migration with OpenSpec

Goal

Use the archived MySQL change as context to drive a second persistence migration — to DynamoDB on LocalStack.

Phase 3a — Propose the change

/opsx-propose dynamodb-migration

Describe the change:

"Migrate the Employee persistence layer from MySQL/JPA to DynamoDB using the AWS SDK v2. Use LocalStack and LocalStackContainer for local development and testing. Keep all controller and service code unchanged."

Review the proposal carefully before accepting. This is the moment to catch anything the AI gets wrong — or anything it gets interestingly right.

Phase 3b — Implement the change

/opsx-apply dynamodb-migration
./gradlew test

Phase 3c — Archive

/opsx-archive dynamodb-migration

Same test cases and assertions. Different container setup.

About

openspec kata, from in-memory to relational persistence to dynamodb

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages