Duration: Full day · Stack: Java · Spring Boot · Gradle · Testcontainers
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
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.
- JDK 21+, Gradle 8+, Docker (running)
- opencode installed and working
openspecCLI 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/localstackopenspec/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.
Experience what AI-assisted development looks like with no structured change.
The skeleton (your presenter will walk through it):
Employee.java— the POJO, fully writtenEmployeeController.java— all five operations stubbed, in-memory persistenceEmployeeService.java— thin pass-through toEmployeeRepositoryInMemoryEmployeeRepository.java—ConcurrentHashMap+AtomicLong- No tests exist
Everything compiles and runs. GET /employees returns an empty list.
"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.
Use OpenSpec to structure a persistence migration from in-memory to MySQL, implement it, and verify with Testcontainers.
Follow these steps in order. Do not skip ahead.
- Create a branch from main, it will be your sandbox for this workshop.
git checkout -b <your-name>- 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.mdopenspec/changes/mysql-migration/design.mdopenspec/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.
- 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- Archive — finalize the change
/opsx-archive mysql-migration
Commit the archive on your branch.
Use the archived MySQL change as context to drive a second persistence migration — to DynamoDB on LocalStack.
/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.
/opsx-apply dynamodb-migration
./gradlew test/opsx-archive dynamodb-migration
Same test cases and assertions. Different container setup.