Webサービス、DATA-INTO、DATA-GEN パート1
最近私のところに来る「これについてちょっと教えてくれませんか」という質問の多くが、JSONに関するものです。質問者は、JSONをファイルで受け取っていたり、WebサービスからJSONを取得しなければならなかったり、クエリへの応答でJSONを生成しなければならなかったりしています。このような要求に対応する方法はたくさんありますが、RPGの命令DATA-INTOとDATA-GENは、面倒な作業の多くを肩代わりしてくれますし、一度基本を理解してしまえばとても簡単に使えます。
コツやテクニックをお伝えしていくこのシリーズは、まずはDATA-INTOとDATA-GENの両方を使用して、リクエストデータを生成し、簡単なREST Webサービスからの応答を処理するという基本的な事例から始めたいと思います。次回以降では、これらの命令でできることをもっと深掘りしたり、今回ご紹介するオペコード例を微調整してもっと複雑な事例に対応する方法をお知らせしていきます。RPGのオペコードに加え、Scott Klement氏のYAJLライブラリの中から特にYAJLDTAGENとYAJLINTOルーチンを使います。さらにWebサービスとの通信にはScott氏のHTTPAPIライブラリを使います。HTTPAPIはすばらしいツールであり、使い方については少し説明しますが、本シリーズで主目的となるものではありませんので、もっと知りたいという方はお知らせください。
まずWebサービスについてご説明しましょう。最初にご紹介する例では、JSONPlaceholderというウェブサイトで無償提供されている「フェイク」RESTサービスを利用します。このサイトを利用するのは、この記事を後で読んで、紹介されているコードを試してみようと思われた読者がいたとしても、今後1~2年間は存在し続けると思われるからです。また、使用するのにアカウントや認証コードが必要ないというところも気に入っています。そのため、私のコードをそのままコピーしていつでも実験してみることができるでしょう。
最初の例は、特定の執筆者(userid)のブログ記事(タイトル、ブログ投稿の内容が含まれる)を新規作成するWebサービスを使用します。このサービスは、サービスによって記事に割り当てられた一意のIDを含む確認を返します。このサービスはPOSTメソッドを使用し、データはHTTPリクエストボディにおいてJSONフォーマットで送信しなければなりません。実際のリクエストは次のようなフォーマットにする必要があります。
{
"title":"Post title goes here ...",
"body":"This is the post content ...",
"userId": 1
}
次に示すのが、Webサービスが返すJSON応答です。基本的には私が送信したデータと新規投稿に割り当てられた一意のIDが含まれているだけであるという点に注目してください。ここでまず押さえておきたいのは、使用しているのが「フェイク」サービスであるため、返されるIDはいつも同じで、データも保存されません。ですから、もしここでテストしてみて現実世界での挙動と違うように思っても心配することはありません。
{
"title": "Post title goes here ...",
"body": "This is the post content ...",
"userID": 1,
"id": 101
}
当然のことですが、今回のような単純なものの場合、コンテンツを自分でまとめてJSONリクエストを作成することもできます。しかし、それは面倒でありエラーの元にもなるため、DATA-GENを使ってその作業がどのように実行されるかを確認していきたいと思います。現実にはこんなに単純なリクエストを見ることは稀でしょう。
まず行うことは、必要なJSONデータをマッピングするデータ構造(DS)を定義することです。今回の場合、単純に3つの必須フィールドを決まった順番に並べた、一階層のみで構成されるDSです。このDSが、その後Scott氏のYAJLDTAGENルーチンの助けを借りてDATA-GENで処理され、必要なJSONが作成されます。DSの定義はこのようになります。
<A> Dcl-DS requestData Qualified Inz;
<B> title varchar(30); // Blog post title
body varchar(200); // Blog post content
userid int(5); // User ID of the post author
End-Ds;
DS内のフィールドの順番と名前は、作成されるJSONの要素の順番とスペリングと合致している必要があります。RPGは、TitleもTITLEもtitleも同一のフィールド名として扱いますが、JSONはそうではありません。JSONでは、名前の大文字と小文字は区別されます。そのため、必ず大文字・小文字の区別も含めて同じスペリングを使わなければなりません。
<A> DSでは、返されるDSと同じフィールド名を使用できるように、QUALIFIEDと指定する必要があります。これがどういう意味かはすぐにお分かりいただけるでしょう。
<B> DS内のフィールドは、Webサービスで要求される名前と同じ名前を持っています。titleとbodyは、どちらもvarcharとして定義されています。これは、フィールド内に大量の後続スペースを作成してしまわないためです。 userid (顧客番号だと思ってください)は、intと定義していますが、数値データ型なら何でも構いません。
DSが完成したら、DATA-GENにJSONを生成するように指示します。次のようになります。
// Generate JSON payload
<C> Data-Gen requestData
<D> %Data( request )
<E> %Gen( 'YAJL/YAJLDTAGEN' );
<C> データのソースとしてさきほどのrequestData DSを特定します。
<D> 生成されたJSONを受け取るターゲット・フィールドの名前を指定します。この例では、長さ200文字のvarcharフィールドです。
<E> %GENは、YAJLDTAGENジェネレータを使用することを示します。また、ジェネレータに渡す追加のオプション・パラメータを指定することもできますが、この例では必要ありません。今後別の例でご紹介することがあるでしょう。
この簡単な例では、これだけで十分です。コード全体(ここからダウンロードできます)を見ると、Webサービスに渡すデータを取得するためにDSPLYオペコードを使用していることが分かります。
JSONリクエストを構築できてしまえば、後はHTTPAPIを使用してWebサービスに送るだけです。私がそのためにここでHTTP_stringルーチンを使用するのは、使い方が最も簡単であることと、返されるデータで関心があるのはIDだけであることが理由です。Webサービスの呼び出しが失敗した場合、エラーがあるということだけ分かれば十分です。理由は重要ではありません。実際、このサービスに限って言えば、役立つことを教えてくれることはないでしょう。
// Perform POST operation to add the new item
<F> Monitor;
<G> response = HTTP_string( 'POST' :
<H> url:
<I> request:
<J> 'application/json');
エラー応答コードが返されるのではなく、HTTP_stringでは例外がスローされるため、これをMONITORグループで実行するのが通常です <F>。どのようなエラーが検知されても、ON-ERRORコードで終了する原因になります。これはコード例の全体を観察すると書かれているのが分かるはずです。
<G> 変数responseがデータの受信側となっていること、そしてこのWebサービスがPOST HTTPメソッドを使用していることが書かれています。
<H> 次に、使用しているサービスのURLを格納する変数を特定します。
<I> 次のパラメータは、requestがHTTPリクエストボディを格納する変数であることを示します。この場合、DATA_GENが構築して<C>で返してきたJSONデータを指します。
<J> 最後のパラメータは、リクエストボディにJSONが含まれていることを示しています。Webサービスが入力タイプとしてXMLを追加でサポートしている場合は、XML形式でリクエストを作成し、これを'application/xml'としてコード化することができます。受け入れ可能なデータの形式を決定するのは、Webサービスです。この場合は、JSONが唯一の選択肢となります。
HTTP_stringが正常に実行されると、response変数にWebサービスからのJSONデータが格納されます。これをDATA-INTOで処理することができます。DATA-GENの場合と同様に、DSを定義する必要があります。今回は、JSON応答ストリームから書き出された値を受け取ります。私が使用しているDSは次の通りです。
// DS to receive response data extracted by DATA-INTO
Dcl-DS responseData Qualified Inz;
<K> id int(5); // Identifying ID for the post
title varchar(30); // These three should contain same
body varchar(200); // values as we submitted
userid int(5); // on the original POST request
End-Ds;
ご覧の通り、このDSは先ほどJSONリクエストを生成するために使用したものと基本的に同じです。唯一違うのは、Webサービスが新規投稿を一意的に識別するために生成したidフィールドが追加されていることです <K>。DSが完成して、Webサービスからの応答があったら、DATA-INTOを使用してJSONを解析し、RPGコードで処理できるようにDSに代入します。
ここで、私が故意にidフィールドをJSON内とは違う場所に定義したことに留意してください。これは、DATA-INTOは、XML-INTOの場合と全く同様に、マッチするのにフィールドの順番を揃える必要はないことを実証しています。値はフィールド名に従って格納されているのであり、順番は関係ありません。
<L> responseData DSを、DATA-INTO操作のターゲットに指定しています。
<M> %DATAは、処理されるデータのソースを識別します。この例では、<G>のHTTP_string操作の結果であるフィールドresponseを示しています。'case=any'オプションは、JSONのフィールド名を大文字に変換してから、ターゲットのDSのフィールド名と照合するように指定します。XML-INTOに詳しい方は、このオプションが%XML BIFの同じオプションと実質同じであることに気づいたでしょう。これはDATA-INTOで使用されるオプションの多くでも同様であり、今後ご紹介する例でご覧いただけることでしょう。
<N> 最後に、JSONの処理にYAJLINTOパーサーを使用することを示すため、%PARSERを使います。%GENと同様に、%PARSERにも追加のパラメータを指定することができますが、これについては今後の例でご紹介します。
// All OK if we get this far so extract data and report
<L> Data-Into responseData
<M> %Data( response : 'case=any' )
<N> %PARSER( 'YAJL/YAJLINTO' ); // Extracted data now in responseData
これで以上です。
最後に
すでに読者のみなさんの頭に浮かんでいるかもしれない2つの質問に少しだけお答えしようと思います。
- 「SQLを使えばここに書かれているようなJSON解析はすべてできるのではないでしょうか?」
- 「IBMは統合Webサービスのソフトウェアの一環としてWebサービスクライアントを提供しているのでしょうか?」
1つ目の質問には「イエスですが、その説明は誰か別の人に任せたい」と回答します。とはいえ、付け足すとすれば、SQLは今回のような単純な例では十分簡単に使えると思いますが、より複雑なケースでは、非常に複雑で維持も大変になる場合が多いということです。近年広まっているように思える「問題が何であろうと、その答えはSQLだ」という考え方には、私は感心しません。この領域においてRPGは非常に優秀で速いのです。
2つ目の質問には「イエスですが、私は好きではない」という回答になります。細かく説明すれば長い話になってしまいますが、私にとってRPGのような「感じ」がしないのです。HTTPAPIはPRGユーザーが書いたもので、結果が目に見えます。IBMが提供しているのはUnix系Apacheツールのポートで、システムコンポーネントとのインターフェイスのためにCスタブを作成してコンパイルしなければなりません。また、HTTPAPIには膨大な数のユーザーがいて、コツやテクニックに関して意見交換をする活発なフォーラムがあります。これはIBMの提供するものにはありません。これは私の個人的な見方であって、違った見方もあるかもしれません。
次回
本シリーズで次にご紹介するコツは、GETメソッドを使ってブログ記事の情報を取得する例を見ていきます。
質問や、この幅広い領域の中で今後もっと深掘りしてほしいところがあれば、ぜひお知らせください。