Menu

Spring Boot Topic

OpenAPI as Contract-First Development with Spring Boot

What is OpenAPI?

OpenAPI is a specification for describing RESTful APIs in a standardized, language-agnostic format (YAML or JSON). It serves as a contract between API providers and consumers, defining endpoints, request/response formats, authentication, and more.

Key Benefits:

  • Documentation: Auto-generates interactive API docs

  • Code Generation: Creates client SDKs and server stubs

  • Validation: Enforces API consistency

  • Testing: Enables contract testing

  • Collaboration: Serves as single source of truth

OpenAPI as Contract-First Development

Contract-First Approach:

  1. Design API first using OpenAPI specification

  2. Review/approve contract with stakeholders

  3. Generate code from the contract

  4. Implement the generated interfaces

Example: Movie API OpenAPI Contract

yaml

openapi: 3.0.3
info:
  title: Movie API
  description: REST API for managing movies
  version: 1.0.0

servers:
  - url: http://localhost:8080/api
    description: Development server

paths:
  /movies:
    get:
      summary: Get all movies
      responses:
        '200':
          description: List of movies
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Movie'
    post:
      summary: Create a new movie
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MovieRequest'
      responses:
        '201':
          description: Movie created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Movie'

  /movies/{id}:
    get:
      summary: Get movie by ID
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: Movie found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Movie'
        '404':
          description: Movie not found

components:
  schemas:
    Movie:
      type: object
      properties:
        id:
          type: integer
          example: 1
        title:
          type: string
          example: "Inception"
        year:
          type: integer
          example: 2010
        director:
          type: string
          example: "Christopher Nolan"
        genre:
          type: string
          example: "Sci-Fi"
      required:
        - id
        - title
        - year

    MovieRequest:
      type: object
      properties:
        title:
          type: string
          example: "Inception"
        year:
          type: integer
          example: 2010
        director:
          type: string
          example: "Christopher Nolan"
        genre:
          type: string
          example: "Sci-Fi"
      required:
        - title
        - year

Spring Boot Implementation

1. Add Dependencies (pom.xml)

xml

<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.5.0</version>
</dependency>
<dependency>
    <groupId>org.openapitools</groupId>
    <artifactId>openapi-generator-maven-plugin</artifactId>
    <version>7.5.0</version>
</dependency>

2. Generate Code from OpenAPI Spec

Add Maven plugin configuration:

xml

<plugin>
    <groupId>org.openapitools</groupId>
    <artifactId>openapi-generator-maven-plugin</artifactId>
    <executions>
        <execution>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <inputSpec>${project.basedir}/src/main/resources/api/movie-api.yaml</inputSpec>
                <generatorName>spring</generatorName>
                <apiPackage>com.example.movie.api</apiPackage>
                <modelPackage>com.example.movie.model</modelPackage>
                <configOptions>
                    <interfaceOnly>true</interfaceOnly>
                    <useSpringBoot3>true</useSpringBoot3>
                </configOptions>
            </configuration>
        </execution>
    </executions>
</plugin>

3. Implement the Generated Interface

Movie Controller Implementation:

java

@RestController
@RequestMapping("/api")
public class MovieController implements MoviesApi {
    
    private final MovieService movieService;
    private final ModelMapper modelMapper;
    
    public MovieController(MovieService movieService) {
        this.movieService = movieService;
        this.modelMapper = new ModelMapper();
    }
    
    @Override
    public ResponseEntity<List<Movie>> getMovies() {
        List<MovieDTO> movies = movieService.getAllMovies();
        List<Movie> apiMovies = movies.stream()
            .map(dto -> modelMapper.map(dto, Movie.class))
            .collect(Collectors.toList());
        return ResponseEntity.ok(apiMovies);
    }
    
    @Override
    public ResponseEntity<Movie> getMovieById(Integer id) {
        return movieService.getMovieById(id)
            .map(dto -> ResponseEntity.ok(modelMapper.map(dto, Movie.class)))
            .orElse(ResponseEntity.notFound().build());
    }
    
    @Override
    public ResponseEntity<Movie> createMovie(MovieRequest movieRequest) {
        MovieDTO movieDTO = modelMapper.map(movieRequest, MovieDTO.class);
        MovieDTO savedDTO = movieService.createMovie(movieDTO);
        Movie response = modelMapper.map(savedDTO, Movie.class);
        return ResponseEntity.status(HttpStatus.CREATED).body(response);
    }
}

4. Service Layer

java

@Service
public class MovieService {
    
    private final Map<Integer, MovieDTO> movies = new ConcurrentHashMap<>();
    private final AtomicInteger idCounter = new AtomicInteger(1);
    
    public List<MovieDTO> getAllMovies() {
        return new ArrayList<>(movies.values());
    }
    
    public Optional<MovieDTO> getMovieById(Integer id) {
        return Optional.ofNullable(movies.get(id));
    }
    
    public MovieDTO createMovie(MovieDTO movieDTO) {
        Integer id = idCounter.getAndIncrement();
        movieDTO.setId(id);
        movies.put(id, movieDTO);
        return movieDTO;
    }
}

5. DTO Classes

java

@Data
public class MovieDTO {
    private Integer id;
    private String title;
    private Integer year;
    private String director;
    private String genre;
}

6. Configure OpenAPI UI

java

@Configuration
public class OpenAPIConfig {
    
    @Bean
    public OpenAPI movieOpenAPI() {
        return new OpenAPI()
            .info(new Info()
                .title("Movie API")
                .description("REST API for managing movies")
                .version("1.0.0"))
            .addServersItem(new Server()
                .url("/api")
                .description("API Server"));
    }
}

Development Workflow

Step 1: Design Contract

bash

# Create OpenAPI spec
src/main/resources/api/movie-api.yaml

Step 2: Generate Code

bash

mvn clean compile
# Generates:
# - MoviesApi.java (interface)
# - Movie.java (model)
# - MovieRequest.java (model)

Step 3: Implement Business Logic

  • Implement the generated MoviesApi interface

  • Add service layer

  • Add data access layer

Step 4: Test & Validate

java

@SpringBootTest
@AutoConfigureMockMvc
class MovieControllerTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @Test
    void createMovie_ShouldReturnCreated() throws Exception {
        String movieJson = """
            {
                "title": "Inception",
                "year": 2010,
                "director": "Christopher Nolan",
                "genre": "Sci-Fi"
            }
            """;
        
        mockMvc.perform(post("/api/movies")
                .contentType(MediaType.APPLICATION_JSON)
                .content(movieJson))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.id").exists())
                .andExpect(jsonPath("$.title").value("Inception"));
    }
}

Step 5: Access Documentation

  • Swagger UI: http://localhost:8080/swagger-ui.html

  • OpenAPI JSON: http://localhost:8080/v3/api-docs

Contract Testing

java

@ContractTest
public class MovieApiContractTest {
    
    @Test
    void shouldAdhereToOpenAPISpec() {
        // Use tools like Spring Cloud Contract or Pact
        // to verify implementation matches spec
    }
}

Best Practices

  1. Version Control: Store OpenAPI spec in version control

  2. Validation: Use OpenAPI validators during CI/CD

  3. Client Generation: Generate client SDKs for consumers

  4. Backward Compatibility: Follow semantic versioning

  5. Documentation: Use OpenAPI extensions for better docs

Tools Integration

  • Swagger Editor: Design/validate OpenAPI specs

  • OpenAPI Generator: Generate code in multiple languages

  • Postman: Import OpenAPI for testing

  • GitHub Actions: Validate spec on PRs

Benefits in Microservices

  • Consistency: Uniform API design across services

  • Parallel Development: Frontend/backend can work simultaneously

  • Automation: Reduces boilerplate code

  • Quality: Early detection of design issues

This contract-first approach ensures your API is well-designed, documented, and consistent before implementation begins, reducing integration issues and improving developer experience.

Comments (0)

No comments yet. Be the first to comment!

LecturreAI Assistant

Hello! I'm LecturreAI

What would you like to learn today?