Daniel Campos
HomeAboutThoughtsProjectsContact
Daniel Campos

Fullstack developer building practical software with clear interfaces and solid foundations.

Navigation

HomeProjectsThoughtsAbout

Social

ContactLinks

© 2026 Daniel Campos. All rights reserved.

CF
ProjectContract First Webshop API

Contract First Webshop API

A learning-focused REST API that practices contract-first design with Spring Boot, PostgreSQL, and generated OpenAPI contracts against a realistic webshop data model.

Snapshot

FieldDetails
TypeBackend Rest API prototype
ContextLearning experiment
RoleSolo developer
Year
2023
StatusCompleted prototype
Main focusContract-first API design, customer CRUD, and layered persistence

Overview

This project is a Spring Boot REST service built around a contract-first workflow. I defined the API in OpenAPI first, generated Spring interfaces and models from that contract, and implemented the customer resource end to end against a PostgreSQL webshop schema.

The database models a full e-commerce domain with customers, addresses, products, orders, stock, and related entities. The implemented API covers customer operations only, which made the project a focused way to practice specification-driven development, persistence mapping, and testable service design without trying to ship an entire storefront backend at once.

The problem

Building backend services from implementation details first often leads to drift between what clients expect and what the server actually exposes. For a webshop domain with several related entities, that mismatch becomes harder to manage as the API grows.

  • API contracts were easy to change informally without a single source of truth
  • Generated models and hand-written persistence code needed a clear boundary
  • PostgreSQL enum types and timestamp fields did not map cleanly to OpenAPI models
  • A full webshop schema existed, but the first slice still needed a maintainable architecture

Who it was for

  • Developers learning contract-first API development with Spring Boot
  • API consumers that need predictable customer CRUD endpoints
  • Future backend work extending the same OpenAPI spec to products, orders, and addresses
  • Anyone reviewing how generated API contracts connect to JPA persistence

My role

I owned the project end to end: the OpenAPI specification, Gradle code generation setup, Spring Boot application structure, customer persistence layer, REST controllers, exception handling, database integration, and unit tests. I also scoped the first implementation to the customer resource while leaving the broader webshop contract prepared for later expansion.

What the project does

The service exposes versioned customer endpoints under /api/v1/customers, backed by the webshop.customer table in PostgreSQL. Requests flow through controller, service, repository adapter, and JPA layers, with MapStruct handling translation between generated API models and database entities.

  • Lists all customers
  • Retrieves a customer by ID
  • Creates a new customer
  • Updates an existing customer
  • Serves interactive API docs through Swagger UI
  • Exposes Spring Actuator endpoints for runtime inspection

Key features

Contract-first API generation

The OpenAPI spec is the starting point for the server surface area. Gradle generates Spring interfaces and model classes before compilation, so the implementation stays aligned with the declared contract.

gradle
openApiGenerate {
    generatorName.set("spring")
    inputSpec.set("$rootDir/src/main/resources/webshop-v1.0.yaml")
    outputDir.set(layout.buildDirectory.dir('generated').get().toString())
    apiPackage.set("com.giusniyyel.openapi.api")
    modelPackage.set("com.giusniyyel.openapi.models")
    configOptions.set([
            delegatePattern  : 'true',
            useResponseEntity: 'false',
            interfaceOnly    : 'true',
            useSpringBoot3   : 'true'
    ])
}

I chose interface-only generation so my controllers could stay explicit while still inheriting the contract shape from the spec.

Customer CRUD with audit timestamps

The service layer sets created and updated timestamps on write operations, keeping that concern out of the controller and closer to application rules.

java
public Customer save(Customer customer) throws CustomerPersistenceException {
    if (customer.getId() != null) {
        customer.setUpdated(OffsetDateTime.now());
    } else {
        customer.setCreated(OffsetDateTime.now());
        customer.setUpdated(OffsetDateTime.now());
    }
    return customerRepository.save(customer);
}

This gave me a simple convention for distinguishing inserts from updates without pushing timestamp logic into the persistence adapter.

Layered persistence with MapStruct

Generated OpenAPI models, JPA entities, and PostgreSQL column types do not match by default. I used MapStruct to translate between API models and CustomerEntity, including custom conversions for dates and timestamps.

I kept generated models at the edge of the system and treated JPA entities as infrastructure details, which made the repository adapter easier to reason about and test.

PostgreSQL webshop schema integration

The project uses a realistic webshop schema with enums, foreign keys, and related tables for products, orders, stock, and addresses. The customer slice connects to that schema directly rather than using an in-memory or simplified demo database.

API documentation and observability

Springdoc OpenAPI serves Swagger UI, and Spring Actuator exposes runtime endpoints. Together they made local development and manual verification straightforward during prototyping.

Technical approach

The application follows a layered structure: REST controllers in infrastructure, application services for use cases, a domain port for customer persistence, and adapter classes that implement that port with Spring Data JPA.

Build flow:

  1. OpenAPI Generator creates models and API interfaces from webshop-v1.0.yaml
  2. Controllers call application services with generated Customer models
  3. Services apply business rules such as timestamp handling
  4. Repository adapters map to JPA entities and persist through CrudRepository
  5. MapStruct converts between API and persistence representations

The repository adapter wraps database failures and missing records in project-specific exceptions:

java
public Customer getCustomerById(Integer id) {
    CustomerEntity customer = customerCrudRepository.findById(id).orElseThrow(
            () -> new ResourceNotFoundException("Customer not found")
    );
    return mapper.toCustomer(customer);
}

I mapped PostgreSQL gender enums through a JPA attribute converter so database values stayed compatible with the generated OpenAPI enum. For testing, I added Mockito-based unit tests for the service and controller layers, covering list retrieval, lookup success and failure, and create/update timestamp behavior.

Security dependencies are present in the build but not enabled in the running prototype, which kept the first slice focused on API and persistence mechanics.

Design decisions

I optimized for a clear first vertical slice rather than partial coverage across every webshop entity. The OpenAPI file already sketches addresses, products, orders, and stock, but only customer paths are active in the running service.

  • Started from OpenAPI instead of annotating controllers after the fact
  • Used a port-and-adapter style boundary around customer persistence
  • Kept generated models at the API edge and JPA entities in infrastructure
  • Centralized date and timestamp conversion in MapStruct rather than manual mapping in services
  • Exposed Swagger UI and Actuator to support local exploration during development
  • Left broader webshop endpoints commented in the spec to define future scope without overbuilding the prototype

Challenges and tradeoffs

  • PostgreSQL enums, date, and timestamp with time zone fields required explicit conversion logic between JPA and OpenAPI models
  • The OpenAPI contract describes more of the webshop than the current implementation exposes, which is useful for planning but can look incomplete from the outside
  • POST and PUT share one controller method, which simplified early development but is less idiomatic than separate create and update handlers
  • Exception handling returns plain text messages for some error cases instead of structured problem details
  • Spring Security is included only as a commented dependency, so authentication and authorization were intentionally out of scope
  • Integration tests against a live PostgreSQL instance were not part of this version, so coverage stays at the unit level

What I learned

This project helped me understand why contract-first development is useful when the API surface needs to stay explicit from the start. Generating models and interfaces early forced me to think about request and response shapes before getting lost in JPA details.

  • How to wire OpenAPI Generator into a Gradle build for Spring Boot 3
  • How to keep generated API types separate from persistence entities using MapStruct
  • How a small hexagonal boundary makes repository swaps and unit testing easier
  • That a realistic schema is valuable for learning, even when the first implementation only covers one resource

Current status

This project is no longer maintained and should be understood as a completed learning prototype. It demonstrates contract-first API design and customer persistence against a PostgreSQL webshop schema, but it does not represent a production-ready storefront backend. I keep it in my portfolio because it shows how I approach API contracts, generated code, and layered backend structure in a domain with more complexity than a toy CRUD example.

If I revisited this today

  • Implement the remaining webshop resources incrementally, one OpenAPI tag at a time
  • Split create and update endpoints and align HTTP status codes with REST conventions
  • Add integration tests with Testcontainers so persistence behavior is verified against PostgreSQL
  • Enable Spring Security with role-based access for admin versus public customer operations
  • Return structured error responses instead of plain text bodies
  • Add pagination, filtering, and validation rules to customer list and write endpoints
  • Publish the OpenAPI spec as a reusable artifact for frontend or mobile clients
Share:
CF
ProjectContract First Webshop API

Summary

A contract-first REST API built with Spring Boot to explore API design, code generation, and layered backend architecture against a real PostgreSQL webshop schema.

Tech Stack

JavaSpringPostgreSQLGradleOpenAPI

Categories

backend

Timeline

Dec 2023 – Dec 2023

View Repository
← Back to Projects