Scenarioにおけるプログラミング
Scenarioには、AOP setting
と呼ばれる全てのコマンド共通で挿入可能なプログラミングスポットとTransactionロールバック時に実行されるCancellation
オプション、また、コマンド本体にスクリプトを実装できるscript
コマンドが提供されています。
Scenarioの名前空間
Scenarioの名前空間では、ビルトインオブジェクト
の他にlogger
、__CONFIG__
アクセッサとcontext
、atom
、model
共通コンテキストが含まれています。Scenarioは、API呼び出しによって駆動するため、context
オブジェクトには、API要求情報へのアクセッサが含まれています。また、Scenarioの名前空間にはコマンドを跨いで代入や参照するためのグローバル変数が格納されます。
Note
Scenarioには、コマンドを跨いで利用できるグローバル変数が定義できます。グローバル変数に格納できる型はシリアライズ可能なものに限定されます。以下の型以外の値をグローバル変数に代入した場合は、トランザクションサーバへのバックアップから除外されます。
ATOM
MU
str
bytes
int
bool
dict
list
OrderedDict
logger
: Scenarioログにログを出力する関数です。ログレベルは、debug
、info
、warning
、error
、critical
の5種類です。以下は使用例です。
logger.debug("DEBUGレベル")
logger.info("INFOレベル")
logger.warning("WARNレベル")
logger.error("ERRORレベル")
logger.critical("CRITレベル")
Scenarioログへの出力結果
[D (YYMMDD) (HH:mm:SS) (file名):(行)] DEBUGレベル
[I (YYMMDD) (HH:mm:SS) (file名):(行)] INFOレベル
[W (YYMMDD) (HH:mm:SS) (file名):(行)] WARNレベル
[E (YYMMDD) (HH:mm:SS) (file名):(行)] ERRORレベル
[C (YYMMDD) (HH:mm:SS) (file名):(行)] CRITレベル
Note
Scenarioログへのログ出力ではinfo
レベル以上が出力される仕様になっています。
debug
レベルのログを出力したい場合は、起動パラメータの設定が必要になります。
"logging": "debug"
context.logger(非推奨)
: ScenarioログとLogタブにログを出力する関数です。ログレベルは、debug
、info
、warning
、error
、critical
の5種類を使用できます。Logタブではログレベルに応じてカラーリングされます。 本機能は動作が重く、後方互換のために提供されていますが将来のリリースで削除されます。以下は使用例です。
context.logger.debug("DEBUGレベル")
context.logger.info("INFOレベル")
context.logger.warning("WARNレベル")
context.logger.error("ERRORレベル")
context.logger.critical("CRITレベル")
Scenarioログへの出力結果
[I (YYMMDD) (HH:mm:SS) (file名):(行)] INFOレベル
[W (YYMMDD) (HH:mm:SS) (file名):(行)] WARNレベル
[E (YYMMDD) (HH:mm:SS) (file名):(行)] ERRORレベル
[C (YYMMDD) (HH:mm:SS) (file名):(行)] CRITレベル
Logタブへの出力結果
[I (YYYY-MM-DDThh:mm:ss) (file名、Toyblock番号、行など)] INFOレベル
[W (YYYY-MM-DDThh:mm:ss) (file名、Toyblock番号、行など)] WARNレベル
[E (YYYY-MM-DDThh:mm:ss) (file名、Toyblock番号、行など)] ERRORレベル
[C (YYYY-MM-DDThh:mm:ss) (file名、Toyblock番号、行など)] CRITレベル
__CONFIG__
:
Config
サービスに登録されたアプリケーションコンフィグ辞書が格納されます。Scenarioのcategory
属性と同一の名称を持つConfig
サービスのプラグインが存在する場合、Scenarioが呼び出された時にConfig
をロードして本変数にマッピングされます。Scenarioのスクリプトから以下のようにアクセスできます。
r = await callout(endpoint=__CONFIG__["endpoint"], path="/v1/tests")
context.session
:
HTTPセッションを格納している変数です。Qmonus SDKでは、tornadoをwebサーバとして利用しています。本オブジェクトは、tornado.web.RequestHandler
です。context.session.request
でAPI要求情報にアクセスすることができますが、次に解説するcontext.request
を利用することを推奨しています。 主に本オブジェクトは、ScenarioからHTTP応答を返却する際に利用します。応答コードの設定やボディーの返却を以下のように記述します。
payload = json.loads(context.session.body)
context.session.set_status(201)
context.session.finish(dict(result=True))
-
context.request
:
API要求情報のラッパーです。context.session.request
でアクセスできる情報と等価ですが、MU型のオブジェクトにキャストされています。 -
context.request.uri
:
リクエストURIにアクセスできます。context.session.request.uri
と等価です。 -
context.request.path
:
リクエストパスにアクセスできます。context.session.request.path
と等価です。 -
context.request.resources
:
リソースパス変数を格納している変数です。/v1/hoges/{hogeId}/hogehoges/{hogehogeId}
のようなリソースパスを持つScenarioの場合に/v1/hoges/aaa/hogehoges/bbb
のようなアクセスがあった場合、resourcesには以下の辞書データがMUにcastされてセットされます。
{"hogeId": "aaa", "hogehogeId": "bbb"}
context.request.params
:
クエリパラメータを格納している変数です。/v1/hoges?name=aaa&name=bbb&status=Completed
のようなアクセスがあった場合、parametersには以下の辞書データがMUにcastされてセットされます。
{"name": ["aaa", "bbb"], "status": ["Completed"]}
-
context.request.method
:
リクエストメソッドにアクセスできます。context.session.request.method
と等価です。 -
context.request.headers
:
リクエストヘッダにアクセスできます。context.session.request.headers
と等価です。
HTTPメソッドがGETまたはDELETEの場合、かつリクエストボディがNoneの場合はContent-TypeヘッダはAPIGWでドロップされます。 -
context.request.body
:
リクエストボディーにアクセスできます。context.session.request.body
と等価です。bytes
型で格納されていますのでjsonの場合はjson.loads
でデコードして使用してください。 -
context.qmonus
:
Transaction機能へのアクセッサです。以下の機能を提供します。
# トランザクションIDをログ出力
logger.info(context.qmonus.xid)
# トランザクション名をログ出力
logger.info(context.qmonus.xname)
# トランザクションを開始する(デフォルトは自動で開始されますが`auto_begin=False`と設定している場合は開発者自身で開始する必要があります)
await context.qmonus.begin()
# トランザクションを保存する(コマンドの遷移時に自動的に実行されますので開発者自身で実行する必要は基本的にはありませんが、コマンド内の任意のタイミングでグローバル変数領域を更新したい場合に使用します。)
await context.qmonus.save()
# トランザクションをcommitする(自動的に実行されますので通常は開発者自身で実行する必要はありません)
await context.qmonus.commit()
# トランザクションをロールバックする
await context.qmonus.rollback(reason="サンプルロールバック")
# トランザクションをロールフォワードする
await context.qmonus.rollforward(reason="サンプルロールフォワード")
# トランザクションを中断する
await context.qmonus.abort(reason="サンプル中断")
# トランザクションを中断してロールバックを開始する
await context.qmonus.abort(with_action="cancel", reason="中断次第ロールバック")
# トランザクションを中断してロールフォワードを開始する
await context.qmonus.abort(with_action="recovery", reason="中断次第ロールバック")
# トランザクションロックを追加する
await context.qmonus.lock(keys=["tenants/A/AppendSample"], retry_count=10, retry_interval=3)
# トランザクションロックを部分的に解除する
await context.qmonus.unlock(keys=["tenants/A/AppendSample"])
# トランザクション情報を検索する(xdomain, xtyep, xid, xname, statusを検索キーに指定可能)
transactions = await context.qmonus.retrieveTransactions(status="Processing")
Caution
auto_rollback_mode=False
の場合、await context.qmonus.abort(with_action="cancel)
を実行してもキャンセルは実行されません。auto_rollback_mode=False
でも強制的にキャンセルを実行したい場合は、以下の例に従い、ignore_auto_rollback_mode=True
を引数に指定してください。
await context.qmonus.abort(with_action="cancel", ignore_auto_rollback_mode=True)
-
context.request_body(非推奨)
: 後方互換のために提供されていますが将来のリリースで削除されます。 -
context.params(非推奨)
: 後方互換のために提供されていますが将来のリリースで削除されます。 -
context.axis(非推奨)
: 後方互換のために提供されていますが将来のリリースで削除されます。 -
context.model(非推奨)
: 後方互換のために提供されていますが将来のリリースで削除されます。 -
プラグインモジュールやファンクションについて プラグインモジュールやファンクションを定義した場合は、
context
オブジェクト配下に格納されます。
# hello関数を持つexampleという名称のプラグインモジュールを定義して呼び出す例
r = await context.example.hello()
# convertという名称のプラグインファンクションを定義して呼び出す例
r = context.convert("aaa")
コマンド共通(AOP setting)
Repeat conditions
コマンドの繰り返し実行条件を定義できる機能です。Repeat count
、Repeat interval
、Index variable name
、Continuation condition
、Interruption condition
を記述できます。
Repeat count
: 繰り返し回数を定義します。integer
で通常は指定しますが、int型を返却するスクリプトで動的に定義することもできます。
# アプリケーションコンフィグに繰り返し回数を定義して参照する例
__CONFIG__["repeatCount"]
Repeat interval
: 繰り返し間隔を定義します。integer
で通常は指定しますが、int型を返却するスクリプトで動的に定義することもできます。
# アプリケーションコンフィグに繰り返し間隔を定義して参照する例
__CONFIG__["repeatInterval"]
-
Index variable name
: 繰り返しカウンタの変数名を定義します。当該コマンドの他のAOP機能やコマンド実装からローカル変数として参照することができます。 -
Continuation condition
: 繰り返しの継続条件式を1ライナーで記述します。
# 結果A、もしくは結果Bが真であれば繰り返しを続行する
resultA==True or resultB==True
Interruption condition
: 繰り返しを抜ける条件式を1ライナーで記述します。Continuation condition
が真の場合でもInterruption condition
が偽であれば繰り返しは中止されます。
# 結果Cが真であれば繰り返しを中止する
resultC==True
Pre-processing
コマンドの事前処理を定義できる機能です。Conditional expression
、Exception handling
、Processing
、Input schema
を記述できます。
Conditional expression
: コマンドの実行を行うか否かを判定する事前条件を設定できる機能です。1ライナーで真偽を返却するコードを記述してください。
# flg1が真で且つ、flg2が偽の場合のみコマンドが実行される
flg1==True and flg2==False
-
Exception handling
:Conditional expression
に合致しない場合、コマンドの実行はスキップされますが、Repeat conditions
が設定されている場合、再度実行可否の試行が行われます。Exception handling
機能は、再度実行可否の試行に移る前に現在の状況が例外をスローすべき状態か否かを判定し、真であれば例外を発出する設定を行うことができます。 -
Processing
: 処理を自由に記述できます。script
コマンド同様に制約はありません。 -
Input schema
: コマンド実装を実行する直前のグローバル変数領域の状態をjsonschemaで定義できます。妥当性チェックでエラーがある場合、例外を発出します。
Post-processing
コマンドの事後処理を定義できる機能です。Conditional expression
、Exception handling
、Processing
、Output schema
を記述できます。
-
Conditional expression
: 前述したPre-processing
と同様 -
Exception handling
: 前述したPre-processing
と同様 -
Processing
: 前述したPre-processing
と同様 -
Output schema
: コマンド実装を実行した直後のグローバル変数領域の状態をjsonschemaで定義できます。妥当性チェックでエラーがある場合、例外を発出します。
Cancellation
Scenarioは、動作中に例外を検出したり、context.qmonus
オブジェクト操作によってワークフローを中断し、ロールバックすることができます。Cancellation
では、ロールバック動作を自由に記述することができます。ワークフローの進行のコントロールはQmonus SDKが担いますが、ロールバック処理の詳細はプラグイン開発者自身が実装する必要があります。
キャンセルオプションには、以下の設定項目があります。
Cancellable
:キャンセルの可否を指定します。真の場合、キャンセルアクションを実行します。偽の場合、キャンセルできないコマンドとみなされロールバックは中断されます。ワークフローによっては、ある時点までロールバックすることが可能でも、それ以降はロールフォワードのみが許可されるケースもあります。このような場合は、ロールバックできないコマンドにFalse
を設定してください。 ただし、Cancellable=False
にもかかわらず、どうしてもトランザクションロックを強制的に解除する必要があるケースなどでは、トランザクションキャンセル時にcancel_mode=breakthrough
を指定することで強制的にキャンセルしたことにすることができます。このケースでは手動での何らかのリカバリオペレーションが発生すると思われます。
Action
:
キャンセル処理を実装します。API呼び出しまたは、カスタムスクリプトを記述できます。トランザクションサーバはシナリオが停止する直前にメモリスペースを復元するため、サーバダウンやNW/DBの障害が中断の原因である場合でも、開発者は異常が発生する直前のデータ状況を想定して処理を記述できます。
例えば、正常処理としてPOST /resources
によって外部サービスにリソースを作成した後、何らかの要因でトランザクションが中断したと仮定します。この後のロールバック処理として外部サービスに作成してしまったリソースを削除したい場合、トランザクションが中断した時点のグローバルメモリに存在しているリソースの名前name
変数がNone
でなければ生成された可能性があるため、外部サービスから該当の名称のリソースを検索して存在していれば削除するCancellation
処理は以下のように記述できます。サービスによっては存在確認をせずともいきなりDELETEを送信しても良いでしょう。その場合は存在しない場合は404が返却されると想定されますのでレスポンスコードが、204 No content
や404 Not Found
を正常として扱うように記述することになるでしょう。
# Cancellation例
if name:
r = await callout(path="/resources?name={}".format(name))
if r.error:
raise Error(r.code, reason="Unable to get resources")
payload = MU(json.loads(r.body)
if payload > 0:
r = await callout(path="/resources/{}".format(payload.resourceId), method=DELETE)
if r.error:
raise Error(r.code, reason="Unable to delete resource %r" % name)