はじめに
今回もAPI Connect - z/OS Connect連携を想定します。
z/OS Connectは既存のz/OS上の資産(CICSアプリケーションやIMSトランザクションなど)をサービスとして外部に公開する仕組みを提供しています。基本的には既存資産がベースになるため、既存のサービスのインターフェースをそのまま公開すると使い勝手が良くない場合があります。例えば、フィールドの名前や構造が分かりにくいといった場合です。
API Connect, z/OS Connectではそれぞれ既存のサービスのAPIを公開/管理するための仕組みを提供していますが、その際に既存インターフェースをなるべく使いやすい形に変えて公開することが望まれます(ここでは理想のインターフェースに変換することを"浄化"と表現します)。
z/OS Connect, API Connectそれぞれでインターフェースを浄化するためにどのようなことができるのかを具体的に見ていきます。
関連記事
IBM API Connect 関連メモ - (1)既存REST APIの取り込み
IBM API Connect 関連メモ - (2)API Connectツールキットを使用したNode.jsアプリ作成(Loopback)
IBM API Connect 関連メモ - (3)カタログ、開発者ポータルの構成
IBM API Connect 関連メモ - (4)各種APIの管理
IBM API Connect 関連メモ - (5)Secure Gateway経由でのz/OS Connectとの連携
IBM API Connect 関連メモ - (6)API Connect-z/OS Connect間TLS接続
IBM API Connect 関連メモ - (7)z/OS Connect 基本認証 + ID伝播
IBM API Connect 関連メモ - (8)z/OS Connect 連携におけるインターフェースの浄化
IBM API Connect 関連メモ - (9)z/OS Connect 連携まとめ
全体像
基本的には上のような流れで既存のI/Fが公開用のI/Fに変換されていくことになります。
まず、公開したい既存のアプリケーション(ここではCICS-COBOLのCOMMAREAアプリケーション)が切り出されたとしましょう。COMMAREAアプリケーションはその名前の通り"COMMAREA"と呼ばれるデータエリアでデータの受け渡しを行うため、入出力データの構造はCOMMAREAの構造を示すCopyBookとして定義されます。
z/OS Connectでは、ターゲットのプログラムの入出力データ構造体を示すCopyBookを取り込んでそれをJSONフォーマットとの相互変換を行う機能を提供してくれます。つまり、CopyBookの定義に従って、それにに相当するJSON Schemaを自動生成してくれます。
ここで、元になるCopyBookというのは当然COBOLの世界で定義されたものですので、外部接続やRESTやJSONの考え方を意識した定義にはなっていないことが多々あります。例えばフィールドのネーミングや構造についてはローカルでCOBOLの世界で実施する分には問題ないかもしれませんが、外部公開して汎用的に使われることを想定すると望ましくない定義になっている可能性があります。そこで、公開していく過程でI/Fの浄化をしていくことになりますが、I/Fに対して各フェーズでどのような操作ができるかはそれぞれ違っています。
以下細かく見ていきます。
1.COBOLレベルでの変換
Copybookをz/OS Connectのツールに取り込む際、上に上げたように既存I/FとしてのCopyBookの定義内容がそのまま公開するには望ましくない場合があります。そこで、対象のCopyBookをツールに取り込む前にある程度都合のよいように変更してしまうということが考えられます。
COBOLの構造体定義はJSONやXMLと違ってメモリ上に確保さえれるエリアを意識した定義方法となっています。すなわち各フィールドは開始位置からのオフセットと長さが重要な要素となっています。例えば以下のような顧客情報を表すCopyBookを考えてみます。
ターゲットとなるアプリケーションのCOMMAREA構造体が上の図左側のCOMMAREA01のように定義されていたとします。
これをそのままツールに取り込むと、想定されるJSONデータは以下のようなフィールド名、構造になります。
{
"COMMAREA01": {
"CUST-ID":"A0001",
"CUST-NAME": "Taro Yamada",
"ADDRESSLINE": "Mihama-ku Nakase 1-1",
"CITY":"Chiba-shi",
"STATE":"Chiba-ken",
"ZIP":"111-2222",
"COUNTRY":"Japan",
"PHONE":"050-1111-2222"
}
}
これをフィールド名や階層を変えて上の図右側のCOMMAREA02のように定義し直してみます。このように、実体であるメモリ上展開されるデータ構造に影響を与えない範囲でCopyBook上の定義を変更することが可能です(addressLineの部分は1つのフィールドを前後20バイトずつに分割しているのでそれでアプリ上問題ないことが前提ですが)。
このように変更したCopyBookをツールに取り込むと、想定されるJSONデータは以下のようなフィールド名、構造になります。
{
"COMMAREA02": {
"customerID":"A0001",
"customerName": "Taro Yamada",
"address": {
"addressLine1":"Mihama-ku",
"addressLine2":"Nakase 1-1",
"city":"Chiba-shi",
"state":"Chiba-ken",
"zip":"111-2222",
"country":"Japan"
},
"phone":"050-1111-2222"
}
}
このように、ツールで変換される形態を想定して、取り込むCopyBookを事前に都合の良いように変換しておくことが考えられます。
ここで行えるのは、フィールド名の変更、限られた範囲での階層構造の変更の辺りです。あとはアプリケーションの作り次第ですが、フィールドの結合や分割が行えるケースもあるかと思います。
また、この変換を行うためのツールが用意されている訳ではありませんのでこの部分は作りこみが必要になりますが、ここは純粋な文字列操作を行うだけですので、スクリプトで比較的容易に実装可能と考えられます。
2.z/OS Connectレベルでの変換(API toolkit)
z/OS Connect提供のAPI Toolkitでは、ターゲットアプリケーションのI/FであるCOMMAREA構造体のCopyBookを元にREST API化することができますが、ここでは2段階のI/Fの変換が行われます。
2-1. Copybookの取り込み (CopyBook => 個別サービス定義)
まず、CopyBookを取り込んで"サービス"として認識させるフェーズがあります。
例えば、カタログから商品の在庫確認をしたり、オーダーするサービスを提供することを想定すると、まず在庫確認用のサービスや、オーダー用のサービスをそれぞれ定義していくことになります。
ここでは、z/OS Connect EE サービス・プロジェクト というプロジェクトを作成し、既存I/Fの入出力のデータ構造をCopyBookから読み取り、サービス・インターフェース・エディターで編集します。
ここではフィールド毎に以下のような操作ができます。
- フィールド名の変更
- I/Fとして公開するかどうかの選択
- デフォルト値の設定
- コード変換有無
データの階層構造は入力とするCopyBookに従うので、この段階で階層構造をカスタマイズすることはできません。
2-2. RestfulService作成 (個別サービス定義 => RESTful API)
2-1の段階ではHTTPでサービス呼び出しを行える状態になりますが、これだといわゆるRPCベースの呼び出しが行えるようになっただけで、RESTful APIとは呼べません。
在庫確認のサービスや、オーダーのサービスなど、個々のサービス定義を行ったら、次にそれら関連する操作をRESTfulサービスとしてまとめるフェーズがあります。
ここでは、z/OS Connect EE API プロジェクトというプロジェクトを作成し、2-1で作成した個々のサービスをまとめてRESTfulサービスとして定義していきます。
ここでは、個々のサービス呼び出しに対して、以下のような操作が行えます。
- 個々のサービス呼び出しに対してURIパスとHTTPメソッド(GET/POST/PUT/DELETE/PATCH/HEAD)の割り当て(※1)
- HTTPレスポンスコード毎のI/Fマッピング (※2)
- 要求、応答(HTTPレスポンス毎)のI/Fマッピング
- 固定値のアサイン
- I/Fとして公開するかどうかの選択
- URIパス、queryString、HTTPヘッダーとJSONデータへのマッピング(※3)
補足
※1
商品一覧紹介のリクエストであればGETメソッドで/listというURIパスを指定した場合に商品一覧用のサービスを呼び出す、オーダーのリクエストであればPOSTメソッドで/placeOrderというURIパスを指定してオーダー内容をBodyにJSONデータを指定しオーダー用のサービスを呼び出す、といったように、サービス用途に合わせてURIパス、メソッドを定義します。
※2
一般的にはHTTPレスポンスコード(200: 正常応答、500: Internal Server Errorなど)によって返されるメッセージのデータ構造が異なることになりますので、レスポンスコードに応じてそれぞれ構造をどのように公開するかを定義します。
1つのレスポンスコードに対して複数の応答用データ構造のパターンが存在する場合はAPI Toolkitでは対応できません。例えば、会員IDをキーに会員情報紹介をするためのサービスがあった場合、通常会員の場合の結果と特別会員の場合の結果のデータ構造が全く違うようなケースの場合は対応できません(データ構造は共通化できて一方だけ特定のフィールドがブランクになる、といったような場合はOKですが)。応答用のデータ構造が複数パターンがある場合は別のサービスとして定義する、ラッパーのプログラムをかませてI/Fの共通化をはかる、などの対応が必要になります。
※3
CopyBookを取り込んで入出力データ構造を認識させると、基本、それらは全てMessage Bodyに含まれるJSONデータとして取り扱われることになります。
しかし、REST APIでは、入力データはBody部分のJSONだけでなく、URIパス、QueryString、HTTPヘッダーとしても与えることができます。APIの設計として変数(パラメーター)をどのように与えるかは選択の余地があります。そこで、URIパス、QueryString、HTTPヘッダーの情報をMessage Bodyのフィールドにマッピングするということもできるようになっています(URIパスとQueryStringは要求時のみ)。
例えば以下のようなイメージです。
z/OS ConnectではこのようなI/Fの変換を行って公開用のI/Fを定義していき、最終的にSwagger文書を生成させることができます。
3. API Connectレベルでの変換(アセンブル)
z/OS Connectでは上で見てきた通り既存のz/OS上のサービスをRESTful API化するための支援機能が提供されています。上で挙げたカスタマイズで足りるケースもあるとは思いますが、そこで行えるインターフェースの浄化のためのカスタマイズは限定的です。
API ConnectではAPI DesignerのUIを用いて、APIが呼び出された時に実行される処理を組み込むことができます("アセンブル"と呼ばれます)。
以下のようなブラウザのインターフェースでGUIベースで各種ポリシー、および、ロジック構成体と呼ばれるオブジェクトを組み合わせて様々な処理を行わせることができます。
参考: API のポリシーおよびロジック構成体
これを利用すれば様々な処理を柔軟に組み合わせることができますので、ドラスティックに入出力データ構造を変更してそのマッピングを定義したり、複数のAPIを組み合わせるなど、かなり柔軟にカスタマイズを行うことができます。
ここで定義された"アセンブル"は、最終的にはyamlファイルに落とし込まれ、ソースタブで確認することができます。
ここでは、単純ケースとして、z/OS Connectで出力したSwagger文書(外部公開用I/F①)を取り込んだのち、新たな外部公開用I/F②を定義しなおして、その変換をアセンブルで実装する流れを具体的に試してみたいと思います。
z/OS Connectで公開されたSwagger文書をAPI Connectに取り込む手順は以下で実施済みなのでここでは割愛します。
API Connect からSecure Gateway経由でのバックエンド・サービス連携
カスタマイズ前のI/F確認
まず、前提としてz/OS Connectで生成した以下のSwagger文書を取り込んだとします。
公開用I/F①
{
"swagger" : "2.0",
"info" : {
"description" : "",
"version" : "1.0.0",
"title" : "cicsstockrestfulservice"
},
"host" : "localhost:8080",
"basePath" : "/showroomdemo/v1/isecics",
"schemes" : [ "https", "http" ],
"consumes" : [ "application/json" ],
"produces" : [ "application/json" ],
"paths" : {
"/order" : {
"post" : {
"tags" : [ "cicsstockrestfulservice" ],
"operationId" : "postCICSStockOrderService",
"parameters" : [ {
"in" : "body",
"name" : "postCICSStockOrderService_request",
"description" : "request body",
"required" : true,
"schema" : {
"$ref" : "#/definitions/postCICSStockOrderService_request"
}
} ],
"responses" : {
"400" : {
"description" : "Bad Request",
"schema" : {
"$ref" : "#/definitions/postCICSStockOrderService_response_400"
}
},
"200" : {
"description" : "OK",
"schema" : {
"$ref" : "#/definitions/postCICSStockOrderService_response_200"
}
}
}
}
},
"/stock/{groupCode}" : {
"get" : {
"tags" : [ "cicsstockrestfulservice" ],
"operationId" : "getCICSStockCheckService",
"parameters" : [ {
"name" : "groupCode",
"in" : "path",
"required" : true,
"type" : "string",
"maxLength" : 4
} ],
"responses" : {
"400" : {
"description" : "Bad Request",
"schema" : {
"$ref" : "#/definitions/getCICSStockCheckService_response_400"
}
},
"200" : {
"description" : "OK",
"schema" : {
"$ref" : "#/definitions/getCICSStockCheckService_response_200"
}
}
}
}
}
},
"definitions" : {
"getCICSStockCheckService_response_400" : {
"type" : "object",
"properties" : {
"stockCheck" : {
"type" : "object",
"properties" : {
"returnCode" : {
"type" : "integer",
"minimum" : 0,
"maximum" : 99
},
"responseMessage" : {
"type" : "string",
"maxLength" : 80
}
}
}
}
},
"getCICSStockCheckService_response_200" : {
"type" : "object",
"properties" : {
"stockCheck" : {
"type" : "object",
"properties" : {
"returnCode" : {
"type" : "integer",
"minimum" : 0,
"maximum" : 99
},
"responseMessage" : {
"type" : "string",
"maxLength" : 80
},
"itemList" : {
"type" : "array",
"items" : {
"type" : "object",
"properties" : {
"itemId" : {
"type" : "string",
"maxLength" : 8
},
"itemName" : {
"type" : "string",
"maxLength" : 66
},
"itemPrice" : {
"type" : "integer",
"minimum" : 0,
"maximum" : 9999999
},
"stored" : {
"type" : "string",
"maxLength" : 1
},
"shipFrom" : {
"type" : "string",
"maxLength" : 64
}
}
},
"maxItems" : 6,
"minItems" : 6
}
}
}
}
},
"postCICSStockOrderService_request" : {
"type" : "object",
"properties" : {
"stockOrder" : {
"type" : "object",
"properties" : {
"orderList" : {
"type" : "array",
"items" : {
"type" : "object",
"properties" : {
"itemId" : {
"type" : "string",
"maxLength" : 8
},
"itemQuantity" : {
"type" : "integer",
"minimum" : 0,
"maximum" : 9999999
}
}
},
"maxItems" : 6,
"minItems" : 6
}
}
}
}
},
"postCICSStockOrderService_response_400" : {
"type" : "object",
"properties" : {
"stockOrder" : {
"type" : "object",
"properties" : {
"returnCode" : {
"type" : "integer",
"minimum" : 0,
"maximum" : 99
},
"responseMessage" : {
"type" : "string",
"maxLength" : 80
}
}
}
}
},
"postCICSStockOrderService_response_200" : {
"type" : "object",
"properties" : {
"stockOrder" : {
"type" : "object",
"properties" : {
"returnCode" : {
"type" : "integer",
"minimum" : 0,
"maximum" : 99
},
"responseMessage" : {
"type" : "string",
"maxLength" : 80
},
"stockOfItemList" : {
"type" : "array",
"items" : {
"type" : "object",
"properties" : {
"itemId" : {
"type" : "string",
"maxLength" : 8
},
"stockYN" : {
"type" : "string",
"maxLength" : 1
},
"stockNum" : {
"type" : "integer",
"minimum" : 0,
"maximum" : 9999999
}
}
},
"maxItems" : 6,
"minItems" : 6
}
}
}
}
}
}
}
これを取り込んでデータ構造はそのままに単純なアセンブルを作成し、TLSサーバー認証 + Basic認証の構成を行っているものとします(手順は以下の通り)。
API Connect からSecure Gateway経由でのバックエンド・サービス連携
SAF Basic認証 + TLSサーバー認証(HTTPS)
この段階でのAPI Connect上のAPI定義は以下のようになっています。
API Connect上のAPI定義
swagger: '2.0'
info:
description: ''
version: 1.0.0
title: cicsstockrestfulservice
x-ibm-name: cicsstockrestfulservice
host: 'localhost:8080'
basePath: /showroomdemo/v1/isecics
schemes:
- https
consumes:
- application/json
produces:
- application/json
paths:
/order:
post:
tags:
- cicsstockrestfulservice
operationId: postCICSStockOrderService
parameters:
- in: body
name: postCICSStockOrderService_request
description: request body
required: true
schema:
$ref: '#/definitions/postCICSStockOrderService_request'
responses:
'200':
description: OK
schema:
$ref: '#/definitions/postCICSStockOrderService_response_200'
'400':
description: Bad Request
schema:
$ref: '#/definitions/postCICSStockOrderService_response_400'
'/stock/{groupCode}':
get:
tags:
- cicsstockrestfulservice
operationId: getCICSStockCheckService
parameters:
- name: groupCode
in: path
required: true
type: string
maxLength: 4
responses:
'200':
description: OK
schema:
$ref: '#/definitions/getCICSStockCheckService_response_200'
'400':
description: Bad Request
schema:
$ref: '#/definitions/getCICSStockCheckService_response_400'
definitions:
getCICSStockCheckService_response_400:
type: object
properties:
stockCheck:
type: object
properties:
returnCode:
type: integer
minimum: 0
maximum: 99
responseMessage:
type: string
maxLength: 80
getCICSStockCheckService_response_200:
type: object
properties:
stockCheck:
type: object
properties:
returnCode:
type: integer
minimum: 0
maximum: 99
responseMessage:
type: string
maxLength: 80
itemList:
type: array
items:
type: object
properties:
itemId:
type: string
maxLength: 8
itemName:
type: string
maxLength: 66
itemPrice:
type: integer
minimum: 0
maximum: 9999999
stored:
type: string
maxLength: 1
shipFrom:
type: string
maxLength: 64
maxItems: 6
minItems: 6
postCICSStockOrderService_request:
type: object
properties:
stockOrder:
type: object
properties:
orderList:
type: array
items:
type: object
properties:
itemId:
type: string
maxLength: 8
itemQuantity:
type: integer
minimum: 0
maximum: 9999999
maxItems: 6
minItems: 6
postCICSStockOrderService_response_400:
type: object
properties:
stockOrder:
type: object
properties:
returnCode:
type: integer
minimum: 0
maximum: 99
responseMessage:
type: string
maxLength: 80
postCICSStockOrderService_response_200:
type: object
properties:
stockOrder:
type: object
properties:
returnCode:
type: integer
minimum: 0
maximum: 99
responseMessage:
type: string
maxLength: 80
stockOfItemList:
type: array
items:
type: object
properties:
itemId:
type: string
maxLength: 8
stockYN:
type: string
maxLength: 1
stockNum:
type: integer
minimum: 0
maximum: 9999999
maxItems: 6
minItems: 6
x-ibm-configuration:
enforced: true
testable: true
phase: realized
cors:
enabled: true
assembly:
execute:
- operation-switch:
title: operation-switch
case:
- operations:
- getCICSStockCheckService
execute:
- invoke:
title: invoke-stockCheck
timeout: 60
verb: GET
cache-response: protocol
cache-ttl: 900
stop-on-error:
- null
version: 1.0.0
target-url: 'https://xxxxx.securegateway.appdomain.cloud:15236/showroomdemo/v1/isecics/stock/$(request.parameters.groupCode)'
secure-gateway: false
tls-profile: zosconnecttls01
username: ZCON01
password: xxxxxxxx
- operations:
- postCICSStockOrderService
execute:
- invoke:
title: invoke-stockOrder
timeout: 60
verb: POST
cache-response: protocol
cache-ttl: 900
stop-on-error:
- null
version: 1.0.0
target-url: 'https://xxxxx.securegateway.appdomain.cloud:15236/showroomdemo/v1/isecics/order'
tls-profile: zosconnecttls01
username: ZCON01
password: xxxxxxxx
otherwise: []
version: 1.0.0
catch: []
securityDefinitions:
Client Secret:
type: apiKey
description: ''
in: header
name: X-IBM-Client-Secret
Client ID:
type: apiKey
description: ''
in: header
name: X-IBM-Client-Id
security:
- Client Secret: []
Client ID: []
※z/OS Connectが生成するSwagger文書はjson形式ですが、API Connectに取り込まれるとyaml形式で保持されます。
さて、ここでカスタマイズ前の元のI/Fを確認しておきます。ここでは/orderのURIパスで呼び出されるサービスに着目してみます。
カスタマイズ前/order のI/F
/order:
post:
tags:
- cicsstockrestfulservice
operationId: postCICSStockOrderService
parameters:
- in: body
name: postCICSStockOrderService_request
description: request body
required: true
schema:
$ref: '#/definitions/postCICSStockOrderService_request'
responses:
'200':
description: OK
schema:
$ref: '#/definitions/postCICSStockOrderService_response_200'
'400':
description: Bad Request
schema:
$ref: '#/definitions/postCICSStockOrderService_response_400'
postCICSStockOrderService_request:
type: object
properties:
stockOrder:
type: object
properties:
orderList:
type: array
items:
type: object
properties:
itemId:
type: string
maxLength: 8
itemQuantity:
type: integer
minimum: 0
maximum: 9999999
maxItems: 6
minItems: 6
postCICSStockOrderService_response_200:
type: object
properties:
stockOrder:
type: object
properties:
returnCode:
type: integer
minimum: 0
maximum: 99
responseMessage:
type: string
maxLength: 80
stockOfItemList:
type: array
items:
type: object
properties:
itemId:
type: string
maxLength: 8
stockYN:
type: string
maxLength: 1
stockNum:
type: integer
minimum: 0
maximum: 9999999
maxItems: 6
minItems: 6
リクエストデータのカスタマイズ
公開用I/F②入力データ構造定義
まずyamlのソース画面で、postCICSStockOrderService_request の定義にならって、新たにpostCICSStockOrderService_request_externalという構造体を追加し、リクエスト用のスキーマとして指定します。
/order:
post:
tags:
- cicsstockrestfulservice
operationId: postCICSStockOrderService
parameters:
- in: body
name: postCICSStockOrderService_request
description: request body
required: true
schema:
$ref: '#/definitions/postCICSStockOrderService_request_external'
responses:
'200':
description: OK
schema:
$ref: '#/definitions/postCICSStockOrderService_response_200'
'400':
description: Bad Request
schema:
$ref: '#/definitions/postCICSStockOrderService_response_400'
postCICSStockOrderService_request_external:
type: object
properties:
stockOrder:
type: object
properties:
dummyData01Ex:
type: string
maxLength: 8
testAuth:
type: string
maxLength: 30
orderListEx:
type: array
items:
type: object
properties:
itemId:
type: string
maxLength: 8
itemQuantity:
type: integer
minimum: 0
maximum: 9999999
maxItems: 6
minItems: 6
ここでは、dummyData01Exというフィールドと、testAuthというフィールドを新たに追加しています。
dummyData01Exというのはその名前の通りダミーのフィールドです。ログ出力用などバックエンドには伝播させないフィールドと意味合いで追加しています。
testAuthというフィールドは、バックエンドのBasic認証用に、HTTPヘッダー "Authorization" に設定する値(userid:passwordをBase64Encodeした値)を受け渡すためのフィールドとして定義しています。実際にJSONデータにこのようにuserid,password情報を設定することは無いと思いますが、ここではアセンブルの手順/動作確認のためにこのようなマッピングを想定しています。
また、orderListは単純にorederListExという名前に変更しているだけです。実質的な意味はあまりありません。
入力データ構造のマッピング
新たに定義した入力データ構造(公開用I/F②)と、バックエンドアクセス用の入力データ構造(公開用I/F①)のマッピングを行います。これはアセンブル画面でmapというポリシーで実装します。以下のようなイメージでマッピング情報を定義していきます。
「+入力」ボタンを押して以下のように指定
コンテキスト変数: request.body
名前: 任意の名前
コンテンツ・タイプ: application/json
定義: #/definitions/postCICSStokOrderService_request_external (上で新規に定義した構造体)
「+出力」ボタンを2回押して、2つの出力用の定義を以下のように指定
<1つめ>
コンテキスト変数: message.body
名前: 任意の名前
コンテンツ・タイプ: application/json
定義: #/definitions/postCICSStokOrderService_request (元の入力用データ構造)
<2つめ>
コンテキスト変数: message.headers.Authorization
名前: 任意の名前
コンテンツ・タイプ: none
定義: string
入力、出力それぞれで指定したデータ定義が表示されるので、対応するフィールドを線でつないでマッピングを行います。
ここでは、orderListEx => orderList, testAuth => HTTPヘッダー"Authorization"にマッピングしています。(dummyData01Exはどこにもマッピングせず捨てています)
レスポンスデータのカスタマイズ
公開用I/F②出力データ構造定義
yamlのソース画面で、postCICSStockOrderService_request の定義にならって、新たにpostCICSStockOrderService_request_externalという構造体を追加し、リクエスト用のスキーマとして指定します。
/order:
post:
tags:
- cicsstockrestfulservice
operationId: postCICSStockOrderService
parameters:
- in: body
name: postCICSStockOrderService_request
description: request body
required: true
schema:
$ref: '#/definitions/postCICSStockOrderService_request_external'
responses:
'200':
description: OK
schema:
$ref: '#/definitions/postCICSStockOrderService_response_200_external'
'400':
description: Bad Request
schema:
$ref: '#/definitions/postCICSStockOrderService_response_400'
postCICSStockOrderService_response_200_external:
type: object
properties:
stockOrder:
type: object
properties:
returnCodeEx:
type: integer
minimum: 0
maximum: 99
responseMessageEx:
type: string
maxLength: 80
stockOfItemList:
type: array
items:
type: object
properties:
itemId:
type: string
maxLength: 8
stockYN:
type: string
maxLength: 1
stockNum:
type: integer
minimum: 0
maximum: 9999999
maxItems: 6
minItems: 6
ここでは、returnCodeの代わりにreturnCodeEx, responseMessageの代わりにresponseMessageExというフィールドを定義しています(名前を変えているだけ)。実質的な意味はあまりありません。
出力データ構造のマッピング
新たに定義した出力データ構造(公開用I/F②)と、バックエンドアクセス用の出力データ構造(公開用I/F①)のマッピングを行います。アセンブル画面でmapというポリシーで実装します。以下のようなイメージでマッピング情報を定義していきます。
「+入力」ボタンを押して以下のように指定
コンテキスト変数: message.body
名前: 任意の名前
コンテンツ・タイプ: application/json
定義: #/definitions/postCICSStokOrderService_response_200 (元の出力用データ構造)
「+出力」ボタンを押して以下のように指定
コンテキスト変数: message.body
名前: 任意の名前
コンテンツ・タイプ: application/json
定義: #/definitions/postCICSStokOrderService_response_200_external (上で新規に定義した構造体)
入力、出力それぞれで指定したデータ定義が表示されるので、対応するフィールドを線でつないでマッピングを行います。
ここでは、名前を変更しているだけなので、それぞれ対応するフィールドを線で結びます。階層になっているフィールドは親の項目名を結べばOkのようです。
API定義まとめ
上で設定したアセンブルの定義はyamlのAPI定義に落とし込まれます。
最終的に生成されるyamlは以下のようになります。
API定義
swagger: '2.0'
info:
description: ''
version: 1.0.0
title: test01
x-ibm-name: test01
host: 'localhost:8080'
basePath: /test01/v1/isecics
schemes:
- https
consumes:
- application/json
produces:
- application/json
paths:
/order:
post:
tags:
- test01
operationId: postCICSStockOrderService
parameters:
- in: body
name: postCICSStockOrderService_request
description: request body
required: true
schema:
$ref: '#/definitions/postCICSStockOrderService_request_external'
responses:
'200':
description: OK
schema:
$ref: '#/definitions/postCICSStockOrderService_response_200_external'
'400':
description: Bad Request
schema:
$ref: '#/definitions/postCICSStockOrderService_response_400'
'/stock/{groupCode}':
get:
tags:
- test01
operationId: getCICSStockCheckService
parameters:
- name: groupCode
in: path
required: true
type: string
maxLength: 4
responses:
'200':
description: OK
schema:
$ref: '#/definitions/getCICSStockCheckService_response_200_external'
'400':
description: Bad Request
schema:
$ref: '#/definitions/getCICSStockCheckService_response_400'
definitions:
getCICSStockCheckService_response_400:
type: object
properties:
stockCheck:
type: object
properties:
returnCode:
type: integer
minimum: 0
maximum: 99
responseMessage:
type: string
maxLength: 80
getCICSStockCheckService_response_200:
type: object
properties:
stockCheck:
type: object
properties:
returnCode:
type: integer
minimum: 0
maximum: 99
responseMessage:
type: string
maxLength: 80
itemList:
type: array
items:
type: object
properties:
itemId:
type: string
maxLength: 8
itemName:
type: string
maxLength: 66
itemPrice:
type: integer
minimum: 0
maximum: 9999999
stored:
type: string
maxLength: 1
shipFrom:
type: string
maxLength: 64
maxItems: 6
minItems: 6
getCICSStockCheckService_response_200_external:
type: object
properties:
stockCheck:
type: object
properties:
returnCodeEx:
type: integer
minimum: 0
maximum: 99
responseMessageEx:
type: string
maxLength: 80
itemListEx:
type: array
items:
type: object
properties:
itemId:
type: string
maxLength: 8
itemName:
type: string
maxLength: 66
itemPrice:
type: integer
minimum: 0
maximum: 9999999
stored:
type: string
maxLength: 1
shipFrom:
type: string
maxLength: 64
maxItems: 6
minItems: 6
postCICSStockOrderService_request:
type: object
properties:
stockOrder:
type: object
properties:
orderList:
type: array
items:
type: object
properties:
itemId:
type: string
maxLength: 8
itemQuantity:
type: integer
minimum: 0
maximum: 9999999
maxItems: 6
minItems: 6
postCICSStockOrderService_request_external:
type: object
properties:
stockOrder:
type: object
properties:
dummyData01Ex:
type: string
maxLength: 8
testAuth:
type: string
maxLength: 30
orderListEx:
type: array
items:
type: object
properties:
itemId:
type: string
maxLength: 8
itemQuantity:
type: integer
minimum: 0
maximum: 9999999
maxItems: 6
minItems: 6
postCICSStockOrderService_response_400:
type: object
properties:
stockOrder:
type: object
properties:
returnCode:
type: integer
minimum: 0
maximum: 99
responseMessage:
type: string
maxLength: 80
postCICSStockOrderService_response_200:
type: object
properties:
stockOrder:
type: object
properties:
returnCode:
type: integer
minimum: 0
maximum: 99
responseMessage:
type: string
maxLength: 80
stockOfItemList:
type: array
items:
type: object
properties:
itemId:
type: string
maxLength: 8
stockYN:
type: string
maxLength: 1
stockNum:
type: integer
minimum: 0
maximum: 9999999
maxItems: 6
minItems: 6
postCICSStockOrderService_response_200_external:
type: object
properties:
stockOrder:
type: object
properties:
returnCodeEx:
type: integer
minimum: 0
maximum: 99
responseMessageEx:
type: string
maxLength: 80
stockOfItemList:
type: array
items:
type: object
properties:
itemId:
type: string
maxLength: 8
stockYN:
type: string
maxLength: 1
stockNum:
type: integer
minimum: 0
maximum: 9999999
maxItems: 6
minItems: 6
x-ibm-configuration:
enforced: true
testable: true
phase: realized
cors:
enabled: true
assembly:
execute:
- operation-switch:
title: operation-switch
case:
- operations:
- getCICSStockCheckService
execute:
- invoke:
title: invoke-stockCheck
timeout: 60
verb: GET
cache-response: protocol
cache-ttl: 900
stop-on-error:
- null
version: 1.0.0
target-url: 'https://xxxxx.securegateway.appdomain.cloud:15236/showroomdemo/v1/isecics/stock/$(request.parameters.groupCode)'
secure-gateway: false
tls-profile: zosconnecttls01
username: ZCON01
password: xxxxxxxx
- map:
title: map stock response
inputs:
message_body:
schema:
$ref: '#/definitions/getCICSStockCheckService_response_200'
variable: message.body
content: application/json
outputs:
message_body_ex:
schema:
$ref: '#/definitions/getCICSStockCheckService_response_200_external'
variable: message.body
content: application/json
actions:
- set: message_body_ex.stockCheck.returnCodeEx
from: message_body.stockCheck.returnCode
- set: message_body_ex.stockCheck.responseMessageEx
from: message_body.stockCheck.responseMessage
- set: message_body_ex.stockCheck.itemListEx
from: message_body.stockCheck.itemList
version: 1.0.0
options: {}
- operations:
- postCICSStockOrderService
execute:
- map:
title: map order request
inputs:
request_body_ex:
schema:
$ref: '#/definitions/postCICSStockOrderService_request_external'
variable: request.body
content: application/json
outputs:
message_body:
schema:
$ref: '#/definitions/postCICSStockOrderService_request'
variable: message.body
content: application/json
header_authorization:
schema:
type: string
variable: message.headers.Authorization
actions:
- set: message_body.stockOrder.orderList
from: request_body_ex.stockOrder.orderListEx
- set: header_authorization
from: request_body_ex.stockOrder.testAuth
version: 1.0.0
options: {}
- invoke:
title: invoke-stockOrder
timeout: 60
verb: POST
cache-response: protocol
cache-ttl: 900
stop-on-error:
- null
version: 1.0.0
target-url: 'https://xxxxx.securegateway.appdomain.cloud:15236/showroomdemo/v1/isecics/order'
tls-profile: zosconnecttls01
- map:
title: map order response
inputs:
message_body:
schema:
$ref: '#/definitions/postCICSStockOrderService_response_200'
variable: message.body
content: application/json
outputs:
message_body_external:
schema:
$ref: '#/definitions/postCICSStockOrderService_response_200_external'
variable: message.body
content: application/json
actions:
- set: message_body_external.stockOrder.returnCodeEx
from: message_body.stockOrder.returnCode
- set: message_body_external.stockOrder.responseMessageEx
from: message_body.stockOrder.responseMessage
- set: message_body_external.stockOrder.stockOfItemList
from: message_body.stockOrder.stockOfItemList
version: 1.0.0
options: {}
otherwise: []
version: 1.0.0
catch: []
securityDefinitions:
Client Secret:
type: apiKey
description: ''
in: header
name: X-IBM-Client-Secret
Client ID:
type: apiKey
description: ''
in: header
name: X-IBM-Client-Id
security:
- Client Secret: []
Client ID: []
テスト実行
アセンブル画面でAPI呼び出しのテストをしてみます。
request.bodyには、公開用I/F②として定義したスキーマに従って組み立てたJSONデータを指定します。
返される結果も公開用I/F②として定義したスキーマに従ったJSONデータになっていることが確認できます。
おわりに
COBOL, z/OS Connect, API Connect それぞれでインターフェースの浄化についての操作がどのように行えるか見てきました。
COBOL => z/OS Connect => API Connectとカスタマイズできる自由度は増していくイメージです。上の例ではシンプルなケースでの手順を示しましたが、特にAPI Connectのアセンブルでは様々なロジックを柔軟に組み込むことができるようなので、もっと様々なカスタマイズを行える余地があります。ただし複雑なロジックを組み込めばそれだけAPIリクエスト時のパフォーマンスに影響することになるので、できるだけ必要最小限のカスタマイズに抑え、充分なパフォーマンステストも実施する必要があると思われます。