diff --git a/Readme.md b/Readme.md
index 2d30a1f..458a69d 100644
--- a/Readme.md
+++ b/Readme.md
@@ -7,13 +7,14 @@ Patbond API is a Spring Boot multi-module backend.
- `patbond-common`: stable cross-module contracts (response wrapper, shared DTOs). No web/messaging dependencies.
- `patbond-user`: user service for user creation, profile lookup, and password verification.
- `patbond-auth`: authentication service for registration and login. Calls `patbond-user` over HTTP via OpenFeign with a configured static URL (no service discovery in the MVP, see ADR-002).
+- `patbond-pet`: pet profile and health record service (M2, ADR-009). Shares the database with `patbond-user` and only reads/writes the `pet_health` schema; the Flyway migration chain stays owned by `patbond-user`. First-wave skeleton: liveness endpoint only.
## Technology Stack
- Java 17 (build baseline; use JDK 17 for release builds)
- Spring Boot 3.5.16
- Spring Cloud 2025.0.3 (OpenFeign only)
-- PostgreSQL 18 + Flyway (patbond-user owns the `identity`/`media` schemas)
+- PostgreSQL 18 + Flyway (patbond-user owns the migration chain: `identity`/`media`/`platform`/`pet_health` schemas)
- Maven (use the committed Maven Wrapper `./mvnw`)
## Build and Test
@@ -49,6 +50,8 @@ cp patbond-user/src/main/resources/application.yml.sample \
patbond-user/src/main/resources/application.yml
cp patbond-auth/src/main/resources/application.yml.sample \
patbond-auth/src/main/resources/application.yml
+cp patbond-pet/src/main/resources/application.yml.sample \
+ patbond-pet/src/main/resources/application.yml
```
Install the shared module once, then run each service in its own terminal:
diff --git a/docker-compose.yml b/docker-compose.yml
index 741aedc..d0116f0 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -60,5 +60,25 @@ services:
depends_on:
- user
+ # M2(ADR-009):宠物健康档案服务。第一波为骨架(仅 /health 探活,无业务端点)。
+ # 与 user 共库;Flyway 迁移链由 user 服务统一执行,故依赖 user 先起,
+ # 保证 pet_health schema 已就绪。
+ pet:
+ build: ./patbond-pet
+ environment:
+ SPRING_CONFIG_LOCATION: file:/config/application.yml
+ PATBOND_DB_URL: jdbc:postgresql://postgres:5432/${PATBOND_DB_NAME:-patbond}
+ PATBOND_DB_USER: ${PATBOND_DB_USER:-patbond}
+ PATBOND_DB_PASSWORD: ${PATBOND_DB_PASSWORD:?先运行 deploy/init-secrets.sh 生成 .env}
+ volumes:
+ - ./patbond-pet/src/main/resources/application.yml.sample:/config/application.yml:ro
+ ports:
+ - "${PATBOND_PET_PORT:-8083}:8083"
+ depends_on:
+ postgres:
+ condition: service_healthy
+ user:
+ condition: service_started
+
volumes:
pgdata:
diff --git a/patbond-pet/Dockerfile b/patbond-pet/Dockerfile
new file mode 100644
index 0000000..885a6a6
--- /dev/null
+++ b/patbond-pet/Dockerfile
@@ -0,0 +1,9 @@
+# Runtime image only — build the jar first: ./mvnw -pl patbond-pet -am package
+# Stateless by design (ADR-007): no local state, config via env / mounted files.
+FROM eclipse-temurin:17-jre
+RUN useradd --system --uid 10001 patbond
+USER patbond
+WORKDIR /app
+COPY target/patbond-pet-1.0.0-SNAPSHOT-exec.jar app.jar
+EXPOSE 8083
+ENTRYPOINT ["java", "-jar", "/app/app.jar"]
diff --git a/patbond-pet/pom.xml b/patbond-pet/pom.xml
new file mode 100644
index 0000000..f0b6069
--- /dev/null
+++ b/patbond-pet/pom.xml
@@ -0,0 +1,94 @@
+
+
+ 4.0.0
+
+
+ com.patbond.patbond
+ patbond-api
+ 1.0.0-SNAPSHOT
+ ../pom.xml
+
+
+ patbond-pet
+ jar
+
+ patbond-pet
+ Pet profile and health record service for Patbond (ADR-009)
+
+
+
+
+ com.patbond.patbond
+ patbond-common
+ ${project.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ org.springframework.boot
+ spring-boot-starter-jdbc
+
+
+ org.postgresql
+ postgresql
+ runtime
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springframework.boot
+ spring-boot-testcontainers
+ test
+
+
+ org.testcontainers
+ postgresql
+ test
+
+
+ org.testcontainers
+ junit-jupiter
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+ repackage
+
+
+
+ exec
+
+
+
+
+
+
+
diff --git a/patbond-pet/src/main/java/com/patbond/patbond/pet/PetApplication.java b/patbond-pet/src/main/java/com/patbond/patbond/pet/PetApplication.java
new file mode 100644
index 0000000..40f16f3
--- /dev/null
+++ b/patbond-pet/src/main/java/com/patbond/patbond/pet/PetApplication.java
@@ -0,0 +1,18 @@
+package com.patbond.patbond.pet;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * Pet profile and health record service (M2, ADR-009: the pet_health domain
+ * lives in its own Maven module, not inside patbond-user). First-wave
+ * skeleton: configuration wiring, datasource and a liveness endpoint only —
+ * business endpoints follow the frozen OpenAPI contract in the second wave.
+ */
+@SpringBootApplication
+public class PetApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(PetApplication.class, args);
+ }
+}
diff --git a/patbond-pet/src/main/java/com/patbond/patbond/pet/controller/HealthController.java b/patbond-pet/src/main/java/com/patbond/patbond/pet/controller/HealthController.java
new file mode 100644
index 0000000..7ee6762
--- /dev/null
+++ b/patbond-pet/src/main/java/com/patbond/patbond/pet/controller/HealthController.java
@@ -0,0 +1,36 @@
+package com.patbond.patbond.pet.controller;
+
+import com.patbond.patbond.common.response.ApiResponse;
+import org.springframework.jdbc.core.simple.JdbcClient;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Map;
+
+/**
+ * Liveness/readiness probe for the skeleton phase: confirms the service is
+ * up and that its datasource can reach the shared PostgreSQL. Deliberately
+ * outside /api/v1 so it stays unauthenticated (same reasoning as compose's
+ * pg_isready: infrastructure probes carry no business data).
+ */
+@RestController
+public class HealthController {
+
+ private final JdbcClient jdbcClient;
+
+ public HealthController(JdbcClient jdbcClient) {
+ this.jdbcClient = jdbcClient;
+ }
+
+ @GetMapping("/health")
+ public ApiResponse