APIファーストな開発を行う
JHipsterアプリケーションを生成する際、「他に使用したい技術はありますか?」と尋ねられたら、API first development using OpenAPI-generator
オプションを選択して、OpenAPI-generatorを活用します。以下のように設定します:
# ... 他のプロンプトへの回答 ...
? Which other technologies would you like to use? (Press <space> to select, <a> to toggle all, <i> to invert selection,
and <enter> to proceed)
◯ Apache Kafka as asynchronous messages broker
◯ Apache Pulsar as asynchronous messages broker
❯◉ API first development using OpenAPI-generator
このオプションを選択すると、ビルドツール(MavenまたはGradle)がOpenAPI(Swagger)仕様書(OAS)ファイルからAPIコードを生成するように構成されます。Swagger v2およびOpenAPI v3の両方の形式がサポートされています。
APIファースト開発の根拠
APIファースト 開発、または「契約優先開発」では、最初にAPI仕様書を作成し、そのAPI仕様からコードを生成します。これは、コードからドキュメントを生成する方法とは対照的です。
APIファースト開発には次の利点があります:
- APIは実装の結果ではなく、利用者向けに設計されます。
- 仕様ファイルを使用して新しいサーバーエンドポイントをモックでき、フロントエンドとバックエンドの分離が進みます。
- 仕様ファイルの使用にはライブサーバーが不要です。
デフォルト構成
このHOWTOでは、JHipsterのデフォルト構成を使用し、Springのデリゲートパターンを利用します。Spring用のOpenAPI Generatorによって生成されたコードは、実装用のモデルとデリゲートを生成します。
プラグインのデフォルト構成は以下の通り(Maven)で、pom.xml
のプロジェクトレベルのbuild
プロファイルに定義されています。この構成はJHipsterによってGradleプロジェクトにも適用されます。
<plugin>
<!--
openapi-generator-cliを使用して、OpenAPI定義ファイルから
Spring-MVCエンドポイントのスタブをコンパイル時に生成するプラグイン
-->
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>${openapi-generator-maven-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.basedir}/src/main/resources/swagger/api.yml</inputSpec>
<generatorName>spring</generatorName>
<apiPackage>demo.jhipster.myapp.web.api</apiPackage>
<modelPackage>demo.jhipster.myapp.service.api.dto</modelPackage>
<supportingFilesToGenerate>ApiUtil.java</supportingFilesToGenerate>
<skipValidateSpec>false</skipValidateSpec>
<configOptions>
<!-- delegatePatternはspringジェネレータにのみ使用可能 -->
<delegatePattern>true</delegatePattern>
<title>jhipster</title>
<useSpringBoot3>true</useSpringBoot3>
</configOptions>
</configuration>
</execution>
</executions>
</plugin>
ℹ️ JHipsterユーザーは、ビルドツールを環境の特性や組織で採用されている標準に合わせて調整することが推奨されます。JHipsterはスケールに対応できるツールとプラクティスを備え、プロジェクトに適した基盤を提供します。JHipsterで使用するSpringのデリゲートパターンが組織の標準でない場合は、
configOption:delegatePattern
を削除できます(デフォルトはfalse
です)。JHipsterのデフォルト動作に変更を加える際の影響を理解することが求められます。JHipsterはSpringの使用方法を教えることはできないため、Springに対する変更がJHipsterで文書化できない動作を引き起こす可能性があります。
openapi-generator-maven-pluginの使用
OpenAPI仕様書(OAS)ファイルは、プラグインのinputSpec
要素で定義されたファイルからジェネレータによって読み込まれます。JHipsterのOASのデフォルト場所はsrc/main/resources/swagger/api.yml
です。プラグインはOASからインターフェースとデータ転送オブジェクト(DTO)を生成します。インターフェースには、HTTPステータス501 未実装
と空の本文で応答するデフォルトメソッドが含まれます。
OpenAPI仕様書を用いたAPIの開発
このガイドでは、簡潔さのためにOpenAPIのクラシックなペットストア仕様を使用します。OASの記述はガイドの範囲外ですが、API開発者はswagger-editorなどのツールを使用してOASを記述し、src/main/resources/swagger/api.yml
に配置できます。また、多くのIDEがOAS編集用ツールを提供しています。
プロ・ヒント
JHipsterでAPI first development using OpenAPI-generator
を選択した場合、Swagger EditorのDocker Compose記述ファイルがsrc/main/docker/swagger-editor.yml
に提供されます。このエディタをローカルで使用するには、docker compose -f src/main/docker/swagger-editor.yml up -d
を実行してください。
JHipsterのレイヤードアーキテクチャとArchUnitによる技術構造テスト
OpenAPI Generatorプラグインによって生成されたデリゲートを実装する前に、JHipsterのレイヤードアーキテクチャ設計を確認し理解することをお勧めします。このアーキテクチャはTNG TechのArchUnitによってテストされ、src/test/java/com/mycompany/myapp/TechnicalStructureTest.java
で定義されています。デリゲートクラスの適切な実装により、このレイヤードアーキテクチャを逸脱しないようにします。
実装ガイド
Pet Store v3
ここからは、OpenAPI InitiativeによるJHipster対応版の拡張Pet Store v3に基づいて説明を行います(こちらにあります)。このJHipsterバージョンでは、server:url
がJHipsterのデフォルトであるhttp://localhost:8081/api
に設定されています(ポートはデフォルトで8081
、生成されたコントローラは/api
を基準パスとし、デフォルトのSecurityConfiguration#filterChain(HttpSecurity, MvcRequestMatcher.Builder)
実装に一致します)。
サーバーソースの生成
./mvnw generate-sources
またはGradleの場合:
./gradlew openApiGenerate
ツールの検証
target/generated-sources
内の生成されたクラスが実装用にクラスパスに含まれていることを確認します。ほとんどのIDEはこれらのディレクトリを自動的に検出し設定します。問題がある場合はIDEのドキュメントを参照してください。
生成されたクラスの確認
generated-sources
を確認します 。
target/generated-sources
└── openapi
└── src
└── main
└── java
└── demo
└── jhipster
├── service
│ └── api
│ └── dto
│ ├── Error.java
│ ├── NewPet.java
│ └── Pet.java
└── web
└── api
├── ApiUtil.java
├── PetsApi.java
├── PetsApiController.java
└── PetsApiDelegate.java
⚠️OpenAPI GeneratorはDTO(「モデル」)およびデリゲートの生成を担当します。実装は
target/generated-sources
に配置しないでください。これらのクラスはいつでも再生成される可能性があるため、ソース管理には追加しないでください。
実装
デリゲートの実装ごとに以下の ステップを繰り返します:
デリゲートの実装
実装クラスではSpringの@Service
アノテーションを使用します。
APIパッケージの作成
web
レイヤーにapi
パッケージを作成します。例:mkdir -p src/main/java/demo/jhipster/web/api
。プロ・ヒントを参照してください。
PetApiのメソッドをオーバーライドする
OpenAPI Generatorは、OASの各paths
要素に対してoperationId
を使用してdemo.jhipster.web.api.PetApi
インターフェースに@RequestMapping
メソッドを作成します。OASの記述はガイドの範囲外ですが、demo.jhipster.web.api.PetApi
内の各@RequestMapping
アノテーションと生成されたメソッ ドを確認してください。注目すべき例としては、operationId: find by pet id
と、その結果としてのインターフェースメソッドfindPetById
があり、以下のようにオーバーライドされます:
package demo.jhipster.web.api;
import demo.jhipster.service.api.dto.Pet;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class PetsApiDelegateImpl implements PetsApiDelegate {
private final List<Pet> pets = new ArrayList<>();
@Override
public ResponseEntity<Pet> findPetById(Long id) {
Pet pet = getPets().stream().filter(p -> id.equals(p.getId())).findAny().orElse(null);
if (pet != null) {
return ResponseEntity.ok(pet);
}
return new ResponseEntity<>(null, HttpStatus.NOT_FOUND);
}
private List<Pet> getPets() {
Pet pet0 = new Pet();
pet0.setId(1L);
pet0.setName("Chessie Cat");
pet0.setTag("cat");
pets.add(pet0);
return pets;
}
}
プロ・ヒント
OpenAPI Generatorが期待する型やコードを生成していない場合、ドキュメントを確認してください。JHipsterのデフォルト設定は多くのAPIに適合するように一般的に調整されています。より柔軟な設定が必要な場合は、提供されているプラグインのimportMappings
およびtypeMappings
オプションを参照してください。
実装のテスト駆動
ターミナル(または「コマンドプロンプト」)で以下を実行します:
./mvnw # サーバーの起動、または 'npm run app:start'も試してください。package.jsonに他のスクリプトもあります!
curl -H "Accept: application/json" http://localhost:8081/api/pets/1
{
"name" : "Chessie Cat",
"tag" : "cat",
"id" : 1
}
開発中のエンドポイントモック
NativeWebRequest
をデリゲートインターフェースに提供することで、オーバーライドされていないメソッドに対して例の応答本文を返すことができます。エンドポイントは引き続きHTTPステータス501 Not Implemented
で応答しますが、実装前にエンドポイントをモックする際に便利な場合があります。
ℹ️ OASサンプルには、ドキュメントとして例の本文が提供されています。各操作の
schemas
にはOpenAPI Generatorが応答本文を生成するためにexample
を定義する必要があります。以下のスニペットが役立ちます:
# ...
# 省略
components:
schemas:
Pet:
allOf:
- $ref: '#/components/schemas/NewPet'
# 省略
example:
name: Chessie Cat
id: 1
tag: cat
デリゲート実装のデフォルトコンストラクタをオーバーライド
OASが適切に定義されている場合、以下のようにNativeWebRequest
を実装に追加します。例の実装では以下の手順を示します:
- 実装に
NativeWebRequest request
プロパティを追加します。 - 引数なしのデフォルトコンストラクタ
public PetsApiDelegateImpl(NativeWebRequest request){...}
をオーバーライドします。 demo.jhipster.web.api.PetsApiDelegate#getRequest()
をオーバーライドし、Optional<NativeWebRequest>
を返します。
package demo.jhipster.web.api;
import demo.jhipster.service.api.dto.Pet;
// その他のインポートは省略
@Service
public class PetsApiDelegateImpl implements PetsApiDelegate {
private final NativeWebRequest request;
private final List<Pet> pets = new ArrayList<>();
public PetsApiDelegateImpl(NativeWebRequest request) {
this.request = request;
}
/**
* NativeWebRequestを実装クラスに提供します。
*/
@Override
public Optional<NativeWebRequest> getRequest() {
return Optional.ofNullable(request);
}
@Override
public ResponseEntity<Pet> findPetById(Long id) {
// 上記で既に示したもの
}
/**
* PetsApiの/petsのRequestMappingで使用されるメソッドを実装します。
* <p>
* OpenAPI仕様書で定義された例の応答本文を使用し、HTTPステータス501 Not Implementedで応答します。
* OpenAPI Generator for Springによって生成されたOpenAPI仕様ファイルです。
*/
@Override
public ResponseEntity<List<Pet>> findPets(List<String> tags, Integer limit) {
return PetsApiDelegate.super.findPets(tags, limit);
}
private List<Pet> getPets() {
// 上記で既に示したもの
}
}
サーバーを再起動し、ターミナル(または「コマンドプロンプト」)でcurl
や他のHTTPツール(例:Insomnia、Postman、httpie)を使用して以下を確認します:
$ curl -H "Accept: application/json" http://localhost:8081/api/pets/1
{
"name" : "Chessie Cat",
"tag" : "cat",
"id" : 1
}
$ curl -H "Accept: application/json" http://localhost:8081/api/pets?tags=cat
* Trying [::1]:8081...
* Connected to localhost (::1) port 8081
> GET /api/pets?tags=cat HTTP/1.1
> Host: localhost:8081
> User-Agent: curl/8.4.0
> Accept: application/json
>
< HTTP/1.1 501 Not Implemented
# 一部ヘッダを省略
< Content-Type: application/json; charset=UTF-8
< Content-Length: 108
<
{ [108 bytes data]
* Connection #0 to host localhost left intact
[ { "name" : "Chessie Cat", "id" : 1, "tag" : "cat" }, { "name" : "Chessie Cat", "id" : 1, "tag" : "cat" } ]
例の本文が返され、応答のHTTPステータスは501 Not Implemented
です。
認証
JHipsterはデフォルトで、demo.jhipster.config.SecurityConfiguration#filterChain(HttpSecurity, MvcRequestMatcher.Builder)
内の/api/**
を認証が必要に設定します。例:.requestMatchers(mvc.pattern("/api/**")).authenticated()
。
認証の詳細はこのガイドの範囲外です。ただし、demo.jhipster.security.jwt.TokenAuthenticationIT
統合テストの確認により、Insomnia、Postman、curl、および他のHTTPツールで認証をテストする際の参考になるかもしれません。例えば、以下のようにDockerを使用してJWTベアラートークンを生成することが可能です:
docker run --rm --name jwt-cli bitnami/jwt-cli encode \
-S b64:<JHIPSTER_JWT_SECRET> \
-P 'auth=["ROLE_ADMIN"]' \
-e=$(date -v+60S +%s) \ # man date; produce an epoch system time + 60 seconds, macOS date command shown
-s anonymous \
-A HS512 \
--no-typ
これにより、以下のようにAPIコール用のBearerトークンが生成されます:
curl -v -H "Accept: application/json" -H "Authorization: Bearer <ENCODED_TOKEN>" http://localhost:8081/api/pets/1