Scenarioにおけるプログラミング

Scenarioには、AOP settingと呼ばれる全てのコマンド共通で挿入可能なプログラミングスポットとTransactionロールバック時に実行されるCancellationオプション、また、コマンド本体にスクリプトを実装できるscriptコマンドが提供されています。

Scenarioの名前空間

Scenarioの名前空間では、ビルトインオブジェクトの他にlogger__CONFIG__アクセッサとcontextatommodel共通コンテキストが含まれています。Scenarioは、API呼び出しによって駆動するため、contextオブジェクトには、API要求情報へのアクセッサが含まれています。また、Scenarioの名前空間にはコマンドを跨いで代入や参照するためのグローバル変数が格納されます。

Note

Scenarioには、コマンドを跨いで利用できるグローバル変数が定義できます。グローバル変数に格納できる型はシリアライズ可能なものに限定されます。以下の型以外の値をグローバル変数に代入した場合は、トランザクションサーバへのバックアップから除外されます。
ATOM MU str bytes int bool dict list OrderedDict

  • logger: Scenarioログにログを出力する関数です。ログレベルは、debuginfowarningerrorcriticalの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タブにログを出力する関数です。ログレベルは、debuginfowarningerrorcriticalの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タブへの出力結果

 [D (YYYY-MM-DDThh:mm:ss) (file名、Toyblock番号、行など)] DEBUGレベル
 [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 countRepeat intervalIndex variable nameContinuation conditionInterruption 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 expressionException handlingProcessingInput 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 expressionException handlingProcessingOutput 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 content404 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)