開発者の皆さん、こんにちは!
InterSystems デベロッパーツールコンテスト2023 の21の応募作品の中から、Experts Nomination 第3位に輝いた @Lorenzo Scalese さんの OpenAPI-Suite(OpenAPI3.0 からObjectScriptコードを生成するためのツールセット)についてご紹介します。
現時点でIRISはOpenAPI2.0までの対応なのですが、このツールの凄いところは、OpenAPI3.0に対応しているところです!
提供している機能は、以下の通りです。
- サーバー側クラスの作成(OpenAPI2.0と同様に、APIファーストで作成するRESTディスパッチクラスの作成に必要なクラス群を作成します。)
- HTTPクライアントクラスの作成
- クライアントプロダクション(ビジネス・サービス、ビジネス・オペレーション、ビジネス・プロセス、Ens.Request、Ens.Response)
- このツールから生成する機能を指定できるWebインターフェース
- OpenAPI 1.x、2.x からバージョン3.0への変換ツール
各機能について詳しくは、@Lorenzo Scales さんが書かれた記事「OpenAPI Suite - Part 1」「OpenAPI Suite - Part 2」をご参照ください。
この記事では、1の機能を試してみました。その中でも、以下の対応がとても素晴らしい!と思いました。
- OpenAPI 3.0から加わったComponentsオブジェクトの内容に合わせ、クラス定義を自動生成している(指定するパッケージ以下にmodelパッケージができ、そこにインスタンス化できるクラスができています)
- POSTやPUTの場合、HTTP要求で渡される情報を実装クラス(impl.cls)で処理しやすいように、Bodyで受け取ったJSON文字を1で生成されたクラスのインスタンスに設定するところまで処理してくれています。
- 現時点では、一部手動で修正が必要ですが、そのまま作成されたインスタンスを利用してimpl.clsで%Save()を書いたら保存されました。)
IRISには、OpenAPI2.0の仕様でRESTディスパッチクラスを作成する方法が提供されていますが、使用するクラス群とベースURLの作成のみを行っています。
参考までに、IRISが対応しているOpenAPI2.0で作成した内容をご紹介します。
(OpenAPI2.0の仕様で作成されたJSONサンプルを使用しています。)
以下の例では、/curdがベースURLとなり、curdパッケージ以下にクラス群が作成されます。
.png)
生成されるクラスは以下の通りです(左:impl.cls、右:spec.cls)
.png)
ディスパッチクラス(disp.cls)は加工不要クラスのため、例ではスタジオでの表示でご紹介しています。
.png)
詳細は【はじめてのInterSystems IRIS】セルフラーニングビデオ:アクセス編:(REST)APIファーストで作成するRESTディスパッチクラス のビデオをご参照ください。
次は @Lorenzo Scalese さんの OpenAPI-Suite の結果をご紹介します。
使用するまでの手順はとても簡単です。
IPM(InterSystems Package Mangaer:以前はZPMとも呼ばれていました)のクライアントツールを管理ポータルかスタジオからインポートしたら、以下コマンドを実行するだけでツールの準備が整います。
クライアントツールのインポートはどのネームスペースでも大丈夫です。管理ポータルからインポートされる場合は、以下メニューを利用します。
管理ポータル→システムエクスプローラ→クラス→インポート対象ネームスペースを選択→インポートボタンクリック
RESTディスパッチクラスを置きたいネームスペースに移動し、ZPMコマンドを利用してツールをインストールします。
zpm "install openapi-suite"
ObjectScript
ObjectScript
USER>zpm "install openapi-suite"
[USER|sslclient] Reload START (/usr/irissys/mgr/.modules/USER/sslclient/1.0.4/)
[USER|sslclient] Reload SUCCESS
[sslclient] Module object refreshed.
[USER|sslclient] Validate START
[USER|sslclient] Validate SUCCESS
[USER|sslclient] Compile START
[USER|sslclient] Compile SUCCESS
[USER|sslclient] Activate START
[USER|sslclient] Configure START
[USER|sslclient] Configure SUCCESS
[USER|sslclient] Activate SUCCESS
[USER|yaml-utils] Reload START (/usr/irissys/mgr/.modules/USER/yaml-utils/0.1.2/)
[USER|yaml-utils] Reload SUCCESS
[yaml-utils] Module object refreshed.
[USER|yaml-utils] Validate START
[USER|yaml-utils] Validate SUCCESS
[USER|yaml-utils] Compile START
[USER|yaml-utils] Compile SUCCESS
[USER|yaml-utils] Activate START
[USER|yaml-utils] Configure START
[USER|yaml-utils] Configure SUCCESS
[USER|yaml-utils] Activate SUCCESS
[USER|swagger-validator-cli] Reload START (/usr/irissys/mgr/.modules/USER/swagger-validator-cli/0.0.1/)
[USER|swagger-validator-cli] Reload SUCCESS
[swagger-validator-cli] Module object refreshed.
[USER|swagger-validator-cli] Validate START
[USER|swagger-validator-cli] Validate SUCCESS
[USER|swagger-validator-cli] Compile START
[USER|swagger-validator-cli] Compile SUCCESS
[USER|swagger-validator-cli] Activate START
[USER|swagger-validator-cli] Configure START
[USER|swagger-validator-cli] Configure SUCCESS
[USER|swagger-validator-cli] Activate SUCCESS
[USER|swagger-converter-cli] Reload START (/usr/irissys/mgr/.modules/USER/swagger-converter-cli/0.0.2/)
[USER|swagger-converter-cli] Reload SUCCESS
[swagger-converter-cli] Module object refreshed.
[USER|swagger-converter-cli] Validate START
[USER|swagger-converter-cli] Validate SUCCESS
[USER|swagger-converter-cli] Compile START
[USER|swagger-converter-cli] Compile SUCCESS
[USER|swagger-converter-cli] Activate START
[USER|swagger-converter-cli] Configure START
[USER|swagger-converter-cli] Configure SUCCESS
[USER|swagger-converter-cli] Activate SUCCESS
[USER|objectscript-openapi-definition] Reload START (/usr/irissys/mgr/.modules/USER/objectscript-openapi-definition/1.3.1/)
[USER|objectscript-openapi-definition] Reload SUCCESS
[objectscript-openapi-definition] Module object refreshed.
[USER|objectscript-openapi-definition] Validate START
[USER|objectscript-openapi-definition] Validate SUCCESS
[USER|objectscript-openapi-definition] Compile START
[USER|objectscript-openapi-definition] Compile SUCCESS
[USER|objectscript-openapi-definition] Activate START
[USER|objectscript-openapi-definition] Configure START
[USER|objectscript-openapi-definition] Configure SUCCESS
[USER|objectscript-openapi-definition] Activate SUCCESS
[USER|io-redirect] Reload START (/usr/irissys/mgr/.modules/USER/io-redirect/1.0.2/)
[USER|io-redirect] Reload SUCCESS
[io-redirect] Module object refreshed.
[USER|io-redirect] Validate START
[USER|io-redirect] Validate SUCCESS
[USER|io-redirect] Compile START
[USER|io-redirect] Compile SUCCESS
[USER|io-redirect] Activate START
[USER|io-redirect] Configure START
[USER|io-redirect] Configure SUCCESS
[USER|io-redirect] Activate SUCCESS
[USER|openapi-common-lib] Reload START (/usr/irissys/mgr/.modules/USER/openapi-common-lib/1.0.0/)
[USER|openapi-common-lib] Reload SUCCESS
[openapi-common-lib] Module object refreshed.
[USER|openapi-common-lib] Validate START
[USER|openapi-common-lib] Validate SUCCESS
[USER|openapi-common-lib] Compile START
[USER|openapi-common-lib] Compile SUCCESS
[USER|openapi-common-lib] Activate START
[USER|openapi-common-lib] Configure START
[USER|openapi-common-lib] Configure SUCCESS
[USER|openapi-common-lib] Activate SUCCESS
[USER|openapi-server-gen] Reload START (/usr/irissys/mgr/.modules/USER/openapi-server-gen/1.0.0/)
[USER|openapi-server-gen] Reload SUCCESS
[openapi-server-gen] Module object refreshed.
[USER|openapi-server-gen] Validate START
[USER|openapi-server-gen] Validate SUCCESS
[USER|openapi-server-gen] Compile START
[USER|openapi-server-gen] Compile SUCCESS
[USER|openapi-server-gen] Activate START
[USER|openapi-server-gen] Configure START
[USER|openapi-server-gen] Configure SUCCESS
[USER|openapi-server-gen] Activate SUCCESS
[USER|openapi-client-gen] Reload START (/usr/irissys/mgr/.modules/USER/openapi-client-gen/2.1.0/)
[USER|openapi-client-gen] Reload SUCCESS
[openapi-client-gen] Module object refreshed.
[USER|openapi-client-gen] Validate START
[USER|openapi-client-gen] Validate SUCCESS
[USER|openapi-client-gen] Compile START
[USER|openapi-client-gen] Compile SUCCESS
[USER|openapi-client-gen] Activate START
[USER|openapi-client-gen] Configure START
[USER|openapi-client-gen] Configure SUCCESS
[USER|openapi-client-gen] Activate SUCCESS
[USER|openapi-suite] Reload START (/usr/irissys/mgr/.modules/USER/openapi-suite/1.0.0/)
[USER|openapi-suite] Reload SUCCESS
[openapi-suite] Module object refreshed.
[USER|openapi-suite] Validate START
[USER|openapi-suite] Validate SUCCESS
[USER|openapi-suite] Compile START
[USER|openapi-suite] Compile SUCCESS
[USER|openapi-suite] Activate START
[USER|openapi-suite] Configure START
[USER|openapi-suite] Configure SUCCESS
[USER|openapi-suite] Activate SUCCESS
USER>
インストール後、以下URLでツールの専用画面にアクセスできます。
http://localhost:ポート番号/openapisuite/ui/index.csp
例)http://localhost:52773/openapisuite/ui/index.csp
.png)
初期段階では、画面右下の「Install On Server」ボタンは押せなくなっています。以下グローバル変数に値をセットすることで、ボタンが押せるようです。
(ZPMコマンドでツールをインストールしたネームスペースでセットしてください)
Set ^openapisuite.config("web","enable-install-onserver") = 1
ObjectScript
ObjectScript
準備が完了したら、以下設定します。
「Applcation package name」にはクラス定義のパッケージ名を(例では、PetStore)、「What do you want to generate?」では、「REST Server」を選択します。「NameSpace」にはRESTディスパッチクラスを登録したいネームスペースを選択し、「Web Application Name」には、定義したいベースURL(ウェブ・アプリケーションパス)を指定します(例では、/pet1)。
後は、OpenAPIの仕様に基づいて作成されたJSONファイルを指定するだけですが、サンプルでは、https://petstore3.swagger.io/api/v3/openapi.json がデフォルトで指定されていますのでこのまま使用します。
最後に、「Install On Server」ボタンをクリックします。
以下のようにログが表示されますので、正常に終了したことを確認してCloseボタンをクリックします。
.png)
ツールを通して、ウェブアプリケーションパス:/pet1が作成されている予定です。確認してみましょう。
管理ポータル→システム管理→セキュリティ→アプリケーション→ウェブ・アプリケーション から /pet1があるか確認します。
.png)
しっかり作成されていました!
では、入力されたパスに対応するメソッドのマッピングが記載されているdisp(ディスパッチクラス)や、コードを実装するimplクラスができているか確認してみます。
PetStoreパッケージ以下にしっかりクラス定義が作成されていました!
.png)
IRISが提供するOpenAPI2.0のクラス群と異なる点は、modelパッケージとrequestsパッケージが含まれている点です。
このツールの中では、OpenAPI3.0のComponentsオブジェクトで指定している情報を、インスタンス生成ができるクラス定義として作成しています。(初期段階では%RegisteredObjectクラスを継承しています)
ComponentsオブジェクトのPetの情報は、以下展開すると参照できます。
"Pet":{
"required":[
"name",
"photoUrls"
],
"type":"object",
"properties":{
"id":{
"type":"integer",
"format":"int64",
"example":10
},
"name":{
"type":"string",
"example":"doggie"
},
"category":{
"$ref":"#/components/schemas/Category"
},
"photoUrls":{
"type":"array",
"xml":{
"wrapped":true
},
"items":{
"type":"string",
"xml":{
"name":"photoUrl"
}
}
},
"tags":{
"type":"array",
"xml":{
"wrapped":true
},
"items":{
"$ref":"#/components/schemas/Tag"
}
},
"status":{
"type":"string",
"description":"pet status in the store",
"enum":[
"available",
"pending",
"sold"
]
}
},
"xml":{
"name":"pet"
}
},
この情報から作成されたクラスは以下の通りです。
.png)
インスタンス化ができるクラスで用意されています。
スーパークラスを%RegisteredObjectから%Persistentに変更+コンパイルを行えば、データベースに登録できる永続クラスとして利用できます。
また、POST要求では、以下の形式でJSONオブジェクトが送付されます。
{
"id": 0,
"category": {
"id": 0,
"name": "Dog"
},
"name": "Hachi",
"photoUrls": [
"https://x.gd/1pbYK"
],
"tags": [
{
"id": 0,
"name": "Middle"
}
],
"status": "available"
}
ObjectScript
ObjectScript
このJSONをそのままPet、Tag、Categoryクラスのインスタンスに当てはめることができればいいので、%JSON.Adaptorを追加で継承させるとよさそうです。
ということで、Pet、Tag、Categoryに手を加えるとしたら、スーパークラスの設定を %RegisterdObject から %Persistent と %JSON.Adaptor の多重継承に変えたらよさそうです。
.png)
この後は、受け取ったBodyの情報をデータベースに更新する処理が必要です。
この処理の大枠を記載してくれているのが、requestパッケージ以下クラスです。以下例では、/petに対してPOST要求を行うときに動作するクラス(PetStore.requests.addPet.cls)の中身です。
.png)
LoadFromRequest()メソッドに注目すると、引数requestにHTTP要求を操作できる%requestが渡されるようになっています(IRISのRESTディスパッチクラスの内部処理では、HTTP要求は%CSP.Requestクラスのインスタンスに収納されます)。
request.Content
ObjectScript
ObjectScript
上記実行でBodyに指定されたJSONオブジェクトを取得できます。
Do ..PetNewObject().%JSONImport(request.Content)
ObjectScript
ObjectScript
と記述がありますが、このクラス内にPetNewObject()メソッドがないため、以下のように修正しています(Petのインスタンスを生成し、プロパティPetに設定した後、受信したJSONをインスタンスに割り当てるための処理に変えています)。
set ..Pet=##class(PetStore.model.Pet).%New()
do ..Pet.%JSONImport(request.Content)
ObjectScript
ObjectScript
これで、HTTP要求を受信したときの処理は終了です。残りは、impl.clsの実装です。
初期段階では、以下の状態で何も記載されていません。
ClassMethod addPet(messageRequest As PetStore.requests.addPet) As %Status
{
$$$ThrowStatus($$$ERROR($$$NotImplemented))
}
ObjectScript
ObjectScript
引数messageRequestには、1つ前の流れで確認した PetStore.requests.addPet クラスのインスタンスが指定されています(このインスタンスのPetプロパティにHTTP要求で受信したJSONオブジェクトから作成したインスタンスが設定されています。)
以下のように書き換え、コンパイルを行ったら修正は終了です!
ClassMethod addPet(messageRequest As PetStore.requests.addPet) As %Status
{
set status=$$$OK
set status=messageRequest.Pet.%Save()
return status
}
ObjectScript
ObjectScript
POST要求をテストしてみます。
.png)
PetもCategoryもTagもしっかりデータ登録できました!
.png)
.png)
少し加工が必要ですが、OpenAPI3.0のComponentsオブジェクトからクラス定義が自動生成されるところが、とても便利だと思いました!
ぜひ皆さんもお試しください。