Reference implementation demonstrating contract-first (API-first / schema-first) development patterns for systems integration.
For that purpose, we will build a simple order management system with the following technologies: Apache Kafka for event streaming, PostgreSQL for data persistence, Apache Avro for event serialization, Confluent Schema Registry for schema governance, and Spring Boot for the application layer.
The project emphasizes best practices in API design, schema evolution, idempotency, and error handling.
Contract-first is an approach where you define the integration boundary first (the contract), then implement code that conforms to it. This repository demonstrates three types of contracts:
- REST API Contracts (OpenAPI 3.0)
- Kafka Event Contracts (Apache Avro + Schema Registry)
- Database Contracts (Flyway migrations)
Key Principle: The contract is the single source of truth. Code, documentation, SDKs, mocks, and tests are all derived from contracts.
graph LR
subgraph Contracts["📄 Contracts — Source of Truth"]
OpenAPI["OpenAPI 3.0 YAML\norders-api.v1.yaml"]
Avro["Avro Schema\nOrderCreated.v1.avsc"]
SQL["Flyway SQL\nV1__create_orders.sql"]
end
subgraph Generated["⚡ Generated Artifacts"]
Controller["Java Interface\n(Server Stub)"]
DTO["Java Records\n(Request / Response DTOs)"]
AvroClass["Java Classes\n(Avro POJOs)"]
DBSchema["DB Schema\n(Tables & Indexes)"]
end
subgraph Teams["👥 Parallel Development"]
Provider["🏭 Provider Team\nImplements stub"]
Consumer["🛒 Consumer Team\nUses client SDK"]
end
OpenAPI -->|"openapi-generator"| Controller
OpenAPI -->|"openapi-generator"| DTO
Avro -->|"avro-maven-plugin"| AvroClass
SQL -->|"Flyway migrate"| DBSchema
Controller --> Provider
DTO --> Provider
DTO --> Consumer
AvroClass --> Provider
AvroClass --> Consumer
classDef contractNode fill:#f3d9fa,stroke:#9c36b5,color:#000,stroke-width:2px
classDef genNode fill:#d3f9d8,stroke:#2f9e44,color:#000,stroke-width:2px
classDef teamNode fill:#dbe4ff,stroke:#3b5bdb,color:#000,stroke-width:2px
class OpenAPI,Avro,SQL contractNode
class Controller,DTO,AvroClass,DBSchema genNode
class Provider,Consumer teamNode
- Java 21+
- Maven 3.8+
- Docker & Docker Compose
# Start full stack (PostgreSQL, Kafka, Zookeeper, Schema Registry, App)
make compose
# Access the application
open http://localhost:8080/
open http://localhost:8080/swagger-ui.html# 1. Install dependencies and generate Avro classes
make setup
# 2. Start infrastructure (PostgreSQL, Kafka, Schema Registry)
docker compose up postgres kafka schema-registry -d
# 3. Run application
make dev
# 4. Test the API
curl -X POST http://localhost:8080/v1/orders \
-H "Content-Type: application/json" \
-d '{
"customerId": "CUST-123",
"idempotencyKey": "test-key-001",
"items": [{"sku": "SKU-001", "quantity": 2}]
}'- POST /v1/orders - Create order with idempotency support
- GET /v1/orders/{orderId} - Retrieve order by ID
- Idempotency: Safe request retries using
idempotencyKey - Error Handling: Standardized
ErrorResponsewith correlation IDs - Swagger UI: Interactive API documentation
- OrderCreated events with backward-compatible schema evolution
- Schema Registry: Confluent Schema Registry for schema governance
- Idempotent Consumers: Event deduplication using
eventId - Dead Letter Queue: Failed message handling with debugging info
- Versioned migrations: V1 (initial schema), V2 (add source column)
- Schema evolution: Expand/migrate/contract pattern
- Idempotency tracking: Tables for REST and Kafka idempotency
graph TD
Client["🖥️ Client / Consumer"]
subgraph AppLayer["⚙️ Spring Boot Application"]
REST["📋 REST Controller<br/>POST /v1/orders · GET /v1/orders/{id}"]
OrderService["🔧 OrderService<br/>Idempotency · Order Creation · Events"]
KafkaConsumer["📨 Kafka Consumer<br/>OrderCreatedListener"]
end
subgraph DataLayer["💾 Infrastructure"]
PostgreSQL[("🗄️ PostgreSQL 17<br/>orders · order_items<br/>idempotency_keys · processed_events")]
KafkaBroker[("📡 Kafka Broker<br/>+ Confluent Schema Registry")]
end
Client -->|"HTTP POST / GET"| REST
REST --> OrderService
OrderService -->|"persist"| PostgreSQL
OrderService -->|"publish OrderCreated"| KafkaBroker
KafkaBroker -.->|"consume"| KafkaConsumer
KafkaConsumer --> OrderService
classDef clientNode fill:#dbe4ff,stroke:#3b5bdb,color:#1a1a2e,stroke-width:2px
classDef apiNode fill:#d3f9d8,stroke:#2f9e44,color:#1a1a2e,stroke-width:2px
classDef serviceNode fill:#c3fae8,stroke:#0ca678,color:#1a1a2e,stroke-width:2px
classDef consumerNode fill:#fff3bf,stroke:#f59f00,color:#1a1a2e,stroke-width:2px
classDef dbNode fill:#e8eaf6,stroke:#3949ab,color:#1a1a2e,stroke-width:2px
classDef brokerNode fill:#fff3e0,stroke:#e67700,color:#1a1a2e,stroke-width:2px
class Client clientNode
class REST apiNode
class OrderService serviceNode
class KafkaConsumer consumerNode
class PostgreSQL dbNode
class KafkaBroker brokerNode
contract-first-integrations/
├── contracts/ # First-class contract artifacts
│ ├── openapi/
│ │ └── orders-api.v1.yaml # REST API contract
│ ├── events/
│ │ ├── avro/
│ │ │ ├── OrderCreated.v1.avsc
│ │ │ └── DeadLetterEnvelope.v1.avsc
│ │ ├── topics.md # Topic semantics
│ │ └── asyncapi.yaml # AsyncAPI documentation
│ └── db/
│ └── flyway/
│ ├── V1__create_orders.sql
│ └── V2__add_order_source.sql
├── src/main/java/
│ └── com/example/contractfirst/
│ ├── config/ # Spring configuration
│ ├── controller/ # REST controllers
│ ├── service/ # Business logic
│ ├── repository/ # Data access
│ ├── entity/ # JPA entities
│ ├── dto/ # Java Record DTOs
│ ├── kafka/ # Kafka producers/consumers
│ ├── mapper/ # Entity/DTO mappers
│ └── exception/ # Custom exceptions
├── docker-compose.yml # Full stack infrastructure
├── Makefile # Build commands
└── README.md
make setup # Install dependencies and generate Avro sources
make contracts # Generate Java classes from Avro schemas
make dev # Run application locally
make test # Run tests
make test-cov # Run tests with coverage report
make build # Build JAR file
make docker # Build Docker image
make compose # Start full stack with docker-compose
make down # Stop docker-compose stack
make clean # Clean build artifactsmake logs # View application logs
make kafka-topics # List Kafka topics
make kafka-consume # Consume OrderCreated events
make db-connect # Connect to PostgreSQL
make db-migrate # Run Flyway migrations
make db-info # Show migration status# Run all tests
make test
# Run with coverage
make test-cov
open target/site/jacoco/index.html# Create order
curl -X POST http://localhost:8080/v1/orders \
-H "Content-Type: application/json" \
-d '{
"customerId": "CUST-123",
"idempotencyKey": "unique-key-001",
"items": [
{"sku": "SKU-001", "quantity": 2},
{"sku": "SKU-002", "quantity": 1}
]
}'
# Get order
curl http://localhost:8080/v1/orders/ORD-XXXXX
# Test idempotency (same key, same payload → returns cached result)
curl -X POST http://localhost:8080/v1/orders \
-H "Content-Type: application/json" \
-d '{
"customerId": "CUST-123",
"idempotencyKey": "unique-key-001",
"items": [
{"sku": "SKU-001", "quantity": 2},
{"sku": "SKU-002", "quantity": 1}
]
}'
# Test idempotency conflict (same key, different payload → 409 Conflict)
curl -X POST http://localhost:8080/v1/orders \
-H "Content-Type: application/json" \
-d '{
"customerId": "CUST-999",
"idempotencyKey": "unique-key-001",
"items": [{"sku": "SKU-999", "quantity": 5}]
}'# List topics
make kafka-topics
# Consume OrderCreated events
make kafka-consume
# Check Schema Registry
curl http://localhost:8081/subjects
curl http://localhost:8081/subjects/orders.order-created.v1-value/versions# Connect to PostgreSQL
make db-connect
# Query tables
SELECT * FROM orders;
SELECT * FROM order_items;
SELECT * FROM idempotency_keys;
SELECT * FROM processed_events;- Client sends
idempotencyKeywith POST requests - Service hashes request body and stores with key
- Duplicate key + same hash → return cached response (safe retry)
- Duplicate key + different hash → return 409 Conflict
sequenceDiagram
participant C as Client
participant A as REST API
participant S as OrderService
participant D as PostgreSQL
C->>A: POST /v1/orders (idempotencyKey: "key-001")
activate A
A->>S: createOrder(request)
activate S
S->>D: SELECT WHERE idempotency_key = 'key-001'
D-->>S: ∅ Not found
S->>D: INSERT order + idempotency record
D-->>S: ✓ Saved
S-->>A: OrderResponse
deactivate S
A-->>C: 201 Created
deactivate A
Note over C,D: Safe retry — same key replayed
C->>A: POST /v1/orders (idempotencyKey: "key-001")
activate A
A->>S: createOrder(request)
activate S
S->>D: SELECT WHERE idempotency_key = 'key-001'
D-->>S: ✓ Cached response found
S-->>A: Cached OrderResponse
deactivate S
A-->>C: 200 OK (cached)
deactivate A
- Events include
eventId(UUID) - Consumer checks
processed_eventstable before processing - Already processed → skip (prevents duplicate billing, etc.)
- Avro field
sourceis nullable with default - Old consumers ignore new field (forward compatible)
- New consumers handle missing field (backward compatible)
- Schema Registry enforces compatibility
- All
ErrorResponseincludestraceIdfor correlation - Standardized error codes:
VALIDATION_ERROR,NOT_FOUND,CONFLICT,INTERNAL_ERROR - Dead Letter Queue for poison messages
- Swagger UI: http://localhost:8080/swagger-ui.html
- OpenAPI Spec: http://localhost:8080/v3/api-docs
- Health Check: http://localhost:8080/actuator/health
- Info: http://localhost:8080/actuator/info
- Static Page: http://localhost:8080/
- Java 21 (Stable LTS)
- Spring Boot 3.5.10 (Web, Data JPA, Actuator, Kafka)
- PostgreSQL 17 (Database)
- Apache Kafka 7.8.1 (Event streaming)
- Apache Avro 1.12.1 (Event serialization)
- Confluent Schema Registry 7.8.1 (Schema governance)
- Flyway (Database migrations)
- Lombok 1.18.46 (Boilerplate reduction)
- springdoc-openapi 2.8.1 (Swagger UI)
- TestContainers 1.21.4 (Integration testing)
- OpenAPI Specification
- Apache Avro Documentation
- Flyway Migrations
- Kafka Idempotent Producer
- Schema Registry
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
- Name: Wallace Espindola - Software Engineer Sr. / Solution Architect / Java & Python Dev
- Contact: wallace.espindola@gmail.com
- LinkedIn: linkedin.com/in/wallaceespindola
- GitHub: github.com/wallaceespindola
- Speaker Deck: speakerdeck.com/wallacese
Contributions, issues, and feature requests are welcome!
Give a ⭐️ if this project helped you understand contract-first development!