(OutSystems)Service Actionの注意すべき特性

[(OutSystems) Service Actionチュートリアル](https://qiita.com/jyunji_watanabe/items/f19948379d1494b5aee8)
に引き続き、OutSystems 10環境で開発してきたチームメンバーにOutSystems 11の新機能を説明する資料です。

Service Actionの設計時にいくつか注意すべき点があるので、実例を通して確認していきます。

# 編集履歴
**2020/07/15追記　URL構成について新しいことがわかったので追記**
　「ロジック（GetUserIdやRoleのCheck Action）によるセキュリティ実装が必要」の下部参照

# 動作確認環境
Personal Environment(Version 11.7.3 (Build 5968))
Service Studio(Version 11.6.27)

# サンプルアプリケーション
https://www.outsystems.com/forge/Component_Overview.aspx?ProjectId=7877

# セキュリティ
## ログインユーザー情報は、呼び出し元モジュールのものを引き継ぐ
[公式ドキュメント](https://success.outsystems.com/Documentation/ja-jp/11/Developing_an_Application/Reuse_and_Refactor/Use_Services_to_Expose_Functionality)によると、「OutSystemsはクライアントセッションからの認証コンテキストを渡します（ユーザーIDおよびテナントID）」という記述があります。

つまり、Service Action呼び出し側のログイン情報を引き継ぎ、呼び出されたService Action内でGetUserId()すると、呼び出し元画面のログインユーザーIDが取れるということ。

サンプルを使って確認してみましょう。
サンプルアプリケーションをダウンロードすると、GetCurrentUserNameというService Actionに実装してあります。単純にGetUserId()でログイン中のユーザーIDを取得し、組み込みのUser Entityを検索してその名前を戻り値に返す処理。
![qServiceAction2_1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/affa01b8-4fb5-6431-c155-5303bfc5107c.png)

このService Actionを別のアプリケーション内にある、Traditional Webのモジュールから呼び、その結果を画面表示してみました。すると、Traditional Webのモジュールにログインしていたユーザの名前が正しくService Actionから返ってきました。

というわけで、Service Actionを使ってサービスを実現した場合でも、呼び出し元とログイン情報は共有できるようです。

## Roleも呼び出し元モジュールのログインユーザーが持っていたものを利用可能
GetUserId()が使えるくらいなので、Roleチェックも可能です。
以下のようにRoleともに作成されるCheck<Role名>の呼び出しは期待通り処理されます。
（GetUserId()でとれるユーザーが該当のRoleを持って入ればTrueが返る）

サンプルアプリケーションでは、CheckTestRoleを確認してください。このテストを実行する前には、実行ユーザに、サンプルアプリケーションに用意してあるHousesoftSampleServiceRoleを持たせることを忘れずに。
![qServiceAction2_2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/c3bea9fd-824c-db7f-77db-aa2b05c0fbfb.png)

## ロジック（GetUserIdやRoleのCheck Action）によるセキュリティ実装が必要
同じく、[公式ドキュメント](https://success.outsystems.com/Documentation/ja-jp/11/Developing_an_Application/Reuse_and_Refactor/Use_Services_to_Expose_Functionality)に、「サービスアクションには同じ環境からのみアクセスできます」と書いてあります。
が、これが、ネットワークやOutSystemsが内部的に持っているセキュリティのロジックで遮断されているのか、単にService Studioや外部サービスから発見できる仕組みがないだけなのか気になりますね。

ブラウザから、Service Actionへのリクエストを直接発行して確かめてみます。
そのために、まずはログからService Actionに実際に使用されたHTTPリクエストを取得しましょう。

REST/SOAPと同じく、Servcie Centerの設定でログレベルの変更をします。
1. Service Studioのモジュールを開いた状態で歯車アイコンをクリックする
2. Service Centerで目標のモジュールの設定画面が開く
3. Operationタブを開く
4. 「Service Actions logging」をFullに変更

変えてみると警告が表示される通り、DBの使用率が上がります。基本的には開発環境で使う機能ですね（本番でしか再現しないトラブルシュートの場合は別ですが）。
![qServiceAction9.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/107059a5-1527-b3cd-b7bd-f560840d3f22.png)

この設定をすると、Service Actionsのログの右端に「Detail」というリンクが出て、実際にService Actionに対して行われたRESTリクエストの生のログが記録されています。
HTTP Trace項目の空行の上側がHTTPリクエストです。

FiddlerなどのHTTPリクエストを任意に発行できるツールを（FiddlerならComposer）使ってリクエストしてみると、普通に結果が帰ってきました。

~~PEだと、http://<自動生成されていそうな文字列\>.outsystemscloud.com/...という形式のURLなので無関係な人にはわからないでしょうが、~~やはり、ログインチェックRoleチェックは必要ですね。

**2020/07/15追記　URL構成について新しいことがわかったので追記**
観察してみると、
http://\<呼び出し先モジュールのデプロイメントゾーン\>/\<モジュール名\>/serviceapi/\<Action名\>
という構成のURLになるように見受けられます。こうなるというドキュメントは見つからないですが。
ただし、ホスト名部分が呼び出し先モジュールのデプロイメントゾーンで決めることを示すドキュメントはあります。

[サービスを使用して機能を公開する](https://success.outsystems.com/Documentation/ja-jp/11/Developing_an_Application/Reuse_and_Refactor/Use_Services_to_Expose_Functionality#.E3.83.88.E3.83.A9.E3.83.B3.E3.82.B6.E3.82.AF.E3.82.B7.E3.83.A7.E3.83.B3.E3.81.8A.E3.82.88.E3.81.B3.E3.83.8D.E3.83.83.E3.83.88.E3.83.AF.E3.83.BC.E3.82.AF.E3.82.92.E5.87.A6.E7.90.86.E3.81.99.E3.82.8B)

# Service Actionの例外は呼び出し元でHandleできる
サービスを提供するための機能ですが、Service Action内で発生した例外は呼び出し側でキャッチできます。

サンプルアプリケーションの、[チュートリアルで作ったAction](https://qiita.com/jyunji_watanabe/items/f19948379d1494b5aee8#%E7%B0%A1%E5%8D%98%E3%81%AAservice-action%E3%82%92%E4%BD%9C%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%8B)では、負の値を渡すと例外を投げるようになっています。

参照元モジュールAll Exceptionに対するHandle Exceptionを用意し、マイナスの金額を「CalculateConsumptionTax」Service Actionに渡すと、きちんと呼び出し元でキャッチされます。

（呼び出し元のモジュールのブレークポイントで止まったところ。Service Actionで設定した例外メッセージがException HandlerのExceptionMessageに表示されている）
![qServiceAction2_3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/6c22d9a7-770d-0c2d-692a-79c66bd1d167.png)

なお、このとき、Service Actionsのログには、HTTPステータス500が返ってきていました。

# トランザクションは呼び出し元と独立
呼び出し元のトランザクションは、呼び出したService Actionとは独立しています。
よって、Service Actionで例外が発生してロールバックしたとしても、呼び出し元のトランザクションはロールバックされません。

こんなモジュールでテストしてみます。
Serviceモジュール内に用意したServer ActionとService Actionを順番に（同じScreen Action内で）実行して、同じEntityに対して1レコードずつ追加する仕組みです。
![qServiceAction2_4.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/40870e8d-6c27-233f-3efc-7ded4c8bb3f0.png)

用意したService Actionには、Boolean型のInput ParameterをTrueにすると強制的に例外を発生させるようになっています（例外自体はService Action内でHandleされるが、TransactionはAbortされる）。

さて、この場合、結果のEntityはどうなるでしょうか？
Service Actionから追加したはずの1レコードは、Service Action内で例外をキャッチしてロールバックしているので実際には追加されません。
では、Server Actionで追加したレコードは？　これはロールバックされずに、無事にレコード追加されてしまいます。呼び出し元モジュールと呼び出し先のService Actionは別のトランザクションになっているからです。