2020年3月10日火曜日

REST APIで取得したJSON配列を扱う3つの方法

最近、あるサービスとの連携を、そのサービスが提供しているREST APIを使ってやってください、という依頼があったので試してみたことを紹介します。Oracle APEXを使うことで可能な方法としては、以下の3つの方法があるかと思います。
  1. Webソース・モジュールを作成する。
  2. Webソース・モジュールを作成し、APEX_EXEC.OPEN_WEB_SOURCE_QUERYを呼び出し、それを処理するパイプライン表関数を作成する。
  3. APEX_WEB_SERVICE.MAKE_REST_REQUEST_Bを呼び出し、JSON文書の取得とパースを行うパイプライン表関数を作成する。
表示されたレポートは以下のような感じになります。上記3つの方法、全てで同じ表示になります。
 

上から順にAPEXでの扱いが簡単ですが、それぞれ一長一短はあります。JSONをそのまま扱う方が、パイプライン表関数を作るときにわかりやすい(つまり3番が一番簡単)という方もいらっしゃるでしょう。

参考にしたのはCarsten Czarskiによるブログポスト"APEX 18.1 Early Adopter 2: REST Services and PL/SQL"です。

Webソース・モジュールのみによるレポート作成


最初にアプリケーション・ビルダーからワークスペース・ユーティリティを選んで、Web資格証明を開きます。
 

Web資格証明として、認証に必要な情報を登録します。Oracle APEX 19.2では、指定可能な認証タイプは3種類あります。
  • 基本認証
  • OAuth2クライアント資格証明フロー
  • Oracle Cloud Infrastructure (OCI)
基本認証は与えられているクライアントIDまたはユーザー名クライアント・シークレットまたはパスワードから、HTTPの"Authorization: Basic"リクエストヘッダーを生成するものです。

OAuth2クライアント資格証明フローは、OAuth2による認証で使用します。API呼び出しに使用する場合は、トークン・エンドポイントの指定はWebソース・モジュールの設定に含まれます。

Oracle Cloud Infrastructure (OCI)は、その名の通り、Oracle Cloud Infrastructureが提供しているAPIの呼び出しに使用されます。使い方については以前にオブジェクト・ストレージについてブログを記載したときに紹介したことがあります。こちらのエントリです。

今回は基本認証を認証タイプとして指定したWeb資格証明を登録しました。静的識別子は必須の項目になっていませんが、ファンクションAPEX_WEB_SERVICE.MAKE_REST_REQUEST_BによってREST APIを呼び出す際に使われるため、必ず指定します。また、PL/SQLのコード中に記述されるので、日本語や空白といった文字は使わない方が良いでしょう。
 

次にWebソース・モジュールを作成します。アプリケーションの共有コンポーネントにある、データ・ソースのセクションに含まれるWebソース・モジュールを開きます。
 

Webソース・タイプとして4種類選択できます。詳細は画面にある""をクリックすると説明が表示されます。

登録済みのWebソース・モジュールをレポートする画面に作成ボタンがあります。それをクリックし、作成画面に進みます。最初に開いたダイアログで最初からをチェックし、に進むとWebソース・モジュールの登録に必要な情報の入力画面になります。

簡易HTTP以外は、それぞれのサービスが持っている、ページネーション(ヘルプではページ区切りという用語になっています)のサポートが含まれます。REST APIの応答に大量の行が含まれる場合、一度の応答に含まれる行を固定し、複数のリクエストを発行することにより結果全体を取得する、というものです。Oracle REST Data Servicesについては、さらにサーバー側に処理を移譲することが可能になっています。これらの特定の機能を持ったサービス以外は、簡易HTTPを選びます。

Webソース・モジュールの名前とREST APIのURLエンドポイントを指定し、へ進みます。
 

リモート・サーバーベースURLサービスURLパスは、入力したURLエンドポイントから自動的に決定されます。へ進みます。
 

事前に登録してあるWeb資格証明を選択し、検出をクリックします。
 

プレビューされている内容を確認し、Webソースの作成をクリックします。以下の画面は概要のプレビュー画面で、データとデータ・プロファイルの確認が可能です。
 

詳細をクリックし、詳細のプレビュー画面に移ると、データとデータ・プロファイルに加えて、操作、レスポンス本文、レスポンス・ヘッダーの確認も可能です。以下は詳細画面にてデータ・プロファイルを表示させています。行セレクタは選択されたJSON配列のキー値です。JSONに含まれているキー値がOracle Databaseでどのように扱われるか、例えば数値が文字列として扱われていないか、日付が文字列として扱われていないかは、あらかじめ確認しておきましょう。
 

これでWebソース・モジュールの作成が完了しました。

Webソース・モジュールからレポートを作成


この方法はOracle APEXの標準的な作業になります。対話グリッド、対話レポートおよびクラシック・レポートを作成する際に使用するデータ・ソースとして、作成済みのWebソース・モジュールを指定します。
 

APEX_EXEC.OPEN_WEB_SOURCE_QUERYによるレポート


作成したWebソース・モジュールの編集画面を開いて、静的識別子を確認します。扱いやすい名前に変更しても良いでしょう。
 

次にデータベースにタイプをふたつ定義します。ひとつめは行を表すタイプT_OIC_INSTANCE_ROWです。
CREATE OR REPLACE EDITIONABLE TYPE  "T_OIC_INSTANCE_ROW" as object( 
    href          varchar2(400), 
    state         varchar2(80), 
    title         varchar2(200), 
    levels        number, 
    ownedby       varchar2(120), 
    priority      number, 
    createdby     varchar2(120), 
    processdn     varchar2(200), 
    processid     number, 
    createddate   timestamp with time zone, 
    processname   varchar2(80), 
    updateddate   timestamp with time zone, 
    processnumber number);
次に、それを使ったテープルを表すタイプT_OIC_INSTANCE_TABLEです。
CREATE OR REPLACE EDITIONABLE TYPE  "T_OIC_INSTANCE_TABLE" as table of t_oic_instance_row;
Webソース・モジュールを使ったパイプライン表関数は以下になります。Webソース・モジュールの静的識別子として、作成したWebソース・モジュールPROCESS_INSTANCE_LISTAPEX_EXEC.OPEN_WEB_SOURCE_QUERYに与えています。
  
タイプに指定されているカラムと、Webソース・モジュールから取り出した値の定義が正確に一致していること、また、日付については、タイプにはTIMESTAMP WITH TIME ZONEで定義されていて、それに設定する値はget_timestamp_tzで取り出していることを確認します。JSONの日付フォーマットがISO8601に従っている限り、タイムゾーン付き(そして、大抵はUTC)のデータとして取り出されます。ここが間違っていると、何のエラーも出力せず次回のapex_exec.next_rowの結果がfalseになりました。原因を見つけるのはとても大変でした。

パイプライン表関数が作成されていると、レポートには次のSQLを使用できます。
select * from table(get_instance_data)

レポートでの指定箇所は以下になります。

APEX_WEB_SERVICE.MAKE_REST_REQUEST_Bによるレポート


前のセクションで作成した、ふたつのタイプはそのまま流用します。APEX_WEB_SERVICE.MAKE_REST_REQUEST_Bを呼び出して取得したJSON文書は、Oracle Databaseに組み込みのJSONを処理するタイプを使って処理します。JSON_OBJECT_T, JSON_ARRAY_Tは、Oracle Database 12c 12.2より使用可能です。
 
作成したファンクションの使い方は、APEX_EXEC.OPEN_WEB_SOURCE_QUERYによるものとまったく同じです。

ここでも、日付データの取得はJSONの日付表現を考慮して、JSON_OBJECT_T.GET_TIMESTAMPを使用し、TIMESTAMP WITH TIME ZONEとして保存します。

Oracle Database 12c 12.2より以前のバージョンでは、データベース組み込みのJSONの機能を使えなかったりします。そのときはAPEX_JSONというOracle APEXが提供しているパッケージを代わりに使用することができます。また、その場合、JSON文書の取得にはBLOBを返却するAPEX_WEB_SERVICE.MAKE_REST_REQUEST_Bではなく、CLOBを返却するAPEX_WEB_SERVICE.MAKE_REST_REQUESTを使用します。JSON文書をBLOBで受け取って処理するのは、文字コードの変換(UTF-8 <-> UCS-2)が発生しないためCLOBより効率がよく、また、Oracle Database 20cではネイティブのJSON型のサポートが追加されるという発表もあります。興味があるかたは、Chris SaxonのOffice Hour(45:30より)で紹介されていますので、参考にしてみてください。