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:
-
Design your API in OpenApi.
-
Generate stub code in Java using the Spring Boot framework.
-
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.
The demo proceeds in four phases (plus some initial set-up):
The phases are:
-
Setup - install and launch RepreZen API Studio.
-
Design - Create one of the standard OpenAPI v3 example models available in RepreZen API Studio.
-
Generate - Use one of the available code generators to generate a new server for the API, using Spring Boot.
-
Implement - Write your business logic into the generated stubs.
-
Repeat (Design ⇒ Generate ⇒ Implement) in order to add a new method to the API
So let’s get going!
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.
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:
-
Click on the drop-down arrow of the New tool in the toolbar, just under the File menu.
-
Select RepreZen Examples to open the Examples Wizard.
-
Click on the OpenAPI v3 tab.
-
Select the Expanded Pet Store (v3) example, and press Finish.
-
You should see a new project in your workspace, and the example model file itself will automatically open in an editor.
-
Browse through the model briefly to familiarize yourself with its operations and other components.
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:
-
In your model project, locate the
petstore-expanded.yaml
file in themodels
folder, and double-click on it. -
Click on the Create a New GenTarget button in the toolbar, just to the left of the Generate button/menu.
-
Type "spring" in the resulting dialog’s search box, and you should see the Java Spring (Boot…) GenTemplate in the list.
-
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.
-
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. -
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). -
Click on the Dependencies tab at the bottom of the editor, then click on the Add… button.
-
Fill out the dialog as shown, then click OK. Values (for copy/paste) are:
-
Save and close the
pom.xml
editor. -
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.) -
The prior step caused a new folder named
implementation
to appear in our model project. Normally, generated files are placed in a folder namedgenerated
in the GenTarget folder, but we changed that by editing therelativeOutputDir
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 Mavenpom.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.
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:
-
Modify the
pom.xml
file so that the project is built using Java 8. This is needed because we set thejava8
parameter tofalse
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 newpetstore-demo
project, and alter itsjava.version
property value to1.8
, then save the file. -
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.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. -
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.
-
Create our implementation class. Start by right-clicking on the
com.reprezen.demo.springboot.api
package in thesrc/main/java
folder, and select New → Class. Name the classPetsApiDelegateImpl
. -
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! :-)
-
Add missing imports. Right-click in the editor and select Source → Organize Imports.
-
Choose
com.google.common.collect.Maps
to resolve theMaps
type. -
Choose
java.util.List
to resolve theList
type. -
All other types should be resolved automatically.
Save the file once you’re finished fixing the imports.
-
-
Launch the service. Right-click on the
petstore-demo
project, and select Run As → Maven build…. In the dialog that appears, typespring-boot:run
in the Goals field, and click Run. -
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.
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.
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'
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).
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.