Skip to content

Latest commit

 

History

History
executable file
·
416 lines (334 loc) · 15.7 KB

README.adoc

File metadata and controls

executable file
·
416 lines (334 loc) · 15.7 KB

Spring Boot Example

API CodeFlow Logo

This project serves as a demo for an API CodeFlow approach to API service implementation. This entails the following three steps, repeated as the API evolves:

  1. Design your API in OpenApi.

  2. Generate stub code in Java using the Spring Boot framework.

  3. Implement your business logic.

This demo will create a working API to manage a database of live pets for a pet store. The API will support adding a new pet, updating an existing pet, deleting a pet, listing all pets, and showing the pet with a given ID.

Working Through the Demo

Overview

The demo proceeds in four phases (plus some initial set-up):

The phases are:

  1. Setup - install and launch RepreZen API Studio.

  2. Design - Create one of the standard OpenAPI v3 example models available in RepreZen API Studio.

  3. Generate - Use one of the available code generators to generate a new server for the API, using Spring Boot.

  4. Implement - Write your business logic into the generated stubs.

  5. Repeat (DesignGenerateImplement) in order to add a new method to the API

So let’s get going!

Phase 0 - Setup

If you don’t already have RepreZen API Studio installed, you’ll want to visit the RepreZen website and sign up for a free trial. If you’re already an Eclipse IDE user, you may want to consider installing API Studio from Eclipse Marketplace, rather than using the stand-alone installer offered in the sign-up process. Installation options are explained here.

Phase 1 - Design

We won’t actually design a model here. Instead, we’ll just use one of the OpenApi3 models available from the API Studio Examples Wizard.

Follow these steps:

  1. Click on the drop-down arrow of the New tool in the toolbar, just under the File menu.

  2. Select RepreZen Examples to open the Examples Wizard.

    Open the Examples Wizard
  3. Click on the OpenAPI v3 tab.

  4. Select the Expanded Pet Store (v3) example, and press Finish.

    Expanded Pet Store Example
  5. You should see a new project in your workspace, and the example model file itself will automatically open in an editor.

  6. Browse through the model briefly to familiarize yourself with its operations and other components.

Phase 2 - Generate

We’ll make use of one of the built-in API Studio GenTemplates to create our server implementation. This GenTemplate is one of dozens that are adapted for use in API Studio from the OpenAPI Generator open-source project.

💡
We need to use a later version of the OpenAPI Generator library than is packaged with API Studio at the time of this writing. Steps 6-9 below accomplish this.

Follow these steps:

  1. In your model project, locate the petstore-expanded.yaml file in the models folder, and double-click on it.

  2. Click on the Create a New GenTarget button in the toolbar, just to the left of the Generate button/menu.

    💡
    If you do not see this in the toolbar, be sure that you are in the RepreZen perspective, by clicking on the appropriate button on the far right of the toolbar: reprezen perspective.
    Create GenTarget
  3. Type "spring" in the resulting dialog’s search box, and you should see the Java Spring (Boot…​) GenTemplate in the list.

  4. Select the Java Spring GenTemplate and press Finish. A new GenTarget is created in your project, and the .gen file that describes it opens in an editor.

    select gentemplate
  1. Modify the new .gen file (which should be showing now in your active editor) as shown in the following table. You can copy/paste directly from this table into the .gen file.

    Parameter Name

    Value

    Output Location and Packages

    relativeOutputDir

    ../../../implementation/springboot-petstore-demo

    modelPackage

    com.reprezen.demo.springboot.model

    apiPackage

    com.reprezen.demo.springboot.api

    invokerPackage

    com.reprezen.demo.springboot

    configPackage

    com.reprezen.demo.springboot.swaggerui

    basePackage

    com.reprezen.demo.springboot

    Maven Artifacts

    groupId

    com.reprezen.demo

    artifactId

    petstore-demo

    artifactDescription

    Demontration of API CodeFlow with Spring Boot generated from an OpenAPI3 doc

    Generated Java Classes

    openApiCodegenConfig

    openApiCodegenConfig:
      hideGenerationTimestamp: true
      delegatePattern: true
      # the java8 option generates default methods in interfaces.
      # This means that omitting a required  method in an
      # implementation class does not cause an error to be flagged.
      # This reduces the effectiveneses of the API CodeFlow process.
      java8: false
    💡
    This value is a YAML object. In order to clarify how the value should appear in the .gen file, we have included the property name with the vaule. That name, openApiCodegenConfig, is not part of the value.
  2. Double-click on the model project’s pom.xml file to open it in an editor.

    ⚠️
    Make sure this file is the one at top-level in the model project, not the file of the same name in the newly created GenTarget folder (or in any other GenTarget folder).
  3. Click on the Dependencies tab at the bottom of the editor, then click on the Add…​ button.

    open pom file
  4. Fill out the dialog as shown, then click OK. Values (for copy/paste) are:

    1. org.openapitools

    2. openapi-generator

    3. 3.2.3

      pom dependency
  5. Save and close the pom.xml editor.

  6. Run the generator, by clicking on the big Generate button in the toolbar. (Since we’ve been actively editing the .gen file for the Java Spring GenTarget, the menu should show that as the generator to run. If not, click instead on the small arrow to the right, and select Spring Boot from the list of targets.)

    generate button
  7. The prior step caused a new folder named implementation to appear in our model project. Normally, generated files are placed in a folder named generated in the GenTarget folder, but we changed that by editing the relativeOutputDir property in the .gen file.

    We will now turn that implementation folder into a Java project in its own right. We can do that easily because the generator created a Maven pom.xml file in the output directory.

    Right-click on the implementation folder and select Import…​, then select Maven / Existing Maven Projects in the resulting dialog, and press Next.

    You should see your implementation folder in the Root Directory field, and the project should appear, already checked, in the Projects list. Click Finish to create the project.

    A build of the new project will start immediately, and will probably take several seconds.

    import maven

Phase 3 - Implement

One of the generated class is an interface named PetsApiDelegate, in the com.reprezen.demo.springboot.api package. In the next phase we will create a corresponding implementation class, containing the business logic for our service.

Follow these steps:

  1. Modify the pom.xml file so that the project is built using Java 8. This is needed because we set the java8 parameter to false in the .gen file. We did that to prevent generation of default methods in generated interfaces, but we really do want to build with Java8.

    Open the pom.xml file in the new petstore-demo project, and alter its java.version property value to 1.8, then save the file.

    pom java8
  2. Now that we’ve modified the pom.xml file, we need to add it to the .openapi.generator.ignore file, so re-generation will leave our changes in place. The file has a format similar to git’s .gitignore file.

    You probably won’t see this file in project explorer, because by default, files with names starting with a dot are not shown. You can show them by opening the drop-down menu in the project explorer toolbar and selecting Filters and Customization…​. Uncheck the .resources checkbox, and you should now see the .openapi.generator.ignore file.

    unfilter dot files

    Once you’re able to see the file, open it and add pom.xml on a line by itself at the end.

    💡
    You may want to re-check the . resources* filter once you’ve made this change.
  3. We need to update the project so that the pom file changes will take effect. Right-click on the project name, and select Maven → Update Project…​`. Press _OK in the dialog that appears.

    maven update
  4. Create our implementation class. Start by right-clicking on the com.reprezen.demo.springboot.api package in the src/main/java folder, and select New → Class. Name the class PetsApiDelegateImpl.

    create class
  5. Replace the text of the class definition with the following:

    package com.reprezen.demo.springboot.api;
    
    @Service
    public class PetsApiDelegateImpl implements PetsApiDelegate {
     	private final Map<Long, Pet> pets = Maps.newHashMap();
    	private long nextId = 0l;
     	@Override
    	public ResponseEntity<Pet> addPet(NewPet newPet) {
    		Pet petToAdd = new Pet();
    		petToAdd.id(nextId++).name(newPet.getName()).tag(newPet.getTag());
    		pets.put(petToAdd.getId(), petToAdd);
    		return new ResponseEntity<>(petToAdd, HttpStatus.CREATED);
    	}
     	@Override
    	public ResponseEntity<Void> deletePet(Long id) {
    		if (!pets.containsKey(id)) {
    			return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    		}
    		pets.remove(id);
    		return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    	}
     	@Override
    	public ResponseEntity<Pet> findPetById(Long id) {
    		if (!pets.containsKey(id)) {
    			return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    		}
    		return new ResponseEntity<>(pets.get(id), HttpStatus.ACCEPTED);
    	}
     	@Override
    	public ResponseEntity<List<Pet>> findPets(List<String> tags, Integer limitObject) {
    		int limit = limitObject == null ? Integer.MAX_VALUE : limitObject;
    		List<Pet> filteredPets = pets.values().stream()//
    				.filter(pet -> (tags == null || tags.isEmpty()) ? true : tags.contains(pet.getTag()))//
    				.limit(limit)//
    				.collect(Collectors.toList());
    		return new ResponseEntity<>(filteredPets, HttpStatus.ACCEPTED);
    	}
     }
    💡
    We are using a simple HashMap to keep track of our pets. A real-life implementation would presumably make use of a production database.

    Don’t freak out at all the red error markers! :-)

  6. Add missing imports. Right-click in the editor and select Source → Organize Imports.

    organize imports
    • Choose com.google.common.collect.Maps to resolve the Maps type.

    • Choose java.util.List to resolve the List type.

    • All other types should be resolved automatically.

    Save the file once you’re finished fixing the imports.

  7. Launch the service. Right-click on the petstore-demo project, and select Run As → Maven build…​. In the dialog that appears, type spring-boot:run in the Goals field, and click Run.

    run service
  8. Exercise the service using Swagger-UI, by visiting http://localhost:8080/. If you open the pets-api-controller menu you’ll see all the operations defined in the model. Click on any one of them and click the Try it out button to get an HTML form that you can use to actually send a request to the running service.

    swagger ui

Phase 4 - Repeat

Our service does not include any means to update an existing pet, other than deleting and recreating the pet - an option that will fail to retain the originally assigned pet id.

We can fix this by adding a new PUT method. The operation will expect a pet id value as a path parameter and the new pet data in the request payload. The effect will be to replace an existing pet record with that id.

Our approach for this and future API changes is to iterate on the API CodeFlow Design → Generate → Implement cycle.

Repeat:Design

Open the petstore-expanded.yaml file in your model project, and add the new operation definition to the /pets/{id} path item.

You can copy and paste the following into the file immediately after the /pets/{id}: line. Be careful to maintain correct indentation; the method name put should be indented two spaces to the right as compared to the /pets/{id} path string. Also, make sure the following get operation is still intact; it’s easy for the pasted text to omit a final newline, which will result in get appearing at the end of the last line of the put operation definition - not cool. Break up the line and correct indentation as needed.

    put:
      description: Update a pet based on the ID
      operationId: updatePet
      requestBody:
        content:
          "application/json":
            schema:
              $ref: "#/components/schemas/NewPet"
      parameters:
        - name: id
          in: path
          description: ID of pet to fetch
          required: true
          schema:
            type: integer
            format: int64
      responses:
        200:
          description: pet response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pet'
        default:
          description: unexpected error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

Repeat:Generate

Here we just re-run the Spring Boot GenTarget by pressing the big Generate button again.

Updated source files, reflecting the new PUT method, will replace most of the existing files in the petstore-demo project. Our implementation class is not removed, and our customized pom.xml file is not replaced (thanks to our listing it in the .openapi-codegen-ignore file).

Repeat:Implement

At this point, all looks fine in the GUI, except in fact there is a problem with the petstore-demo project. You can see this by selecting Project → Clean…​ in the toolbar, and clicking Clean in the resulting dialog. This will cause the demo project to be rebuilt, and the result will be an error marker on the PetsApiDelegateImpl class.

This is not surprising, because the interface implemented by that class now declares a method, updatePet(Long, NewPet), that we never implemented. So of course, we now need to implement that method.

Open the PetsApiDelegateImpl class, and add the following method definition to the class, then save the file. An automatic rebuild will then clear the error marker.

@Override
public ResponseEntity<Pet> updatePet(Long id, NewPet newPet) {
	if (!pets.containsKey(id)) {
		return new ResponseEntity<>(HttpStatus.NOT_FOUND);
	}
	pets.get(id).name(newPet.getName()).tag(newPet.getTag());
	return new ResponseEntity<>(pets.get(id), HttpStatus.ACCEPTED);
}

You can now restart the service. Make sure to stop the previous launch first, by clicking on the red Terminate button in the Console view’s toolbar. When you reload the Swagger-UI page, you’ll find that your PUT method is now available, along with the others.