Spring Boot と Angular2 をタイプセーフに繋ぐ
フロントエンドの開発は、TypeScript や flow によりタイプセーフに行えるようになってきています。
そうなるとバックエンドとフロントエンドの通信もタイプセーフにしたくなってくるはずです。
Swagger を使えばそれが実現できそうです。
Swagger により Angular2 のクライアントのコードを自動生成できるのです。
作成してみた Example Code を github に置きました。
バックエンド
バックエンドには、Spring Boot を使いました。
Swagger を使うための Springfox というものがありまして、Spring MVC は、Swagger との相性がとても良いです。
Java
まずはバックエンドのコードです。
/backend/src/main/java/app/Application.java
package app; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; import static springfox.documentation.builders.PathSelectors.regex; @SpringBootApplication @EnableSwagger2 // (1) @RestController public class Application { @GetMapping("/rest/add") public Response add(@RequestParam Integer arg1, @RequestParam Integer arg2) { return new Response(arg1 + arg2); } @Bean public Docket documentation() { return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.any()) .paths(regex("^/rest/.*$")) // (2) .build(); } public static class Response { public final Integer result; public Response(final Integer result) { this.result = result; } } public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
リクエストパラメータで受け取った二つの整数を足してレスポンスするという単純なコードです。
Spring Boot 側は次のコマンドで起動します。フロントエンドのビルドも走るのでちょっと時間がかかります。
$ cd backend $ ./gradlew bootRun
リクエスト・レスポンスは、次のような感じになります。
$ curl -s 'http://localhost:8080/rest/add?arg1=1&arg2=2' {"result":3}
(1)で EnableSwagger2 アノテーションを付けることにより、バックエンドの仕様を公開するようになります。
(2)で 対象となる URLパターンを限定しています。
次のような感じで JSON で仕様が確認できるようになります。
$ curl -s 'http://localhost:8080/v2/api-docs' {"swagger":"2.0","info":{"description":"Api Documentation","version":"1.0","title":"Api Documentation","termsOfService":"urn:tos","contact":{},"license":{"name":"Apache 2.0","url":"http://www.apache.org/licenses/LICENSE-2.0"}},"host":"localhost","basePath":"/","tags":[{"name":"application","description":"Application"}],"paths":{"/rest/add":{"get":{"tags":["application"],"summary":"add","operationId":"addUsingGET","consumes":["application/json"],"produces":["*/*"],"parameters":[{"name":"arg1","in":"query","description":"arg1","required":true,"type":"integer","format":"int32"},{"name":"arg2","in":"query","description":"arg2","required":true,"type":"integer","format":"int32"}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/Response"}},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"}}}}},"definitions":{"Response":{"type":"object","properties":{"result":{"type":"integer","format":"int32"}}}}}
build script
ビルドには Gradle を使っています。
/backend/build.gradle
buildscript { ext { springBootVersion = '1.4.3.RELEASE' } repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") } } apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'org.springframework.boot' jar { baseName = 'app' version = '0.0.1-SNAPSHOT' } sourceCompatibility = 1.8 targetCompatibility = 1.8 repositories { mavenCentral() } dependencies { compile('org.springframework.boot:spring-boot-starter-web') compile 'io.springfox:springfox-swagger2:2.6.1' testCompile('org.springframework.boot:spring-boot-starter-test') testCompile 'io.springfox:springfox-staticdocs:2.6.1' } def SWAGGER_JSON_FILE = 'publications/swagger.json'; def SWAGGER_CLIENT_DIR = '../frontend/src/swagger'; task generateSwaggerSpec(type: Test, dependsOn: testClasses) { inputs.files fileTree('src/main/java') outputs.file file(SWAGGER_JSON_FILE) filter { includeTestsMatching "app.SwaggerSpecGenerator" } } configurations { swaggercodegen } dependencies { swaggercodegen 'io.swagger:swagger-codegen-cli:2.2.1' } task cleanSwaggerCodegen(type: Delete) { delete fileTree(SWAGGER_CLIENT_DIR).include('*/*') } task swaggerCodegen(type: JavaExec) { inputs.file file(SWAGGER_JSON_FILE) outputs.dir file(SWAGGER_CLIENT_DIR) classpath = configurations.swaggercodegen main = 'io.swagger.codegen.SwaggerCodegen' args('generate', '-l', 'typescript-angular2', '-i', SWAGGER_JSON_FILE, '-o', SWAGGER_CLIENT_DIR) } task compileFrontend(type:Exec) { inputs.files fileTree('../frontend/src') outputs.dir file('build/resources/main/static') workingDir '../frontend' ant.condition(property: "isWindows", value: true) { os(family: "windows") } commandLine(ant.properties.isWindows ? ['cmd', '/c', 'ng', 'build', '--aot', '--target=production'] : ['ng', 'build', '--aot', '--target=production']) } swaggerCodegen.dependsOn generateSwaggerSpec compileFrontend.dependsOn swaggerCodegen jar.dependsOn compileFrontend bootRun.dependsOn compileFrontend
追加した処理は、以下です。
- mock mvc を使ったテストで バックエンドの仕様を表す
swagger.json
の生成 swagger.json
から Angular2 のクライアントコードの生成- gradle から ng コマンドを叩いて フロントエンドのビルドを開始する
Angular2 用のクライアントコードを生成するコマンドは、次になります。
$ ./gradlew swaggerCodegen
次の位置にファイルが生成されます。
/frontend/src/swagger/.
フロントエンド
フロントエンドは、Angular2 を使います。
TypeScript
/frontend/src/app/app.component.ts
import { Component, Input } from '@angular/core'; import {ApplicationApi} from '../swagger/api/ApplicationApi' import {Http} from '@angular/http'; import { environment } from '../environments/environment'; @Component({ selector: 'app-root', template: ` <h1> <input type="number" [(ngModel)]="arg1" /> + <input type="number" [(ngModel)]="arg2" /> <button (click)="add()">=</button> {{result}} </h1> `, styleUrls: ['./app.component.css'] }) export class AppComponent { arg1: number; arg2: number; result: number; constructor(private http: Http) { } add() { if (this.arg1 || this.arg2) { new ApplicationApi(this.http, window.location.origin) // (1) .addUsingGET(this.arg1, this.arg2) .subscribe(data=>this.result = data.result); } } }
Swagger により生成された ApplicationApi
を利用しています。
通信部分のコードで補完がきくし、エラーチェックしてくれます。
そうそう、俺が実現したかったのこれ!
(1) で basePath として window.location.origion
を指定してるのが微妙(省略したらそれになって欲しい)。
とても単純な例なので複雑なパターンに対応できるのか分かっていません。。
まー、Swagger の場合、コード生成する部分を簡単に置き換えられるようにはなっているので少し安心ですが。
フロントエンド開発用のサーバ起動は次になります。
$ cd frontend $ npm start
画面は以下。
本題に関しましては、以上です。
以下、おまけ。
おまけ
プロジェクトの雛形作成
プロジェクトのルートに backend, frontend というディレクトリを作って完全にバックエンドのファイルとフロントエンドのファイルを分けて管理するようにしてみました。
Spring Boot 側の雛形は、 Spring Initializr で、Angular側は、Angular-CLI で作成しました。
バックエンド と フロントエンドで使う IDE を分ける
今回、バックエンドは eclipse(STS)、フロントエンドは Visual Studio Code でコーディングしました。
本当は、 eclipse だけで済ますことができれば良いのですが、フロントエンド開発に eclipse は弱そうです。
ルートの backend ディレクトリは、 eclipse の プロジェクトとして読み込み、 frontend ディレクトリは VS Code のプロジェクトとして読み込みます。
フロンエンド開発用サーバへのリクエストをバックエンドのサーバに proxy する
Angular-CLI で作成したプロジェクトの場合、proxy.conf.json
に proxy の情報を書きます。
/frontend/proxy.conf.json
{ "/rest": { "target": "http://localhost:8080", "secure": false } }
そして、 package.json の scripts.start 部分も書き換えます。
{ // 省略 "scripts": { "start": "node_modules/.bin/ng serve --proxy-config proxy.conf.json" }, // 省略 }
backend の .gitignore に .bin
を追加する
Spring Initializr で作成した .gitignore に .bin
がない。
追加しないと eclipse でコンパイルしたファイルが commit される。
frontend のビルドでファイルの出力先を変更する
frontend でビルドしたファイルを backend 側に出力し、 jar の中に含めるようにしています。
その設定が、package.json の scripts.build の -op
です。
{ // 省略 "scripts": { "build": "node_modules/.bin/ng build --aot -t production -op ../backend/build/resources/main/static" }, // 省略
Angular2 は、実案件でそろそろ使って良い?
個人的には Angular-CLI や Visual Studio Code Exetnsion の正式リリースを待ってから実案件で使うのが良いかと思っているところです。
Angular2 で HTML テンプレートは、どこに書く?
別HTMLに書くか、typescript の Decorator に書くか選択できます。
コンパイル速度や IDE のコード解析を考えると、Decorator に書いた方が良いかもしれません。
Angular2 と React どっちが良い ?
今のところ、 実績のある React かなーと。
React も swagger で fetch API のクライアントを生成すればタイプセーフな通信もできそうですし、JSX, flow, typescript によるタイプセーフなテンプレートも良い感じですし。
Angular2 はデフォルトのコンパイルだとテンプレート部分は、単なる文字列であって、AOTやらでどこまで静的解析してくれるのかよく分かっていません。
React は、ライブラリの組み合わせに悩みそうな感じがあり、フルスタックフレームワーク的な Angular の方はその点悩むこと少なそうと思います。
flow より TypeScript を推したい自分としては、TypeScript 製の Angular2 に期待しているところです。
以上
Angular2によるモダンWeb開発 [ 末次 章 ] | はじめてのSpring Boot改訂版 [ 槇俊明 ] |