Skip to main content
Redhat Developers  Logo
  • Products

    Featured

    • Red Hat Enterprise Linux
      Red Hat Enterprise Linux Icon
    • Red Hat OpenShift AI
      Red Hat OpenShift AI
    • Red Hat Enterprise Linux AI
      Linux icon inside of a brain
    • Image mode for Red Hat Enterprise Linux
      RHEL image mode
    • Red Hat OpenShift
      Openshift icon
    • Red Hat Ansible Automation Platform
      Ansible icon
    • Red Hat Developer Hub
      Developer Hub
    • View All Red Hat Products
    • Linux

      • Red Hat Enterprise Linux
      • Image mode for Red Hat Enterprise Linux
      • Red Hat Universal Base Images (UBI)
    • Java runtimes & frameworks

      • JBoss Enterprise Application Platform
      • Red Hat build of OpenJDK
    • Kubernetes

      • Red Hat OpenShift
      • Microsoft Azure Red Hat OpenShift
      • Red Hat OpenShift Virtualization
      • Red Hat OpenShift Lightspeed
    • Integration & App Connectivity

      • Red Hat Build of Apache Camel
      • Red Hat Service Interconnect
      • Red Hat Connectivity Link
    • AI/ML

      • Red Hat OpenShift AI
      • Red Hat Enterprise Linux AI
    • Automation

      • Red Hat Ansible Automation Platform
      • Red Hat Ansible Lightspeed
    • Developer tools

      • Red Hat Trusted Software Supply Chain
      • Podman Desktop
      • Red Hat OpenShift Dev Spaces
    • Developer Sandbox

      Developer Sandbox
      Try Red Hat products and technologies without setup or configuration fees for 30 days with this shared Openshift and Kubernetes cluster.
    • Try at no cost
  • Technologies

    Featured

    • AI/ML
      AI/ML Icon
    • Linux
      Linux Icon
    • Kubernetes
      Cloud icon
    • Automation
      Automation Icon showing arrows moving in a circle around a gear
    • View All Technologies
    • Programming Languages & Frameworks

      • Java
      • Python
      • JavaScript
    • System Design & Architecture

      • Red Hat architecture and design patterns
      • Microservices
      • Event-Driven Architecture
      • Databases
    • Developer Productivity

      • Developer productivity
      • Developer Tools
      • GitOps
    • Secure Development & Architectures

      • Security
      • Secure coding
    • Platform Engineering

      • DevOps
      • DevSecOps
      • Ansible automation for applications and services
    • Automated Data Processing

      • AI/ML
      • Data Science
      • Apache Kafka on Kubernetes
      • View All Technologies
    • Start exploring in the Developer Sandbox for free

      sandbox graphic
      Try Red Hat's products and technologies without setup or configuration.
    • Try at no cost
  • Learn

    Featured

    • Kubernetes & Cloud Native
      Openshift icon
    • Linux
      Rhel icon
    • Automation
      Ansible cloud icon
    • Java
      Java icon
    • AI/ML
      AI/ML Icon
    • View All Learning Resources

    E-Books

    • GitOps Cookbook
    • Podman in Action
    • Kubernetes Operators
    • The Path to GitOps
    • View All E-books

    Cheat Sheets

    • Linux Commands
    • Bash Commands
    • Git
    • systemd Commands
    • View All Cheat Sheets

    Documentation

    • API Catalog
    • Product Documentation
    • Legacy Documentation
    • Red Hat Learning

      Learning image
      Boost your technical skills to expert-level with the help of interactive lessons offered by various Red Hat Learning programs.
    • Explore Red Hat Learning
  • Developer Sandbox

    Developer Sandbox

    • Access Red Hat’s products and technologies without setup or configuration, and start developing quicker than ever before with our new, no-cost sandbox environments.
    • Explore Developer Sandbox

    Featured Developer Sandbox activities

    • Get started with your Developer Sandbox
    • OpenShift virtualization and application modernization using the Developer Sandbox
    • Explore all Developer Sandbox activities

    Ready to start developing apps?

    • Try at no cost
  • Blog
  • Events
  • Videos

API-first design with OpenAPI and Red Hat Fuse

July 9, 2019
Jacob Borella
Related topics:
Java
Related products:
Red Hat Fuse

Share:

    API-first design is a commonly used approach where you define the interfaces for your application before providing an actual implementation. This approach gives you a lot of benefits. For example, you can test whether your API has the right structure before investing a lot of time implementing it, and you can share your ideas with other teams early to get valuable feedback. Later in the process, delays in the back-end development will not affect front-end developers dependent on your service so much, because it's easy to create mock implementations of a service from the API definition.

    Much has been written about the benefits of API-first design, so this article will instead focus on how to efficiently take an OpenAPI definition and bring it into code with Red Hat Fuse.

    Imagine an API has been designed that is used for exposing a beer API. As you can see in the JSON file describing the API, it's an OpenAPI definition and each operation is identified by an operationId. That will prove to be handy when doing the actual implementation. The API is pretty simple and consists of three operations:

    • GetBeer—Get a beer by name.
    • FindBeersByStatus—Find a beer by its status.
    • ListBeers—Get all beers in the database.

    Keep generated code separate from the implementation

    We don’t want to code all the DTOs and boilerplate code, because that’s very time-consuming and trivial as well. Therefore, we'll use the Camel REST DSL Swagger Maven Plug-in for generating all of that.

    We want to keep the code generated by the swagger plugin separate from our implementation for several reasons, including:

    • Code generation consumes time and resources. Separating code generation from compiling allows us to spend less time waiting and thus more time drinking coffee with colleagues and being creative in all sorts of ways.
    • We don't have to worry that a developer will accidentally put some implementation stuff in an autogenerated class and thus lose valuable work the next time the stub is regenerated. Of course, we have everything under version control, but it's still time-consuming to resolve what was done, moving code, etc.
    • Other projects can refer to the generated artifacts independently of the implementation.

    To keep the generated stub separate from the implementation, we have the following initial structure:

    .
    +-- README.md
    │-- fuse-impl
    │   +-- pom.xml
    │   `-- src
    │       │-- main
    │       │   │-- java
    │       │   `-- resources
    │       `-- test
    │           │-- java
    │           `-- resources
    `-- stub
        │-- pom.xml
        `-- src
            `-- spec
    

    The folder stub contains the project for the generated artifacts. The folder fuse-impl contains our implementation of the actual service.

    Setting up code generation with Swagger

    First, configure the Swagger plugin by adding the following in the pom.xml file for the stub project:

    …
    <dependencies>
      <dependency>
        <groupId>org.apache.camel</groupId>
        <artifactId>camel-swagger-java-starter</artifactId>
      </dependency>
    …
    </dependencies>
    …
    <plugins>
      <plugin>
        <groupId>org.apache.camel</groupId>
        <artifactId>camel-restdsl-swagger-plugin</artifactId>
        <version>2.23.0</version>
        <executions>
          <execution>
            <goals>
              <goal>generate-xml-with-dto</goal><!-- 1 -->
            </goals>
          </execution>
        </executions>
        <configuration>
          <specificationUri><!-- 2 -->
            ${project.basedir}/src/spec/beer-catalog-API.json
          </specificationUri>
          <fileName>camel-rest.xml</fileName><!-- 3 -->
          <outputDirectory><!-- 4 -->
                  ${project.build.directory}/generated-sources/src/main/resources/camel-rest
          </outputDirectory>
          <modelOutput>
            ${project.build.directory}/generated-sources
          </modelOutput>
          <modelPackage>com.example.beer.dto</modelPackage><!-- 5 -->
        </configuration>
      </plugin>
    </plugins>
    ...
    

    The plugin is pretty easy to configure:

    1. The goal is set to generate-xml-with-dto, which means that a rest DSL XML file is generated from the definition together with my Data Transfer Objects. There are other options, including one to generate a Java client for the interface.
    2. specificationUri points to the location of my API definition.
    3. The name of the rest DSL XML file to generate.
    4. Where to output the generated rest DSL XML file. If placed in this location, Camel will automatically pick it up if included in a project.
    5. Package name for the DTOs.

    In pom.xml, we also need to change the location of the source and resource files for the compiler. Finally, we need to copy the API specification to the location we chose previously. This isn't described here because it's known stuff, but you can refer to the source code for the specifics as needed. Now, we're ready to generate the stub for the REST service.

    So far, we have the following file structure in the stub project:

    .
    `-- stub
        +-- pom.xml
        `-- src
            `-- spec
                `-- beer-catalog-API.json
    

    Run mvn install in the stub dir and the stub is automatically generated, compiled, put in a jar file, and put into the local Maven repository. The DTOs are generated in the package we chose previously. Furthermore, an XML file is created for the REST endpoint.

    Contents of file stub/target/generated-sources/src/main/resources/camel-rest/camel-rest.xml:

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <rests xmlns="http://camel.apache.org/schema/spring">
        <restConfiguration component="servlet"/>
        <rest>
            <get id="GetBeer" uri="/beer/{name}">
                <description>Get beer having name</description>
                <param dataType="string" description="Name of beer to retrieve" name="name" required="true" type="path"/>
                <to uri="direct:GetBeer"/>
            </get>
            <get id="FindBeersByStatus" uri="/beer/findByStatus/{status}">
                <description>Get beers having status</description>
                <param dataType="string" description="Status of beers to retrieve" name="status" required="true" type="path"/>
                <param dataType="number" description="Number of page to retrieve" name="page" required="false" type="query"/>
                <to uri="direct:FindBeersByStatus"/>
            </get>
            <get id="ListBeers" uri="/beer">
                <description>List beers within catalog</description>
                <param dataType="number" description="Number of page to retrieve" name="page" required="false" type="query"/>
                <to uri="direct:ListBeers"/>
            </get>
        </rest>
    </rests>
    

    The important thing to note is that each REST operation is routing to a uri named direct:operatorId, where operatorId is the same operator as in the API definition file. This enables us to easily provide an implementation for each operation.

    Providing an implementation of the API

    For the example implementation, we chose Fuse running in a Spring Boot container to make it easily deployable in Red Hat OpenShift.

    Besides the usual boilerplate code, the only thing we have to do is add a dependency to the project containing the stub in our pom.xml file of the fuse-impl project:

        <dependency>
          <groupId>com.example</groupId>
          <artifactId>beer</artifactId>
          <version>1.0</version>
        </dependency>
    

    Now we're all set, and we can provide our implementation of the three operations. As an example of an implementation, consider the following.

    Contents of fuse-impl/src/main/java/com/example/beer/routes/GetBeerByNameRoute.java:

    package com.example.beer.routes;
    
    import org.apache.camel.Exchange;
    import org.apache.camel.Processor;
    import org.apache.camel.builder.RouteBuilder;
    import org.apache.camel.model.dataformat.JsonLibrary;
    import org.springframework.stereotype.Component;
    
    import java.math.BigDecimal;
    import com.example.beer.service.BeerService;
    import com.example.beer.dto.Beer;
    import org.apache.camel.BeanInject;
    
    @Component
    public class GetBeerByNameRoute extends RouteBuilder {
      @BeanInject
      private BeerService mBeerService;
    
      @Override
      public void configure() throws Exception {
        from("direct:GetBeer").process(new Processor() {
    
          @Override
          public void process(Exchange exchange) throws Exception {
            String name = exchange.getIn().getHeader("name", String.class);
            if (name == null) {
              throw new IllegalArgumentException("must provide a name");
            }
            Beer b = mBeerService.getBeerByName(name);
    
            exchange.getIn().setBody(b == null ? new Beer() : b);
          }
        }).marshal().json(JsonLibrary.Jackson);
      }
    }
    

    Here we inject a BeerService, which holds information about the different beers. Then we define a direct endpoint, which provides the endpoint, to which the REST call is routed (remember the operationId mentioned earlier?). The processor tries to look up the beer. If no beer is found, an empty beer object is returned. To try the example, you can run:

    cd fuse-impl
    mvn package
    java -jar target/beer-svc-impl-1.0-SNAPSHOT.jar
    #in a separate terminal
    curl http://localhost:8080/rest/beer/Carlsberg
    {"name":"Carlsberg","country":"Denmark","type":"pilsner","rating":5,"status":"available"}
    

    We might have to do this over and over again. In that case, we can create a Maven archetype for the two projects. Alternatively, we can clone a template project containing all the boilerplate code and do the necessary changes from there. That will be a bit more work, though, because we'll have to rename Maven modules as well as Java classes, but it's not too much of a hassle.

    Conclusion

    With an API-first approach, you can design and test your API before doing the actual implementation. You can get early feedback on your API from the people using it, without having to provide an actual implementation. In this way, you can save time and money.

    Going from design to actual implementation is easy with Red Hat Fuse. Just use the Camel REST DSL Swagger Maven Plugin to generate a stub and you are set for providing the actual implementation. If you want to try it for yourself, use the example code as a starting point.

    Last updated: June 23, 2023

    Recent Posts

    • How Kafka improves agentic AI

    • How to use service mesh to improve AI model security

    • How to run AI models in cloud development environments

    • How Trilio secures OpenShift virtual machines and containers

    • How to implement observability with Node.js and Llama Stack

    Red Hat Developers logo LinkedIn YouTube Twitter Facebook

    Products

    • Red Hat Enterprise Linux
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform

    Build

    • Developer Sandbox
    • Developer Tools
    • Interactive Tutorials
    • API Catalog

    Quicklinks

    • Learning Resources
    • E-books
    • Cheat Sheets
    • Blog
    • Events
    • Newsletter

    Communicate

    • About us
    • Contact sales
    • Find a partner
    • Report a website issue
    • Site Status Dashboard
    • Report a security problem

    RED HAT DEVELOPER

    Build here. Go anywhere.

    We serve the builders. The problem solvers who create careers with code.

    Join us if you’re a developer, software engineer, web designer, front-end designer, UX designer, computer scientist, architect, tester, product manager, project manager or team lead.

    Sign me up

    Red Hat legal and privacy links

    • About Red Hat
    • Jobs
    • Events
    • Locations
    • Contact Red Hat
    • Red Hat Blog
    • Inclusion at Red Hat
    • Cool Stuff Store
    • Red Hat Summit

    Red Hat legal and privacy links

    • Privacy statement
    • Terms of use
    • All policies and guidelines
    • Digital accessibility

    Report a website issue