LIGHT

  • News
  • Docs
  • Community
  • Reddit
  • GitHub

OpenAPI 3.0 Specification Best Practices

OpenAPI 3.0 specification is a very loose specification which gives the designer many options to write the spec. In most cases, developers write the code with annotations and generate the specification afterward. With an enterprise scale in mind, we encourage a design first approach. The outcome is not just a document but a specification that can be used to scaffold a new project and loaded during runtime to verify JWT scopes for security and validate requests to protect the business layer against attacks.

To enable light-codegen to generate meaningful code and utilize the full potential of the light-rest-4j framework, the author of the OpenAPI 3.0 specification should follow the best practices below.

Guidelines

  • clear and easily readable by architects, analysts, developers
  • well documented, with explanations provided in description tags
  • adheres to OpenAPI specifications - v3.0 at the time of this writing
  • uses a design which lends itself to a clean and easily consumable object model

Security First

Often API designers focus on functionalities and add security later on. We would encourage to follow security first approach so that security is considered for every endpoint during the design.

Before working on a new specification, you can copy from an existing one. Petstore is a good starting point. There are also other OpenAPI specifications in the model-config repository to help you in learning the design style.

Within the petstore specification you can find the following block that defines the security under the component. This section is optional in the specification, but here it is mandatory. light-codegen throws an error if securitySchemes are not found.

components:
  securitySchemes:
    petstore_auth:
      type: oauth2
      description: This API uses OAuth 2 with the client credential grant flow.
      flows:
        clientCredentials:
          tokenUrl: 'https://localhost:6882/token'
          scopes:
            'write:pets': modify pets in your account
            'read:pets': read your pets

Once the securitySchemes is defined, you can specify security scopes for each endpoint. When you add a new endpoint you might ask yourselves a question. Do I need to create a brand new scope or pick one or more existing scopes from the scopes defined in securitySchemes?

The endpoint/operation definition with security definition looks like this.

  '/pets/{petId}':
    get:
      summary: Info for a specific pet
      operationId: showPetById
      tags:
        - pets
      parameters:
        - name: petId
          in: path
          required: true
          description: The id of the pet to retrieve
          schema:
            type: string
      security:
        - petstore_auth:
            - 'read:pets'
      responses:
        '200':
          description: Expected response to a valid request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pet'
              example:
                id: 1
                name: Jessica Right
                tag: pet

Above design ensures that the light-rest-4j framework compares the scopes from JWT token against the scopes defined for each endpoint at runtime. It gives you the flexibility to grant permissions based on endpoints to consumers.

Schemas

In the specification, models define what would be the request/response body for each endpoint. There are two different places to define models. Flattened/scattered in each endpoint or extracted into the schemas in the components.

When the model definition scattered in each endpoint, there is no name and the same model might be duplicated in several endpoints. The generator does not generate POJO classes as it does not make sense to generate some classes named body0, body1, body2, etc. Chances are body0 is for get request, and body2 is for put request. As both of them are dealing a same set of attributes, they are the same. How would developers remember that in your get handler, you should use body0 but in your put handler, you should use body2? They would be shocked when they found that these two classes have the same fields and methods except the class names are different.

To generate POJO classes with proper names and to avoid duplications, it is best to extract common data objects into the schemas section under the components.

Here is an example in the petstore specification.

  schemas:
    Pet:
      required:
        - id
        - name
      properties:
        id:
          type: integer
          format: int64
        name:
          type: string
        tag:
          type: string

The above Pet definition is used to generate a Java class called Pet in light-codegen. With Pet is defined, you can put $ref in each point for schema definition. Here is an example response that utilizes the Pet schema definition.

      responses:
        '200':
          description: Expected response to a valid request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pet'
              examples:
                response:
                  value:
                    id: 1
                    name: Jessica Right
                    tag: pet

Here is another response example with an array. You can see both endpoints are sharing the same Pet schema and in the generated code, the corresponding handlers share the same POJO class called Pet.

      responses:
        '200':
          description: An paged array of pets
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Pet'
              example:
                - id: 1
                  name: catten
                  tag: cat
                - id: 2
                  name: doggy
                  tag: dog

Examples

OpenAPI specification gives you an opportunity to define an example response for each endpoint so that your API consumer can easily understand what would be expected when the endpoint is accessed. Also, light-codegen has a feature to generate mock responses based on the examples defined in the specification. Once you have examples defined, the generated project can be built and started with mock responses for consumers to start their work immediately without waiting for the provider to complete the API implementation.

For more details on this topic, please refer to OpenAPI 3.0 Mock.

Naming Convention

As schema name is translated into Java class name, it is better to follow the naming convention of Java.

  • Elements should always start with an upper-case letter, to respect class definitions in generated code, which always start with an upper-case letter.
  properties:
     error:
       $ref: 'org/APPERR0001/1.0.1/errors.yaml#/error'
  ...
  vs
  ...
  properties:
    error:
      $ref: 'org/APPERR0001/1.0.1/errors.yaml#/Error'
  • Elements should use only alpha-numeric characters and avoid underscores, @ signs or others. OpenAPI general guidelines recommend alpha-numeric only, and, while these would generate correct programming language code, it would break accepted programming guidelines.
  "@nameID":
    $ref: "org/APIDOMAIN0001/0.0.1/elementDefs.yaml#/NameID"
  "@accessCode":
    $ref: "org/APIDOMAIN0001/0.0.1/elementDefs.yaml#/Access_Code"
...
  filterList:
     type: array
     items:
       $ref: "org/APIDOMAIN0001/0.0.1/elementDefs.yaml#/APIFilter_Type"
...
vs
...
  nameID:
    $ref: "org/APIDOMAIN0001/0.0.2/elementDefs.yaml#/NameID"
  accessCode:
    $ref: "org/APIDOMAIN0001/0.0.2/elementDefs.yaml#/AccessCode"
...
  filterList:
    type: array
    items:
      $ref: "org/APIDOMAIN0001/0.0.2/elementDefs.yaml#/APIFilterType"    

Object definitions are to be avoided from the declaration of Body elements

The objects should be moved to the components/schemas section of the specification or in an external document, for shared definitions.

The Body should not contain any declarations as in “type: object”.

Ex.: selection element:

Original version:

  ...
  selection:
    description:  Identifies the selection to retrieve information for. Only one of the child elements of this structure are to be provided.
    type: object
    properties:
      id:
        $ref: "org/APIDOMAIN0001/0.0.1/elementDefs.yaml#/ID"
      card:
        $ref: "org/APIDOMAIN0001/0.0.1/elementDefs.yaml#/AlternativeID"
  ...      

Updated version:

...
  selection:
    $ref: "#/components/schemas/Selection"
  ...
  schemas:
    Selection:
      type: object
      description:  Identifies the selection to retrieve information for. Only one of the child elements of this structure are to be provided.
      properties:
        id:
          $ref: "org/APIDOMAIN0001/0.0.2/elementDefs.yaml#/ID"
        card:
          $ref: "org/APIDOMAIN0001/0.0.2/elementDefs.yaml#/AlternativeID"
  ...

Implicit object definitions are to be avoided from the declaration of Body elements when used from collection declarations

An object should be defined in the components/schemas section of the specification or an external document, for shared definitions, and be referenced from the collection in the Body, instead of the declaration of an implicit object.

The Body should not contain any collection declarations with implicit object definitions; the equivalent of Java inline declarations.

Ex.: names element:

Original version:

  ...
  names:
    description: Contact information.
    type: array
    items:
      properties:
        "id":
          $ref: "org/APIDOMAIN0001/0.0.1/elementDefs.yaml#/ID"
        name:
          description: A name can contain up to two lines of name information.
          type: array
          items:
            $ref: "org/APIDOMAIN0001/0.0.1/elementDefs.yaml#/Name"
  ...          

Updated version:

...
  names:
    type: array
    items:
      $ref: "#/components/schemas/RetrieveNames"
  ...
  schemas:
  RetrieveNames:
    description: Contact information.
    type: object
    properties:
      id:
        $ref: "org/APIDOMAIN0001/0.0.2/elementDefs.yaml#/ID"
      name:
        description: A name can contain up to two lines of name information.
        type: array
        items:
          $ref: "org/APIDOMAIN0001/0.0.2/elementDefs.yaml#/Name"
  ...
  • About Light
    • Overview
    • Testimonials
    • What is Light
    • Features
    • Principles
    • Benefits
    • Roadmap
    • Community
    • Articles
    • Videos
    • License
    • Why Light Platform
  • Getting Started
    • Get Started Overview
    • Environment
    • Light Codegen Tool
    • Light Rest 4j
    • Light Tram 4j
    • Light Graphql 4j
    • Light Hybrid 4j
    • Light Eventuate 4j
    • Light Oauth2
    • Light Portal Service
    • Light Proxy Server
    • Light Router Server
    • Light Config Server
    • Light Saga 4j
    • Light Session 4j
    • Webserver
    • Websocket
    • Spring Boot Servlet
  • Architecture
    • Architecture Overview
    • API Category
    • API Gateway
    • Architecture Patterns
    • CQRS
    • Eco System
    • Event Sourcing
    • Fail Fast vs Fail Slow
    • Integration Patterns
    • JavaEE declining
    • Key Distribution
    • Microservices Architecture
    • Microservices Monitoring
    • Microservices Security
    • Microservices Traceability
    • Modular Monolith
    • Platform Ecosystem
    • Plugin Architecture
    • Scalability and Performance
    • Serverless
    • Service Collaboration
    • Service Mesh
    • SOA
    • Spring is bloated
    • Stages of API Adoption
    • Transaction Management
    • Microservices Cross-cutting Concerns Options
    • Service Mesh Plus
    • Service Discovery
  • Design
    • Design Overview
    • Design First vs Code First
    • Desgin Pattern
    • Service Evolution
    • Consumer Contract and Consumer Driven Contract
    • Handling Partial Failure
    • Idempotency
    • Server Life Cycle
    • Environment Segregation
    • Database
    • Decomposition Patterns
    • Http2
    • Test Driven
    • Multi-Tenancy
    • Why check token expiration
    • WebServices to Microservices
  • Cross-Cutting Concerns
    • Concerns Overview
  • API Styles
    • Light-4j for absolute performance
    • Style Overview
    • Distributed session on IMDG
    • Hybrid Serverless Modularized Monolithic
    • Kafka - Event Sourcing and CQRS
    • REST - Representational state transfer
    • Web Server with Light
    • Websocket with Light
    • Spring Boot Integration
    • Single Page Application
    • GraphQL - A query language for your API
    • Light IBM MQ
    • Light AWS Lambda
    • Chaos Monkey
  • Infrastructure Services
    • Service Overview
    • Light Proxy
    • Light Mesh
    • Light Router
    • Light Portal
    • Messaging Infrastructure
    • Centralized Logging
    • COVID-19
    • Light OAuth2
    • Metrics and Alerts
    • Config Server
    • Tokenization
    • Light Controller
  • Tool Chain
    • Tool Chain Overview
  • Utility Library
  • Service Consumer
    • Service Consumer
  • Development
    • Development Overview
  • Deployment
    • Deployment Overview
    • Frontend Backend
    • Linux Service
    • Windows Service
    • Install Eventuate on Windows
    • Secure API
    • Client vs light-router
    • Memory Limit
    • Deploy to Kubernetes
  • Benchmark
    • Benchmark Overview
  • Tutorial
    • Tutorial Overview
  • Troubleshooting
    • Troubleshoot
  • FAQ
    • FAQ Overview
  • Milestones
  • Contribute
    • Contribute to Light
    • Development
    • Documentation
    • Example
    • Tutorial
“OpenAPI 3.0 Specification Best Practices” was last updated: July 5, 2021: fixes #275 checked and corrected grammar/spelling for majority of pages (#276) (b3bbb7b)
Improve this page
  • News
  • Docs
  • Community
  • Reddit
  • GitHub
  • About Light
  • Getting Started
  • Architecture
  • Design
  • Cross-Cutting Concerns
  • API Styles
  • Infrastructure Services
  • Tool Chain
  • Utility Library
  • Service Consumer
  • Development
  • Deployment
  • Benchmark
  • Tutorial
  • Troubleshooting
  • FAQ
  • Milestones
  • Contribute