docs: add redis stream demo implementation plan
This commit is contained in:
@@ -0,0 +1,595 @@
|
||||
# Redis Stream Demo Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Build an independent Spring Boot demo project that imports `redis-stream-springboot-starter` and verifies real Redis Stream publish/subscribe behavior.
|
||||
|
||||
**Architecture:** The demo is a downstream Maven Spring Boot application under `C:\code\hatch\redis-stream-starter-demo`. It depends on the starter from the local Maven repository, sends a `DemoMessage` with `RedisStreamEnhanceTemplate`, and consumes it with an `EnhanceStreamConsumerHandler` implementation. Integration testing uses the provided Redis service when reachable and skips when unreachable.
|
||||
|
||||
**Tech Stack:** Java 8, Maven, Spring Boot `2.3.12.RELEASE`, JUnit 5, `redis-stream-springboot-starter:1.0.0-SNAPSHOT`, Redis Stream.
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
- Create: `C:\code\hatch\redis-stream-starter-demo\pom.xml`
|
||||
- Maven project definition, Spring Boot 2.3.12 dependency management, starter dependency, test setup.
|
||||
- Create: `C:\code\hatch\redis-stream-starter-demo\.gitignore`
|
||||
- Keeps build output and IDE files out of the demo project.
|
||||
- Create: `C:\code\hatch\redis-stream-starter-demo\src\main\resources\application.yml`
|
||||
- Redis host/port defaults and overridable password environment variable.
|
||||
- Create: `C:\code\hatch\redis-stream-starter-demo\src\main\java\com\njcn\demo\redisstream\DemoApplication.java`
|
||||
- Spring Boot entry point.
|
||||
- Create: `C:\code\hatch\redis-stream-starter-demo\src\main\java\com\njcn\demo\redisstream\DemoMessage.java`
|
||||
- Demo message payload extending `StreamBaseMessage`.
|
||||
- Create: `C:\code\hatch\redis-stream-starter-demo\src\main\java\com\njcn\demo\redisstream\DemoRuntimeInbox.java`
|
||||
- In-memory queue shared by runtime logging and integration tests.
|
||||
- Create: `C:\code\hatch\redis-stream-starter-demo\src\main\java\com\njcn\demo\redisstream\DemoProducer.java`
|
||||
- Sends demo messages through the starter template.
|
||||
- Create: `C:\code\hatch\redis-stream-starter-demo\src\main\java\com\njcn\demo\redisstream\DemoConsumer.java`
|
||||
- Starter consumer handler for `demo-topic`.
|
||||
- Create: `C:\code\hatch\redis-stream-starter-demo\src\main\java\com\njcn\demo\redisstream\DemoStartupRunner.java`
|
||||
- Sends one demo message when running the app manually.
|
||||
- Create: `C:\code\hatch\redis-stream-starter-demo\src\test\java\com\njcn\demo\redisstream\DemoRoundTripIntegrationTest.java`
|
||||
- Real Redis publish/subscribe integration test.
|
||||
|
||||
## Task 1: Prepare Build Scaffolding
|
||||
|
||||
**Files:**
|
||||
- Create: `C:\code\hatch\redis-stream-starter-demo\pom.xml`
|
||||
- Create: `C:\code\hatch\redis-stream-starter-demo\.gitignore`
|
||||
- Create: `C:\code\hatch\redis-stream-starter-demo\src\main\resources\application.yml`
|
||||
|
||||
- [ ] **Step 1: Install the starter into the local Maven repository**
|
||||
|
||||
Run from the starter repository:
|
||||
|
||||
```bash
|
||||
mvn install
|
||||
```
|
||||
|
||||
Expected: `BUILD SUCCESS`, with `redis-stream-springboot-starter-1.0.0-SNAPSHOT.jar` installed into the local Maven repository.
|
||||
|
||||
- [ ] **Step 2: Create demo project directories**
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
New-Item -ItemType Directory -Force `
|
||||
'C:\code\hatch\redis-stream-starter-demo\src\main\java\com\njcn\demo\redisstream', `
|
||||
'C:\code\hatch\redis-stream-starter-demo\src\main\resources', `
|
||||
'C:\code\hatch\redis-stream-starter-demo\src\test\java\com\njcn\demo\redisstream'
|
||||
```
|
||||
|
||||
Expected: all three directories exist.
|
||||
|
||||
- [ ] **Step 3: Create `pom.xml`**
|
||||
|
||||
Write this exact content:
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.njcn.demo</groupId>
|
||||
<artifactId>redis-stream-starter-demo</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>redis-stream-starter-demo</name>
|
||||
<description>Demo application for redis-stream-springboot-starter</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<spring-boot.version>2.3.12.RELEASE</spring-boot.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.njcn</groupId>
|
||||
<artifactId>redis-stream-springboot-starter</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
<encoding>UTF-8</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.22.2</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Create `.gitignore`**
|
||||
|
||||
Write this exact content:
|
||||
|
||||
```gitignore
|
||||
target/
|
||||
.idea/
|
||||
*.iml
|
||||
.classpath
|
||||
.project
|
||||
.settings/
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Create `application.yml`**
|
||||
|
||||
Write this exact content:
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
redis:
|
||||
host: ${DEMO_REDIS_HOST:192.168.1.22}
|
||||
port: ${DEMO_REDIS_PORT:12379}
|
||||
password: ${DEMO_REDIS_PASSWORD:}
|
||||
|
||||
redis-stream:
|
||||
env-isolation: true
|
||||
env: demo
|
||||
consumer-name: demo-consumer
|
||||
read-count: 10
|
||||
block-ms: 500
|
||||
max-retry: 1
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Verify build scaffolding resolves dependencies**
|
||||
|
||||
Run from the demo project:
|
||||
|
||||
```bash
|
||||
mvn test -DskipTests
|
||||
```
|
||||
|
||||
Expected: `BUILD SUCCESS`.
|
||||
|
||||
## Task 2: Write the Round-Trip Integration Test First
|
||||
|
||||
**Files:**
|
||||
- Create: `C:\code\hatch\redis-stream-starter-demo\src\test\java\com\njcn\demo\redisstream\DemoRoundTripIntegrationTest.java`
|
||||
|
||||
- [ ] **Step 1: Write the failing test**
|
||||
|
||||
Write this exact content:
|
||||
|
||||
```java
|
||||
package com.njcn.demo.redisstream;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
|
||||
import org.junit.jupiter.api.extension.ExecutionCondition;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.data.redis.connection.stream.RecordId;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
@ExtendWith(DemoRoundTripIntegrationTest.RedisReachableCondition.class)
|
||||
@SpringBootTest
|
||||
class DemoRoundTripIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private DemoProducer producer;
|
||||
|
||||
@Autowired
|
||||
private DemoRuntimeInbox inbox;
|
||||
|
||||
@Test
|
||||
void publishesAndConsumesDemoMessage() throws Exception {
|
||||
inbox.clear();
|
||||
String payload = "integration-" + System.currentTimeMillis();
|
||||
|
||||
DemoMessage sent = new DemoMessage();
|
||||
sent.setPayload(payload);
|
||||
sent.setSource("integration-test");
|
||||
sent.setTag("demo");
|
||||
|
||||
RecordId recordId = producer.send(sent);
|
||||
|
||||
DemoMessage received = inbox.poll(10, TimeUnit.SECONDS);
|
||||
assertNotNull(recordId, "XADD should return a Redis stream record id");
|
||||
assertNotNull(received, "consumer should receive the sent message within 10 seconds");
|
||||
assertEquals(sent.getKey(), received.getKey(), "message key should round-trip");
|
||||
assertEquals(payload, received.getPayload(), "payload should round-trip");
|
||||
assertEquals("integration-test", received.getSource(), "source should round-trip");
|
||||
}
|
||||
|
||||
static class RedisReachableCondition implements ExecutionCondition {
|
||||
@Override
|
||||
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
|
||||
String host = System.getProperty("spring.redis.host",
|
||||
System.getenv().getOrDefault("DEMO_REDIS_HOST", "192.168.1.22"));
|
||||
int port = Integer.parseInt(System.getProperty("spring.redis.port",
|
||||
System.getenv().getOrDefault("DEMO_REDIS_PORT", "12379")));
|
||||
try (Socket socket = new Socket()) {
|
||||
socket.connect(new InetSocketAddress(host, port), 500);
|
||||
return ConditionEvaluationResult.enabled("redis reachable at " + host + ":" + port);
|
||||
} catch (Exception e) {
|
||||
return ConditionEvaluationResult.disabled("redis unreachable at " + host + ":" + port);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run the test and verify it fails because application classes do not exist**
|
||||
|
||||
Run from the demo project with the Redis password supplied through the process environment:
|
||||
|
||||
```bash
|
||||
mvn test -Dtest=DemoRoundTripIntegrationTest
|
||||
```
|
||||
|
||||
Expected: compilation failure mentioning missing symbols such as `DemoProducer`, `DemoRuntimeInbox`, and `DemoMessage`.
|
||||
|
||||
## Task 3: Implement Minimal Demo Application Classes
|
||||
|
||||
**Files:**
|
||||
- Create: `C:\code\hatch\redis-stream-starter-demo\src\main\java\com\njcn\demo\redisstream\DemoApplication.java`
|
||||
- Create: `C:\code\hatch\redis-stream-starter-demo\src\main\java\com\njcn\demo\redisstream\DemoMessage.java`
|
||||
- Create: `C:\code\hatch\redis-stream-starter-demo\src\main\java\com\njcn\demo\redisstream\DemoRuntimeInbox.java`
|
||||
- Create: `C:\code\hatch\redis-stream-starter-demo\src\main\java\com\njcn\demo\redisstream\DemoProducer.java`
|
||||
- Create: `C:\code\hatch\redis-stream-starter-demo\src\main\java\com\njcn\demo\redisstream\DemoConsumer.java`
|
||||
|
||||
- [ ] **Step 1: Create `DemoApplication.java`**
|
||||
|
||||
Write this exact content:
|
||||
|
||||
```java
|
||||
package com.njcn.demo.redisstream;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class DemoApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(DemoApplication.class, args);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Create `DemoMessage.java`**
|
||||
|
||||
Write this exact content:
|
||||
|
||||
```java
|
||||
package com.njcn.demo.redisstream;
|
||||
|
||||
import com.njcn.middle.stream.domain.StreamBaseMessage;
|
||||
|
||||
public class DemoMessage extends StreamBaseMessage {
|
||||
|
||||
private String payload;
|
||||
|
||||
public String getPayload() {
|
||||
return payload;
|
||||
}
|
||||
|
||||
public void setPayload(String payload) {
|
||||
this.payload = payload;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Create `DemoRuntimeInbox.java`**
|
||||
|
||||
Write this exact content:
|
||||
|
||||
```java
|
||||
package com.njcn.demo.redisstream;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Component
|
||||
public class DemoRuntimeInbox {
|
||||
|
||||
private final BlockingQueue<DemoMessage> messages = new LinkedBlockingQueue<>();
|
||||
|
||||
public void add(DemoMessage message) {
|
||||
messages.add(message);
|
||||
}
|
||||
|
||||
public DemoMessage poll(long timeout, TimeUnit unit) throws InterruptedException {
|
||||
return messages.poll(timeout, unit);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
messages.clear();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Create `DemoProducer.java`**
|
||||
|
||||
Write this exact content:
|
||||
|
||||
```java
|
||||
package com.njcn.demo.redisstream;
|
||||
|
||||
import com.njcn.middle.stream.template.RedisStreamEnhanceTemplate;
|
||||
import org.springframework.data.redis.connection.stream.RecordId;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class DemoProducer {
|
||||
|
||||
public static final String TOPIC = "demo-topic";
|
||||
|
||||
private final RedisStreamEnhanceTemplate streamTemplate;
|
||||
|
||||
public DemoProducer(RedisStreamEnhanceTemplate streamTemplate) {
|
||||
this.streamTemplate = streamTemplate;
|
||||
}
|
||||
|
||||
public RecordId send(DemoMessage message) {
|
||||
return streamTemplate.send(TOPIC, message, false);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Create `DemoConsumer.java`**
|
||||
|
||||
Write this exact content:
|
||||
|
||||
```java
|
||||
package com.njcn.demo.redisstream;
|
||||
|
||||
import com.njcn.middle.stream.handler.EnhanceStreamConsumerHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class DemoConsumer extends EnhanceStreamConsumerHandler<DemoMessage> {
|
||||
|
||||
public static final String GROUP = "demo-group";
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(DemoConsumer.class);
|
||||
|
||||
private final DemoRuntimeInbox inbox;
|
||||
|
||||
public DemoConsumer(DemoRuntimeInbox inbox) {
|
||||
this.inbox = inbox;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String topic() {
|
||||
return DemoProducer.TOPIC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String group() {
|
||||
return GROUP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<DemoMessage> messageType() {
|
||||
return DemoMessage.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(DemoMessage message) {
|
||||
log.info("Consumed Redis Stream demo message: key={}, payload={}, source={}, tag={}",
|
||||
message.getKey(), message.getPayload(), message.getSource(), message.getTag());
|
||||
inbox.add(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRetry() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean throwException() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Run the integration test and verify it passes**
|
||||
|
||||
Run from the demo project with the Redis password supplied through the process environment:
|
||||
|
||||
```bash
|
||||
mvn test -Dtest=DemoRoundTripIntegrationTest
|
||||
```
|
||||
|
||||
Expected: `BUILD SUCCESS`, and the test reports one successful test when Redis is reachable.
|
||||
|
||||
## Task 4: Add Manual Startup Publisher
|
||||
|
||||
**Files:**
|
||||
- Create: `C:\code\hatch\redis-stream-starter-demo\src\main\java\com\njcn\demo\redisstream\DemoStartupRunner.java`
|
||||
|
||||
- [ ] **Step 1: Write the failing startup-runner test by extending the integration test**
|
||||
|
||||
Modify `DemoRoundTripIntegrationTest.java` to include this field and test method:
|
||||
|
||||
```java
|
||||
@Autowired
|
||||
private DemoStartupRunner startupRunner;
|
||||
|
||||
@Test
|
||||
void startupRunnerPublishesDemoMessage() throws Exception {
|
||||
inbox.clear();
|
||||
|
||||
startupRunner.run();
|
||||
|
||||
DemoMessage received = inbox.poll(10, TimeUnit.SECONDS);
|
||||
assertNotNull(received, "startup runner should publish a message");
|
||||
assertEquals("startup-runner", received.getSource(), "startup runner should set source");
|
||||
assertEquals("hello redis stream starter", received.getPayload(), "startup runner payload should match");
|
||||
}
|
||||
```
|
||||
|
||||
The full test class should still keep the first `publishesAndConsumesDemoMessage` test unchanged.
|
||||
|
||||
- [ ] **Step 2: Run the test and verify it fails because `DemoStartupRunner` does not exist**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -Dtest=DemoRoundTripIntegrationTest
|
||||
```
|
||||
|
||||
Expected: compilation failure mentioning missing symbol `DemoStartupRunner`.
|
||||
|
||||
- [ ] **Step 3: Create `DemoStartupRunner.java`**
|
||||
|
||||
Write this exact content:
|
||||
|
||||
```java
|
||||
package com.njcn.demo.redisstream;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.data.redis.connection.stream.RecordId;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class DemoStartupRunner implements ApplicationRunner {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(DemoStartupRunner.class);
|
||||
|
||||
private final DemoProducer producer;
|
||||
|
||||
public DemoStartupRunner(DemoProducer producer) {
|
||||
this.producer = producer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(ApplicationArguments args) {
|
||||
run();
|
||||
}
|
||||
|
||||
public RecordId run() {
|
||||
DemoMessage message = new DemoMessage();
|
||||
message.setPayload("hello redis stream starter");
|
||||
message.setSource("startup-runner");
|
||||
message.setTag("demo");
|
||||
RecordId recordId = producer.send(message);
|
||||
log.info("Published Redis Stream demo message: recordId={}, key={}, payload={}",
|
||||
recordId, message.getKey(), message.getPayload());
|
||||
return recordId;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run the integration test and verify both tests pass**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -Dtest=DemoRoundTripIntegrationTest
|
||||
```
|
||||
|
||||
Expected: `BUILD SUCCESS`, with two successful tests when Redis is reachable.
|
||||
|
||||
## Task 5: Final Verification
|
||||
|
||||
**Files:**
|
||||
- No new files.
|
||||
|
||||
- [ ] **Step 1: Run the starter build**
|
||||
|
||||
Run from the starter repository:
|
||||
|
||||
```bash
|
||||
mvn install
|
||||
```
|
||||
|
||||
Expected: `BUILD SUCCESS`.
|
||||
|
||||
- [ ] **Step 2: Run all demo tests**
|
||||
|
||||
Run from the demo project with the Redis password supplied through the process environment:
|
||||
|
||||
```bash
|
||||
mvn test
|
||||
```
|
||||
|
||||
Expected: `BUILD SUCCESS`.
|
||||
|
||||
- [ ] **Step 3: Run the demo application**
|
||||
|
||||
Run from the demo project with the Redis password supplied through the process environment:
|
||||
|
||||
```bash
|
||||
mvn spring-boot:run
|
||||
```
|
||||
|
||||
Expected: startup logs show a published Redis Stream message and a consumed Redis Stream message. Stop the process after the message is consumed.
|
||||
|
||||
- [ ] **Step 4: Capture final file status**
|
||||
|
||||
Run from the starter repository:
|
||||
|
||||
```bash
|
||||
git status --short
|
||||
```
|
||||
|
||||
Expected: only the implementation plan file is uncommitted unless execution commits it separately.
|
||||
|
||||
## Self-Review
|
||||
|
||||
- Spec coverage: Tasks cover local starter installation, independent demo project creation, Redis configuration, producer, consumer, startup publishing, integration testing, and manual runtime verification.
|
||||
- Placeholder scan: No TBD/TODO markers are used. Redis password is intentionally not written into this committed plan.
|
||||
- Type consistency: `DemoMessage`, `DemoProducer`, `DemoConsumer`, `DemoRuntimeInbox`, `DemoStartupRunner`, and `DemoRoundTripIntegrationTest` use matching package names and method signatures.
|
||||
Reference in New Issue
Block a user