Testing OpenAPI specification compliance in Quarkus with Prism

Quarkus is a good framework choice when developing RESTful APIs in an API-first manner by using the OpenAPI generator to generate models and API interfaces. That way, larger parts of compliance with the API specification are already handled by the compiler. However, not every inconsistency can be detected this way. In this post, I demonstrate how to integrate Stoplight’s Prism proxy into the test infrastructure of a Quarkus Kotlin project for validating OpenAPI specification compliance automatically as part of the API tests. Table of contentsMotivationValidating OpenAPI compliance with PrismIntegrating Prism into QuarkusImplementing a Quarkus test resourceRedirecting test requests through the test resourceDetecting and fixing a bug in the example projectUsing the test resource in integration testsTrading confidence for test execution timeSummary MotivationIn API-first development with Quarkus and Kotlin I have shown a basic setup for Quarkus to support API-first development. As a short recap, API-first development is an approach of developing (RESTful) APIs, where a formal specification of the intended API (changes) is created before implementing the API provider or consumers. That way, we can make use of the specification for code generation, parallel development, and for verification purposes. A common issue seen when using APIs based on their documentation is that the actual API implementation differs from what is documented. The setup shown before prevents larger parts of this problem by leveraging code generation through the OpenAPI Generator. Moreover, the compiler catches a few error cases when generated interfaces are not implemented properly. However, not every aspect of API compliance is validated this way and we would still be able to provide an implementation that deviates from the specification. Here’s a short example demonstrating one of the more obvious ways we can still deviate: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 # ... paths: /pets: get: summary: List all pets operationId: listPets responses: '200': description: An array of pets content: application/json: schema: $ref: "#/components/schemas/Pets" # ... components: schemas: PetId: type: integer format: int64 Pet: type: object required: - id - name properties: id: $ref: "#/components/schemas/PetId" name: type: string Pets: type: array items: $ref: "#/components/schemas/Pet" ...

December 4, 2024 · updated December 6, 2024 · 16 min

API-first development with Quarkus and Kotlin

API-first development is a way of developing distributed systems that makes the API specifications of the system components a first-class citizen in the development process. This approach promises to better control the challenges of loosely coupled components communicating via inter-process communication so that the benefits of separating a monolith into individual components pay off sooner. In this post, I will give a brief introduction on what API-first development is and how my preferred JVM-based setup for developing microservices using the API-first principles looks like at the moment. Table of contentsWhat is API-first developmentAPI-first development for REST APIs with OpenAPIAPI-first and code-first development with OpenAPISetting up an API-first development workflow in QuarkusAdd the API definition to the projectEnable SwaggerUIOnly serve the defined API in SwaggerUILet Quarkus determine the server in the OpenAPI specificationGenerate API stubs using the OpenAPI GeneratorImplementing a generated API resourceSummaryBibliography What is API-first developmentIn a distrusted system, such as a microservice architecture, inter-process communication plays a crucial role. But it comes at a price. Distributed systems are often harder to debug and to refactor. In a monolith, the IDE and the compiler help refactoring programming APIs such as an interface or abstract base class. A mismatch between the API provider and the consumer will usually be caught by the compiler. In a distributed system, usually no compiler helps when changing APIs and we cannot rely on it to find mismatches between consumers and providers. Without care and special testing techniques, errors will only surface at runtime, making the system more brittle and changes more costly. Therefore, it is important that: APIs are well-designed so that breaking or incompatible changes are infrequentif required, breaking or incompatible changes happen in a controlled way that prevents API consumers from suddenly failing without prior notice....

August 16, 2024 · updated December 6, 2024 · 16 min