Programming with Java APIs, Part 2: API definitions

Use API definitions to generate a Spring Web MVC app with an Angular 2 frontend

1 2 Page 2
Page 2 of 2

Running the generate goal with mvn generate or as part of mvn package automatically creates the source code in the src/gen/java/main path, which you have defined in the sourceFolder element.

In a real project, of course, the inputSpec parameter will either be a URL hosted by your organization (where changes to the API are posted) or a file checked out with version control. In either case, API changes will be reflected by the generated code, ready for project integration.

Generating client-side code

With the backend stubbed out, you're ready to generate some client-side code. As I explained in Part 1, API definitions are the common point of contact between the frontend and backend in web applications. Because of this, we can use the the same API definition to output code for both the server and the client side.

Swagger's client-side language support

Similar to the backend, Swagger supports a variety of frontend technologies. In this case, let's say we want to generate a client for our Hello World application. Swagger offers several generators capable of outputting clients. You can view them on Swagger's GitHub page, or you could execute the following in your CLI: java -jar swagger-codegen-cli-2.2.2.jar langs.

An easy way to get a headstart on building a UI or other API consumer is to combine the /v2/api-docs endpoint we created at the beginning of this article with a client generator. Swagger supports technologies like Java, Node.js, and Angular.js. Right now, I want to show you an interesting option that will output code for several of these.

Assuming the server that we created earlier is running, this endpoint should be available on your local system: http://localhost:8080/v2/api-docs. Now, take the Swagger swagger-codegen JAR that you previously downloaded and point it at your local API spec. I've done this in Listing 9.

Listing 9. Generate client examples with the Swagger CLI


java -jar swagger-codegen-cli-2.2.2.jar generate \
  -i http://localhost:8080/v2/api-docs \
  -l html2 \
  -o client/html2

In Listing 9, we are again executing the swagger-codegen JAR with the generate command. This time, however, we are directing it via the -i switch to the local http://localhost:8080/v2/api-docs spec. Note that I've also set the plugin language to html2 with the -l flag. The -o variable specifies the output directory.

When you run this command, you will get an index.html file in your output directory. This is a static file that represents a client for the API at the time you run it. If you open this HTML file in your browser, you'll see something like Figure 3.

api driven dev fig3 Matthew Tyson

Figure 3. Client API examples in index.html

The interesting part of this particular plugin is that it actually outputs examples for each endpoint in several languages. Within each endpoint, you'll see a tab for Java, JavaScript, Python, and so on. You can use any of these as a starting point to code the application frontend.

An API-driven Angular 2 client

Next we'll develop a more involved client example based on Angular 2, using the same tools we've already explored.

Begin by cloning the Angular 2 WebPack boilerplate code. You can do this by typing git clone https://github.com/kozlice/angular2-webpack-boilerplate.git on your command line. Next, prepare the app by typing npm install in your newly created /angular2-webpack-boilerplate directory. Finally, test the app by entering the npm start command.

You now have a simple Angular 2 client running at http://localhost:9045/. If you visit that URL in your browser, you should see something like Figure 4.

api driven dev fig4 Matthew Tyson

Figure 4. Angular starter UI 

Using the Swagger Codegen snapshot

The PetStore app we used to generate our API includes a Spring backend, which we'll now use to build out some UI elements. For this you will need to install the codegen plugin for Angular 2.

The first step is to install the codegen repository to your local system, placing it anywhere that is convenient to remember. To install codegen, just enter the following command on your CLI: git clone https://github.com/swagger-api/swagger-codegen.git

Inside the newly created swagger-codegen directory are several Maven projects. We're interested in the CLI project, so change the directory to /swagger-codegen/modules/swagger-codegen-cli and build the project from source by typing mvn package. When Maven finishes its work, the target directory will contain the new swagger-codegen-cli JAR file, which we'll use to generate the Angular 2 code.

Return now to the root of the Angular 2 application, first shown in Listing 9. You'll be generating code into the src/app directory, as shown in Listing 10. Listing 10 is similar to Listing 9, where you created the HTML client, but in this case you are using the typescript-angular2 plugin and the snapshot JAR.

Listing 10. Using Codegen snapshot to generate Angular 2 code


java -jar >path-to-swagger-cli-project>/swagger-codegen/modules/swagger-codegen-cli/target/swagger-codegen-cli.jar generate \
  -i http://localhost:8080/v2/api-docs \
  -l typescript-angular2 \
  -o ./src/app

Angular 2's Codegen output

If you look in the src/app directory, you'll see two new folders: /api and /model. These contain the service implementation and model classes for the API.

If you look in the /model/Greeting.ts file, you'll see something like Listing 11.

Listing 11. Generated Angular 2 model for the Greeting schema


export interface Greeting {
    content?: string;

    id?: number;

}

Listing 11 shows a class modelling Greeting, which we originally created as a Spring Web MVC model class. So you've taken a backend model, generated an API schema for it, and generated a frontend model from that. Not bad! Note, too, that the class in Listing 11 has content and id fields, just as you would expect from a Spring app.

Similarly, in the /api directory, you'll find an expression of the path-mapping portion of the Greeting API in the GreetingcontrollerApi.ts file. For instance, Listing 12 has the getGreetingsUsingGET() method, which provides functionality for executing a GET against the Greeting endpoint.

Listing 12. getGreetingsUsingGET()


public getGreetingsUsingGET(extraHttpRequestParams?: any): Observable>Array>models.Greeting>> {
        return this.getGreetingsUsingGETWithHttpInfo(extraHttpRequestParams)
            .map((response: Response) => {
                if (response.status === 204) {
                    return undefined;
                } else {
                    return response.json() || {};
                }
            });
    }

Angular 2 uses observables, so getGreetingsUsingGET is in this idiom. We'll see how to use this in the Angular 2 in just a moment. First, we need to make two small changes that will get everything working together.

Connecting Angular 2 and Spring

At the head of the file, you'll find a variable assignment for basePath. For the sake of this example, change the variable to point to an HTTP localhost, as shown in Listing 13. (Of course, in a real-world app you would more likely modify the backend to support HTTPS.)

Listing 13. Updating basePath


protected basePath = 'http://localhost:8080';

Next, you'll modify the Spring app's GreetingController class. Modify the Greeting service method to allow cross-origin requests, as seen in Listing 14. This will allow the Spring app to handle requests from the Angular 2 app running on a different port.

Listing 14. Allowing cross-origin requests in Spring Web MVC


@CrossOrigin(origins = "http://localhost:9045")
public Greeting greeting(@RequestParam(value="name", defaultValue="World") String name) {
    return new Greeting(counter.incrementAndGet(),
                        String.format(template, name));
}

Using the generated Angular 2 code

Next you'll make two more changes to the Angular application. First, update the article/article-list.html file as seen in Listing 15.

Listing 15. article-list.html Modifications


>div *ngIf="isLoading">
    Loading…
>/div>

{{ greeting }}

>article *ngFor="let article of articles" class="article-list-item">
    >h4>>a [routerLink]="['/articles', article.id]">{{ article.title }}>/a>>/h4>
>/article>

Here you are adding an Angular template variable with {{ greeting }}, which is where the UI will output the greeting from the Spring backend.

Note that article-list.html file is an Angular template. It provides markup for the article-list component, which is defined in the /article/article-list.component.ts file. In Listing 16, I've updated that file with changes that are vital to making use of the generated Angular code. Apply these changed to article-list.component.ts, we'll take a look at this file in detail next.

Listing 16. Updates to article-list.component.ts


import { Component, ViewEncapsulation, OnInit, OnDestroy } from '@angular/core';

import { ArticleService } from './article.service';
import { Article } from './article.model';
import { Response } from '@angular/http';
import { GreetingcontrollerApi } from '../api/api'; // 1

/**
 * A simple component, which fetches articles list from HTTP API and displays them.
 */
@Component({
    selector: 'da-article-list',
    templateUrl: './article-list.html',
    styleUrls: ['./article-list.scss'],
    encapsulation: ViewEncapsulation.Emulated,
    providers: [ArticleService, GreetingcontrollerApi] // 2
})
export class ArticleListComponent implements OnInit, OnDestroy {
    private articles: Article[];
    private error: Response;
    private isLoading: boolean = true;
    greeting: string = ""; // 3

    constructor(private articleService: ArticleService, private greetingService: GreetingcontrollerApi) {} // 4

    ngOnInit(): void {
        this.articleService.getAll().subscribe(
            (data)  => this.articles = data,
            (error) => this.error = error,
            ()      => this.isLoading = false
        );
        param = { "name": "test" };
        this.greetingService.greetingUsingGET(param).subscribe( // 5
          (data) => this.greeting = data.content,
          (error) => this.error = error,
          () => {}
        );
    }

    ngOnDestroy(): void {}
}

Notes about the code

Listing 16 is commented with numbers, which I'll use to explain how ArticleListComponent works.

  1. Import the GreetingcontrollerApi class from the /api/api module.
  2. Add the GreetingcontrollerApi as a "provider." This allows the service to be injected into the class, in a fashion similar to Spring auto-wiring.
  3. Add the greeting member variable to the component. greeting is the variable the {{ greeting }} template variable will resolve to.
  4. Add the private greetingService: GreetingcontrollerApi argument to the constructor. This is where the controller is actually injected into the class. It will be available as a private member variable.
  5. Add the this.greetingService.greetingUsingGET(param) call. This is where you make use of the generated service class. The call returns an observable, which you will subscribe to in order to obtain the result.

Testing the app

Now when you run the Angular and Spring applications together, combining your backend and frontend code, you should see the UI in Figure 5.

api driven dev fig5 Matthew Tyson

Figure 5. The Angular UI with the Spring API greeting

Conclusion

Programming with APIs is a valuable enhancement to the software development lifecycle, in part because using APIs helps formalize our thinking about systems. As you've seen in this two-part article, capturing that thought in Java API definitions can be done at every stage of development.

In Part 1 we used OpenAPI to help define a BikeParts app, and you learned how using APIs formalizes requirements gathering. In Part 2, we've used automated to tools to generate application code from Java API definitions. With the PetStore app you saw how to generate frontend and backend code from an API. You also saw how to use application code to create an API.

APIs have been part of software development for decades, but they've moved to center stage in software development only recently. As the tooling around Java APIs continues to mature and improve, we will have even more options for putting APIs at the heart of Java-based software development. It will be interesting to see how API-driven development evolves over the next few years.

1 2 Page 2
Page 2 of 2