Swagger-CodegenでLiferay Headless REST APIを作ってみた(2) - Swagger-CodegenでLiferay Headless REST APIを作ってみた(2) - aegif Labo Blog Liferay
null Swagger-CodegenでLiferay Headless REST APIを作ってみた(2)
こんにちは。
前回はLiferayのHeadless APIのソースコードの構造を考察しました。今回はその構造を踏まえて、OpenAPIのオフィシャルツールが作成したコードをLiferayに取り込んでみます。
Swagger-Codegen
ひとまず、OpenAPIのオフィシャルコード作成ツールSwagger-Codegenをインストールしましょう。Macの場合、homebrewを利用すれば brew install swagger-codegen
で手軽に完成できます。インストール後、swagger-codegen -h
を実行し、以下の出力ができればOKです。
$ swagger-codegen -h
commands:
Command additional help
generate generate
config-help config-help
meta meta
langs langs
version version
目標API仕様
今回はOpenAPIの仕様を試したいので、以下のポリモーフィズムAPIを目標として置きましょう。
- パスとメソッド:
GET /vehicle?id=xxx
- ポリモーフィズムレスポンス
- type=sedanの場合、定員数を返却する
{
id:
type: sedan
max_passengers: 5
}
-
- type=truckの場合、最大積載量を返却する
{
id:
type: truck
max_load: 1500
}
-
- type=sportsの場合、最大スピードを返却する
{
id:
type: sports
max_speed: 200
}
API記述ファイル
OpenAPIのポリモーフィズムレスポンスを利用し、上記仕様のSchema定義は以下になります。
components:
schemas:
Vehicle:
type: object
discriminator:
propertyName: type
mapping:
Sedan: '#/components/schemas/Sedan'
Truck: '#/components/schemas/Truck'
Sports: '#/components/schemas/Sports'
properties:
name:
type: string
type:
type: string
enum:
- Sedan
- Truck
- Sports
Sedan:
description: 'a sedan'
allOf:
- $ref: '#/components/schemas/Vehicle'
- type: object
properties:
max_passengers:
type: integer
Truck:
description: 'a truck'
allOf:
- $ref: '#/components/schemas/Vehicle'
- type: object
properties:
max_load:
type: integer
Sports:
description: 'a sports car'
allOf:
- $ref: '#/components/schemas/Vehicle'
- type: object
properties:
max_speed:
type: integer
フルyaml設定ファイルはappendix Aを参照してください。
Swagger-Codegenでのソースコード作成
Swagger-Codegenの基本的な使い方は以下になります:
$swagger-codegen generate -i <input_file> -l <language> -o <output>
-l <language>
でいくつの言語を選択できますが、前回の内容を踏まえて、LiferayのHeadless APIはJAX-RSベースであり、OSGIのJAX-RSホワイトボードはCXFベースで実装されているので、今回はjaxrs-cxfを指定します(Swagger-Codegenがサポートする言語リストはAppendix Bを参考してください)。
$swagger-codegen generate -i vehicle.yaml -l jaxrs-cxf -o vehicle-src
そしで、vehicle-srcの下に作成されたソースコードを確認してみましょう。
- Schema(モデル)定義(/modelの下)
- Vehicle:ベースクラス
- Sedan, SportsとTruck:それぞれはVehicleを継承するサブクラスになる
public class Vehicle {
@JsonProperty("name")
public String getName() {
return name;
}
@JsonProperty("type")
public String getType() {
return type.getValue();
}
...
}
@Schema(description="a sedan")
public class Sedan extends Vehicle {
...
@JsonProperty("max_passengers")
public Integer getMaxPassengers() {
return maxPassengers;
}
...
}
@Schema(description="a sports car")
public class Sports extends Vehicle {
...
@JsonProperty("max_speed")
public Integer getMaxSpeed() {
return maxSpeed;
}
...
}
@Schema(description="a truck")
public class Truck extends Vehicle {
...
@JsonProperty("max_load")
public Integer getMaxLoad() {
return maxLoad;
}
...
}
- ベースAPIインタフェース(/apiの下)
- /vehicleに、パラメータ=id、戻り値=VehicleクラスのJAX-RS APIが作成される
@Path("/")
public interface DefaultApi {
@GET
@Path("/vehicle")
@Produces({ "application/json" })
@Operation(summary = "Get a vehicle", tags={ })
@ApiResponses(value = {
@ApiResponse(
responseCode = "200",
description = "Vehicle Request",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = Vehicle.class))) })
public Vehicle vehicleGet(@QueryParam("id") Integer id);
}
Liferayへ移行
それでは、以上のコードをLiferayに移行しましょう。前回の考察により、以下のコンポーネントが必要になります。(*) JAVAパッケージ名はvehicle.headless.rest.*と指定しています。
- Liferay.Vulcanに登録するJAX-RS Applicationクラス
- アプリ名をvehicle.appと指定する
osgi.jaxrs.extension.select
に、当該アプリをLiferay.Vulcanに紐付ける- ホワイトボードのbase-pathにLiferayのホワイトボードベースである/oに対するパスを指定する(今回はvehicle-appに設定する)
- 以上の設定により、当該APIのベースパスは {hostname}:{port}/o/vehicle-app になる
@Component(
property = {
"liferay.jackson=false",
"osgi.jaxrs.application.base=/vehicle-app",
"osgi.jaxrs.extension.select=(osgi.jaxrs.name=Liferay.Vulcan)",
"osgi.jaxrs.name=vehicle.app"
},
service = Application.class
)
@Generated("")
public class VehicleApplication extends Application {
}
- APIインタフェースの変更
- APIインタフェース名をDefaultApiからVehicleHeadlessRestに変更する
- buildRESTに合わせてAPIパスをv1.0に指定する
@Path("/v1.0")
public interface VehicleHeadlessRest {
...
public Vehicle vehicleGet(@QueryParam("id") Integer id);
}
- APIインタフェースの実装
- osgiに介して上記Applicationに紐付ける
- サンプルのため、ダミーエンティティを導入する
@Component(
property = {
"api.version=v1.0",
"osgi.jaxrs.application.select=(osgi.jaxrs.name=vehicle.app)",
"osgi.jaxrs.resource=true"
},
scope = ServiceScope.PROTOTYPE,
service = VehicleHeadlessRest.class
)
public class VehicleHeadlessRestImpl implements VehicleHeadlessRest {
@Override
public Vehicle vehicleGet(Integer id) {
return vehicles.get(id);
}
private static final Map<Integer, Vehicle> vehicles = new HashMap<>(); static {
vehicles.put(1, (new Sedan()).maxPassengers(5).type(TypeEnum.SEDAN).name("a sedan"));
vehicles.put(2, (new Truck()).maxLoad(10).type(TypeEnum.TRUCK).name("a truck"));
vehicles.put(3, (new Sports()).maxSpeed(250).type(TypeEnum.SPORTS).name("a sports car"));
}
}
以上のソースコードをビルドしLiferayにデプロイすると、Headless REST api {hostname}:{port}/o/vehicle-app/v1.0/vehicleが作成できます。早速、試してみましょう。
現時点ではまだLiferay API Explorer UIを利用できないため、まず外部からアクセスするための認証を解除します:
- 公式手順に従って、Control Panel → Security → Service Access Policy に新規エンティティを作成する
- サービスクラス:API実装クラス名 vehicle.headless.rest.impl.VehicleHeadlessRestImpl(パッケージ名は適宜変更)
- メソッド名:実装クラスのメソッド vehicleGet
curlを利用し、上記APIパスにアクセスすると、以下のようにHeadless APIが正しく作成されることを確認できます:
$ curl -X 'GET' 'http://localhost:8080/o/vehicle-app/v1.0/vehicle?id=1' -H 'accept: application/json'
{
"max_passengers" : 5,
"name" : "a sedan",
"type" : "Sedan"
}
$ curl -X 'GET' 'http://localhost:8080/o/vehicle-app/v1.0/vehicle?id=3' -H 'accept: application/json'
{
"max_speed" : 250,
"name" : "a sports car",
"type" : "Sports"
}
Liferay API Explorer UIの登録
ここまでの実装で、LiferayのOSGIホワイトボードにREST APIを登録しましたが、作成されたvehicle-appがLiferayのAPI Explorer UIでは表示されていません。前回考察した通り、API Explorerで表示する場合、/vehicle-app/v1.0/openapi.jsonにAPIを作成し、VehicleHeadlessRestインタフェースの実装クラスをLiferay API Explorer UIへ提供する必要があります。そのため:
- OpenAPIResourceImplクラスを実装するJAX-RSサービスを作成する
- /openapi.jsonまたは.yamlパスにGETメソッドを実装し、当該メソッドはVehicleHeadlessRestImplクラスを返却する
osgi.jaxrs.application.select
にvehicle.appを紐付ける
@Component(
property = {
"api.version=v1.0",
"osgi.jaxrs.application.select=(osgi.jaxrs.name=vehicle.app)",
"osgi.jaxrs.resource=true"
},
service = OpenAPIResourceImpl.class
)
@OpenAPIDefinition(
info = @Info(description = "Vehicle APP", ...)
@Path("/v1.0")
public class OpenAPIResourceImpl {
@GET
@Path("/openapi.{type:json|yaml}")
@Produces({MediaType.APPLICATION_JSON, "application/yaml"})
public Response getOpenAPI(@PathParam("type") String type) throws Exception {
...
}
...
private final Set<Class<?>> _resourceClasses = new HashSet<Class<?>>() {
{
add(VehicleHeadlessRestImpl.class);
add(OpenAPIResourceImpl.class);
}
};
}
このクラスをデプロイすると、Liferay API Explorerにvehicle-appが表示されます。では、早速試しましょう。
- Liferay API Explorer UIに登録されたことを確認する
- id=1(sedan)のサンプルリクエスト
- id=2(truck)のサンプルリクエスト
まとめ
今回は前回の成果を踏まえて、OpenAPIの標準仕様の記述ファイルを用いて、OpenAPIのソースコード生成ツールに作成されたコードをそのままLiferayのHeadless REST API化しました。作成されたAPIは一般的なCXFベースのJAXRSウェブサービスと見られています。
今回は検証のため、buildRESTが作成するAPIの中に存在するLiferayシステムサービスアクセス部分を実装していません。LiferayシステムサービスもAPIメソッドに組み込みたい場合、buildRESTのサンプルまたはテンプレート(portal-tools-rest-builder/resource.ftl、portal-tools-rest-builder/resource_factory_impl.ftl)をを参考にすればよいでしょう。
appendix A: yamlファイル
- vehicle.yaml
components:
schemas:
Vehicle:
type: object
discriminator:
propertyName: type
mapping:
Sedan: '#/components/schemas/Sedan'
Truck: '#/components/schemas/Truck'
Sports: '#/components/schemas/Sports'
properties:
name:
type: string
type:
type: string
enum:
- Sedan
- Truck
- Sports
Sedan:
description: 'a sedan'
allOf:
- $ref: '#/components/schemas/Vehicle'
- type: object
properties:
max_passengers:
type: integer
Truck:
description: 'a truck'
allOf:
- $ref: '#/components/schemas/Vehicle'
- type: object
properties:
max_load:
type: integer
Sports:
description: 'a sports car'
allOf:
- $ref: '#/components/schemas/Vehicle'
- type: object
properties:
max_speed:
type: integer
paths:
/vehicle:
get:
summary: Get a vehicle
parameters:
- in: query
name: id
schema:
type: integer
responses:
'200':
description: 'Vehicle Request'
content:
application/json:
schema:
$ref: '#/components/schemas/Vehicle'
openapi: 3.0.1
info:
description: "API for Get a Vehicle"
license:
name: "Apache 2.0"
url: "http://www.apache.org/licenses/LICENSE-2.0.html"
title: "Get Vehicle"
version: v1.0
appendix B: Swagger-Codegenの対応言語一覧
aspnetcore
csharp
csharp-dotnet2
go
go-server
dynamic-html
html
html2
java
jaxrs-cxf-client
jaxrs-cxf
inflector
jaxrs-cxf-cdi
jaxrs-spec
jaxrs-jersey
jaxrs-di
jaxrs-resteasy-eap
jaxrs-resteasy
micronaut
spring
nodejs-server
openapi
openapi-yaml
kotlin-client
kotlin-server
php
python
python-flask
r
ruby
scala
scala-akka-http-server
swift3
swift4
swift5
typescript-angular
typescript-axios
typescript-fetch
javascript