記事
· 2023年9月28日 22m read

OpenAPI Suite - パート 1

コミュニティの皆さん、こんにちは。

私が作成した OpenAPI-Suite という最新のパッケージをご紹介します。これは、OpenAPI 仕様バージョン 3.0 から ObjectScript コードを生成するツールセットです。  簡単に言うと、これらのパッケージでは以下を行うことができます。

  • サーバーサイドクラスの生成。  ^%REST による生成コードに非常に似ていますが、バージョン 3.0 がサポートされていることに付加価値があります。
  • HTTP クライアントクラスの生成。
  • クライアントプロダクション(ビジネスサービス、ビジネスオペレーション、ビジネスプロセス、Ens.Request、Ens.Response)クラスの生成。
  • コードの生成とダウンロードまたはサーバーでの直接コンパイルを行う Web インターフェース。
  • バージョン 1.x からバージョン 3.0 への仕様の変換。
  • 概要

    OpenAPI Suite は多数のパッケージに分割されており、様々な開発者コミュニティライブラリや公開 REST サービスを使用しています。  以下の図では、開発されたすべてのパッケージと、使用されているライブラリと Web サービスを示しています。

    注意: 公開 REST サービスの使用に問題がある場合は、コンバーターとバリデーターサービスの Docker インスタンスを開始することができます。

    各パッケージの機能

    OpenAPI Suite は、メンテナンス、改善、および今後の拡張を行いやすくするために、様々なパッケージで設計されています。  パッケージごとに役割がありますので、  それを確認してみましょう!

    openapi-common-lib

    これには、他のパッケージのすべての共通コードが含まれています。  たとえば、openapi-client-genopenapi-server-gen は、OpenAPI 仕様の以下の入力を受け入れます。 

  • URL
  • ファイルパス
  • %Stream.Object 
  • %DynamicObject
  • YAML 形式
  • JSON 形式
  • OpenAPI バージョン 1.x、2.x、3.0.x
  • ただし、%DynamicObject 内の仕様 3.0.x のみを処理できます。  変換のコードはこのパッケージにあります。  また様々なユーティリティも含まれています。  

    swagger-converter-cli

    openapi-common-lib の依存関係です。  これは、OpenAPI バージョン 3.0 でバージョン 1.x または 2.x を変換するために公開 REST サービスの converter.swagger.io を使用する HTTP クライアントです。

    swagger-validator-cli

    これも openapi-common-lib の依存関係です。名前は「validator」となっていますが、仕様の検証に使用されるものではありません。  converter.swagger.io は、OpenAPI 仕様の構造を単純化できるように、「parse」サービスを提供しています。  例: 「nested object definition」の定義を作成し、それを「$ref」に置換します。  これにより、コード生成アルゴリズムで処理されるケース数が軽減されます。

    openapi-client-gen

    このパッケージは、開発者が REST サービスを使用しやすくするクライアントサイドのコード生成専用です。

    単純な HTTP クライアントまたはプロダクションクライアント(ビジネスサービス、プロセス、オペレーション、プロダクションクラス)が含まれます。  元々、OpenAPI 2.x をサポートするために作成されていましたが、バージョン 3.x をサポートするように完全にリファクタリングされました。

    openapi-server-gen

    これは openapi-client-gen とは逆に、サーバーサイドのコード生成専用です。  ^%REST が存在するため、仕様バージョン 2.0 ではなく、バージョン 3.0 サポートがこのパッケージのターゲットとなっています。  

    openapi-suite

    上記すべてのパッケージをひとまとめにし、以下を行う REST API を提供します。 

  • コードを生成し、IRIS インスタンスでコードをコンパイルします。
  • コンパイルせずにダウンロードのみのコードを生成します。
  • この REST API を使用し、OpenAPI Suite の機能を使用するための Web インターフェースも提供されています。

    ライブラリ

    以下に、この開発で利用した DC 上の既存のライブラリをいくつか紹介します。

    objectscript-openapi-definition

    OpenAPI 仕様からモデルクラスを生成する便利なライブラリです。  これはこのプロジェクトで非常に重要な要素であり、私自身も貢献しているライブラリです。

    ssl-client

    SSL 構成を作成できるようにします。  主に、HTTPS リクエストの「DefaultSSL」という名前の構成の作成に使用されています。  

    yaml-utils

    YAML 形式仕様の場合に、このライブラリは JSON 形式に変換するために使用されます。  このプロジェクトでは不可欠なライブラリです。  ちなみに、最初は openapi-client-gen バージョン 1 で YAML 仕様をテストするために開発されました。

    io-redirect

    これは私のライブラリの 1 つです。「write」をストリーム、ファイル、グローバル、または文字列変数にリダイレクトできます。  ログのトレースを維持するために、REST サービスで使用されています。  このコミュニティ記事からアイデアを得ました。

    IPM によるインストール

    このスイートをインストールするには、IPM(zpm)の使用が最適です。  多数のパッケージと依存関係があるため、IPM を使用するのが確実に便利と言えます。

    zpm "install openapi-suite"
    ; optional
    ; zpm "install swagger-ui"

     

    Docker によるインストール

    特別なことは何もありません。このプロジェクトは intersystems-iris-dev-template を使用しています。

    git clone git@github.com:lscalese/openapi-suite.git
    cd openapi-suite
    docker-compose up -d

    Iris の起動にエラーがある場合は、おそらく iris-main.log の権限の問題と思われます。

    以下を試してみてください。

    touch iris-main.log && chmod 777 iris-main.log

    注意: irisowner ユーザーに RW 権限を追加するだけで十分なはずです。

    使用方法

    OpenAPI-Suite には、コードを「生成してダウンロード」するか「生成してインストール」するための Web インターフェースが備わっています。  

    このインターフェースは http://localhost:52796/openapisuite/ui/index.csp で使用できます(*必要に応じて、使用しているポート番号に変更してください)。  

    非常に簡単で、フォームに入力するだけです。

  • Application package name: 生成されるクラスに使用されるパッケージの名前です。  既存でないパッケージ名である必要があります。
  • What do you want to generate?: 生成するものを HTTP Client、Client Production、または REST server から選択します。
  • Namespace: コードが生成されるネームスペースを選択します。  「Install On Server」(サーバーにインストール)をクリックした場合にのみ意味があり、そうでない場合はこのフィールドは無視されます。
  • Web Application Name: Web アプリケーションはオプションであり、「REST Server」の生成を選択した場合にのみ利用できます。  生成される REST ディスパッチクラスに関連する Web アプリケーションを作成しない場合は、空のままにします。
  • OpenAPI specification field: 仕様を指している URL を入力するか、仕様自体をコピー/貼り付けします(後者の場合、仕様は JSON 形式である必要があります)。
  • 「Download Only」(ダウンロードのみ)ボタンをクリックした場合、コードは XML ファイルで生成されて返され、クラスはサーバーから削除されます。  生成されるクラスを一時的に保存するために使用するネームスペースは、OpenAPI-Suite がインストールされているネームスペースです(Docker インストールを使用した場合のデフォルトは IRISAPP です)。  

    ただし、「Install On Server」(サーバーにインストール)ボタンをクリックした場合、コードは生成・コンパイルされ、サーバーは、コード生成/コンパイルのステータス付きの JSON メッセージとログを返します。

    デフォルトではこの機能は無効化されていますが、有効にするには IRIS ターミナルを開いて以下を実行してください。

    Set ^openapisuite.config("web","enable-install-onserver") = 1

    OpenAPI-Suite REST API を詳しく見る

    CSP フォームは、http://localhost:52796/openapisuite にある REST サービスを使用します。

    swagger-ui http://localhost:52796/swagger-ui/index.html を開き、http://localhost:52796/openapisuite/_spec を詳しく見てみましょう。

    これは、Angular フレームワークを使用してより高度なフロントエンドアプリケーションを作成するための第一歩です。


    コードをプログラミングで生成する

    もちろん、UI の使用は必須ではありません。このセクションでは、コードをプログラミングで生成し、生成されたサービスを使用する方法について説明します。

    すべてのスニペットは、dc.openapi.suite.samples.PetStore クラスでも使用できます。

     

    HTTP クライアント

    Set features("simpleHttpClientOnly") = 1
    Set sc = ##class(dc.openapi.client.Spec).generateApp("petstoreclient", "https://petstore3.swagger.io/api/v3/openapi.json", .features)

     

    最初の引数は、クラスが生成されるパッケージであるため、有効なパッケージ名を渡すようにしましょう。  2 つ目の引数は、仕様を指す URL、ファイル名、ストリーム、または %DynamicObject です。  「features」は配列であり、現在以下のサブスクリプトのみを使用できます。 

    simpleHttpClientOnly: 1 である場合、単純な HTTP クライアントのみが生成されます。そうでない場合、プロダクションも生成されます(デフォルトの動作)。

    compile: 0 である場合、生成されたコードはコンパイルされません。  エクスポートの目的のみでコードを生成する場合に便利です。  デフォルトは、compile = 1 です。

    以下は、生成したばかりの HTTP クライアントで「addPet」サービスを使用する例です。

    Set messageRequest = ##class(petstoreclient.requests.addPet).%New()
    Set messageRequest.%ContentType = "application/json"
    Do messageRequest.PetNewObject().%JSONImport({"id":456,"name":"Mittens","photoUrls":["https://static.wikia.nocookie.net/disney/images/c/cb/Profile_-_Mittens.jpg/revision/latest?cb=20200709180903"],"status":"available"})
      
    Set httpClient = ##class(petstoreclient.HttpClient).%New("https://petstore3.swagger.io/api/v3","DefaultSSL")
    ; MessageResponse will be an instance of petstoreclient.responses.addPet
    Set sc = httpClient.addPet(messageRequest, .messageResponse)
    If $$$ISERR(sc) Do $SYSTEM.Status.DisplayError(sc) Quit sc
      
    Write !,"Http Status code : ", messageResponse.httpStatusCode,!
    Do messageResponse.Pet.%JSONExport()

     

     

    クリックして生成されたクラスを表示します。

     

    HTTP クライアントのプロダクション

    Set sc = ##class(dc.openapi.client.Spec).generateApp("petstoreproduction", "https://petstore3.swagger.io/api/v3/openapi.json")

    最初の引数は、単純な HTTP クライアントのコード生成をテストする場合のパッケージ名であるため、クライアントプロファクションの場合は必ず別のパッケージ名を使用してください。  2 つ目と 3 つ目も HTTP クライアントと同じルールが適用されます。

    テストする前に、以下のコマンドを使用して、管理ポータルからプロダクションを起動してください。

    Do ##class(Ens.Director).StartProduction("petstoreproduction.Production")

    以下は、「addPet」サービスを使用する例ですが、今回は生成されたプロダクションを使用します。

    Set messageRequest = ##class(petstoreproduction.requests.addPet).%New()
    Set messageRequest.%ContentType = "application/json"
    Do messageRequest.PetNewObject().%JSONImport({"id":123,"name":"Kitty Galore","photoUrls":["https://www.tippett.com/wp-content/uploads/2017/01/ca2DC049.130.1264.jpg"],"status":"pending"})
    ; MessageResponse will be an instance of petstoreclient.responses.addPet
    Set sc = ##class(petstoreproduction.Utils).invokeHostSync("petstoreproduction.bp.SyncProcess", messageRequest, "petstoreproduction.bs.ProxyService", , .messageResponse)
    Write !, "Take a look in visual trace (management portal)"
    If $$$ISERR(sc) Do $SYSTEM.Status.DisplayError(sc)
    Write !,"Http Status code : ", messageResponse.httpStatusCode,!
    Do messageResponse.Pet.%JSONExport()

    次に、視覚的なトレースを開いて詳細を確認します。 

    packages モデル、リクエスト、およびレスポンスに生成されたクラスは、単純な HTTP クライアント用に生成されるコードと非常によく似ています。  パッケージリクエストのクラスは Ens.Request を継承し、パッケージレスポンスのクラスは Ens.Response を継承します。  ビジネスオペレーションのデフォルトの実装は非常に単純です。以下のスニペットをご覧ください。 

    Class petstoreproduction.bo.Operation Extends Ens.BusinessOperation [ ProcedureBlock ]
    {
    
    Parameter ADAPTER = "EnsLib.HTTP.OutboundAdapter";
    Property Adapter As EnsLib.HTTP.OutboundAdapter;
    /// Implement operationId : addPet
    /// post /pet
    Method addPet(requestMessage As petstoreproduction.requests.addPet, Output responseMessage As petstoreproduction.responses.addPet) As %Status
    {
        Set sc = $$$OK, pHttpRequestIn = ##class(%Net.HttpRequest).%New(), responseMessage = ##class(petstoreproduction.responses.addPet).%New()
        $$$QuitOnError(requestMessage.LoadHttpRequestObject(pHttpRequestIn))
        $$$QuitOnError(..Adapter.SendFormDataArray(.pHttpResponse, "post", pHttpRequestIn, , , ..Adapter.URL_requestMessage.%URL))
        $$$QuitOnError(responseMessage.LoadFromResponse(pHttpResponse, "addPet"))
        Quit sc
    }
    ...
    }
    }

     

    HTTP サーバーサイド REST アプリケーション

    Set sc = ##class(dc.openapi.server.ServerAppGenerator).Generate("petstoreserver", "https://petstore3.swagger.io/api/v3/openapi.json", "/petstore/api")

    最初の引数は、クラスを生成するパッケージ名です。  2 つ目は HTTP クライアントと同じルールが適用されます。  3 つ目の引数は必須ではありませんが、使用されている場合、Web アプリケーションが指定された名前で作成されます(有効な Web アプリケーション名を指定することに注意してください)。

    クラス petstoreserver.disp(ディスパッチ %CSP.REST クラス)は、^%REST が生成するコードに似ており、リクエストを受け入れるか拒否する多数のチェックを実行し、petstoreserver.impl で関連するサービス ClassMethod 実装を呼び出します。  主な違いは、実装メソッドに渡される引数で、これは petstoreserver.requests オブジェクトです。 例:

    Class petstoreserver.disp Extends %CSP.REST [ ProcedureBlock ]
    {
    
    Parameter CHARSET = "utf-8";
    Parameter CONVERTINPUTSTREAM = 1;
    Parameter IgnoreWrites = 1;
    Parameter SpecificationClass = "petstoreserver.Spec";
    /// Process request post /pet
    ClassMethod addPet() As %Status
    {
        Set sc = $$$OK
        Try{
            Set acceptedMedia = $ListFromString("application/json,application/xml,application/x-www-form-urlencoded")
            If '$ListFind(acceptedMedia,$$$LOWER(%request.ContentType)) {
                 Do ##class(%REST.Impl).%ReportRESTError(..#HTTP415UNSUPPORTEDMEDIATYPE,$$$ERROR($$$RESTContentType,%request.ContentType)) Quit
            }
            Do ##class(%REST.Impl).%SetContentType($Get(%request.CgiEnvs("HTTP_ACCEPT")))
            If '##class(%REST.Impl).%CheckAccepts("application/xml,application/json") Do ##class(%REST.Impl).%ReportRESTError(..#HTTP406NOTACCEPTABLE,$$$ERROR($$$RESTBadAccepts)) Quit
            If '$isobject(%request.Content) Do ##class(%REST.Impl).%ReportRESTError(..#HTTP400BADREQUEST,$$$ERROR($$$RESTRequired,"body")) Quit
            Set requestMessage = ##class(petstoreserver.requests.addPet).%New()
            Do requestMessage.LoadFromRequest(%request)
            Set scValidateRequest = requestMessage.RequestValidate()
            If $$$ISERR(scValidateRequest) Do ##class(%REST.Impl).%ReportRESTError(..#HTTP400BADREQUEST,$$$ERROR(5001,"Invalid requestMessage object.")) Quit
            Set response = ##class(petstoreserver.impl).addPet(requestMessage)
            Do ##class(petstoreserver.impl).%WriteResponse(response)
        } Catch(ex) {
            Do ##class(%REST.Impl).%ReportRESTError(..#HTTP500INTERNALSERVERERROR,ex.AsStatus(),$parameter("petstoreserver.impl","ExposeServerExceptions"))
        }
        Quit sc
    }
    ...
    }
    

    ご覧のとおり、dispatch クラスは実装メソッドを呼び出す前に「LoadFromRequest」と「RequestValidate」を呼び出しています。  これらのメソッドにはデフォルトの実装がありますが、コードジェネレーターはすべてのケースに対応できません。  現時点では、「query」、「headers」、「path」、およびコンテンツタイプが「application/json」、「application/octet-stream」、または「multipart/form-data」である body で最も一般的なケースがパラメーターとして自動的に処理されます。  開発者は、必要に応じて実装をチェック/完了する必要があります(未対応のケースについては、デフォルトでは、コードジェネレーターは $$$ThrowStatus($$$ERROR($$$NotImplemented)) を設定します)。

     

     

    リクエストクラスの例:

     

    ^%REST の使用方法と同じように、「petstoreserver.impl」クラスには、サービスに関連したすべてのメソッドが含まれており、開発者が実装する必要があります。 

    Class petstoreserver.impl Extends %REST.Impl [ ProcedureBlock ]
    {
    
    Parameter ExposeServerExceptions = 1;
    /// Service implemntation for post /pet
    ClassMethod addPet(messageRequest As petstoreserver.requests.addPet) As %Status
    {
        ; Implement your service here.
        ; Return {}
        $$$ThrowStatus($$$ERROR($$$NotImplemented))
    }
    
    ...
    }

     

    生成されたパッケージの短い説明

    パケージ名 / クラス名 タイプ 説明
    petstoreclient.model petstoreproduction.model クライアントサイドとサーバーサイド すべてのモデルが含まれます。  これらのクラスは、JSON から簡単にオブジェクトを読み込めるように %JSON.Adaptor を拡張します。   プロダクションが生成されると、これらのクラスは %Persistent も拡張します。
    petstoreclient.requests petstoreproduction.requests クライアントサイドとサーバーサイド %Net.HttpRequest を簡単に初期化するために使用されるオブジェクト。  仕様にはオペレーションごとのクラスが定義されています。プロダクションが生成される場合、これらのクラスは Ens.Request を拡張します。 注意: このクラスの実装は、生成対象がサーバーサイドであるかクライアントサービスであるかによって異なります。  クライアントサイドの場合、すべてのクラスには「LoadHttpRequestObject」メソッドが含まれるため、このクラスプロパティから「%Net.HttpRequest」を読み込むことができます。 クラスがサーバーサイドを対象に生成される場合、「%request」オブジェクトからインスタンスを読み込むために、各クラスに「LoadFromRequest」メソッドが含まれています。
    petstoreclient.responses petstoreproduction.responses クライアントサイドとサーバーサイド petstoreclient.requests の 逆です。  %Net.HttpRequest のレスポンスを処理できます。プロダクションが生成される場合、これらのクラスは Ens.Response を拡張します。
    petstoreclient.HttpClient クライアントサイド HTTP リクエストを実行するためのすべてのメソッドが含まれます。OpenAPI 仕様で定義されるオペレーションごとに 1 つのメソッドがあります。
    petstoreproduction. bo.Operation クライアントサイド オペレーションクラスには、OpenAPI 仕様で定義されたオペレーションごとに 1 つのメソッドがあります。
    petstoreproduction.bp クライアントサイド 同期と非同期の 2 つのデフォルトのビジネスプロセスが定義されています。
    petstoreproduction.bs クライアントサイド 実装するすべての空のビジネスサービスが含まれます。
    petstoreproduction.Production クライアントサイド プロダクション構成の設定。
    petstoreserver.disp サーバーサイド クラス dispatch %CSP.REST
    petstoreserver.Spec サーバーサイド このクラスには、XData ブロックに OpenAPI 仕様が含まれています。
    petstoreserver.impl サーバーサイド これには、OpenAPI 仕様で定義されたオペレーションに関連するすべての空のメソッドが含まれています。  これが、開発者がサービスを実装する必要のあるクラス(%REST.Impl を拡張)です。

    開発ステータス

    OpenAPI-Suite はまだ非常に未熟な製品であり、さらに多くのテストを実施した上での改善が必要です。  OpenAPI 3 のサポートは部分的であり、さらに多くの機能がサポートされる可能性があります。 

    テストは公開されている仕様 https://petstore3.swagger.io/api/v3/openapi.json と比較的に単純な 2 つの仕様を使って実施されました。  もうちろん、すべてのケースに対応するには不十分です。  仕様を共有していただければ、喜んでテストに使用させていただきます。

    このプロジェクトの基盤は十分であり、AsyncAPI をサポートするように拡張するなど、簡単に進化させることができると考えています。

    お気軽にフィードバックをお寄せください。

    このアプリケーションをご利用いただき、開発者ツールコンテストで支援していただければ幸いです。

    お読みいただきありがとうございました。

    ディスカッション (0)1
    続けるにはログインするか新規登録を行ってください