JS7® JobScheduler 複数Workflowの同期実行

# 概要

複数のWorkflowを決まった順番で実行する同期実行の方法について説明します。
WorkflowのAddOrder処理と、NoticeBoardの機能を使った連携方法です。

# 前提条件

- JS7 jobscheduler の環境準備、初期設定を済ませていること。
  サンプルWorkflowを使ってざっと学習していることが望ましい。
  まだの方はこちらから
  [JS7® JobScheduler 初期設定、サンプルWorkFlowの登録](https://qiita.com/saitamanokusa/items/2d3162b688329f5ecae2)
- WorkFlowの作成、実行、ログ確認などの基本操作ができること。
  こちらで簡単な操作説明をしています
  [JS7 JobScheduler 初めてのWorkFlow実行](https://qiita.com/saitamanokusa/items/1e5ab48cd2e9d38e035c)
- NoticeBoard の基本的な使い方を理解していること。
  サンプルWorkflow中の処理や、こちらの記事を学習していると理解しやすいと思います。
  [JS7® JobScheduler 複数Workflowの同期実行](https://qiita.com/saitamanokusa/items/6fb6f87f2d7649c296d8)
- 本記事はJobScheduler Ver. 2.4.1 で確認した内容です。
今後内容が変わる可能性があります。

# はじめに

Workflow処理の利用に慣れると、Workflowの定義数が増えていきます。
さらに、これらのWorkflowを、決まった順序で実行したいと考えるようになると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529537/81e525c6-9dea-3dd6-2371-d17d3eb3973f.png)

単一Workflowにすべての処理を詰め込めば、順次処理は可能です。
しかしこの手段では、Job数の増加とともに、全体の見通しが悪くなり、特定の処理だけ修正、実行することも困難です。
※Ver. 2.4より、開始／終了Job指定が可能だが、数が多いと扱いづらい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529537/161abc12-ddf8-b73d-0063-d4e2ee67bed1.png)

特定Workflowから、他のWorkflow処理を実行する手段として、AddOrder処理があります。
しかし、こちらは非同期処理となります。
※呼び出したWorkflowの終了を待たずに、後続処理が進んでしまう。
そのため各Workflowが同時に実行されてしまう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529537/05afbcfa-f3e7-c62b-2d5c-dd2839dbc34b.png)

そこで本記事では、NoticeBoardを利用することで、呼び出したWorkflowの終了を待ってから、次の処理に進む手段をご紹介します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529537/983a4caf-4f5b-66a3-7eb4-7658f010ebb7.png)

この手段ならば、各Workflowを決まった順序で実行することができます。
もちろん各Workflowは、通常どおり個別に実行することも可能です。

# 設定

実際に簡単な設定を行ってみます。
以降は、Workflowを呼び出す側を親Workflow、呼び出される側（実際の処理を行う側）を子Workflowとします。

親Workflowから、２つの子Workflowを順次実行する設定を作成してみます。

## NorticeBoard定義

まずは、2つの子Workflow用に、それぞれのNoticeBoardを作成します。

Practice - Controller - Notice Boards と展開し、新規にNotice Boardを作成します。
名前は`notice_sub_A`としてください。

値は下記のように設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529537/38de7ce1-cee4-3735-1c7e-24782d7fe400.png)

|設定|値|
|-|-|
|Name|notice_sub_A|
|Notice ID for posting order | `replaceAll($js7OrderId, '^#[0-9]{4}-[0-9]{2}-[0-9]{2}#.*?-(.+?)!.+?$', '$1')` |
|Notice ID for expecting order | `replaceAll($js7OrderId, '^#([0-9]{4})-([0-9]{2})-([0-9]{2})#\w(.+?)-.+?$', '$1$2$3$4')` |
|End of Life of Notice | 60 Seconds |

設定できましたら、Deployしてください。

---

全く同じ設定で、`notice_sub_B`を作成、Deployしてください。（Copy、Pasteで可）

|設定|値|
|-|-|
|Name|notice_sub_B|
|Notice ID for posting order | `replaceAll($js7OrderId, '^#[0-9]{4}-[0-9]{2}-[0-9]{2}#.*?-(.+?)!.+?$', '$1')` |
|Notice ID for expecting order | `replaceAll($js7OrderId, '^#([0-9]{4})-([0-9]{2})-([0-9]{2})#\w(.+?)-.+?$', '$1$2$3$4')` |
|End of Life of Notice | 60 Seconds |

## 子Workflowの定義

つぎに、呼び出される側の2つの子Workflowを作成します。
※今回はサンプルとして、簡単な処理で済ませます。

Practice - Controller - Workflows より、新規Workflow `SyncExec_sub_A` を作成します。
適当なJobと、最後にPostNoticesを追加してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529537/a24e70d5-4b65-b224-e920-571ecda2f933.png)

|Jobの項目|値|
|-|-|
|Name/Label|job|
|Agent|primaryAgent|
|JobClass|Shell|
|Script| `sleep 10`|

|PostNoticesの項目|値|
|-|-|
|Notice Board Names|notice_sub_A|

設定できましたら、Deployしてください。

---

同じく子Workflow`SyncExec_sub_B`を作成します。
こちらの PostNotices は、`notice_sub_B`を指定します。

|Jobの項目|値|
|-|-|
|Name/Label|job|
|Agent|primaryAgent|
|JobClass|Shell|
|Script| `sleep 10`|

|PostNoticesの項目|値|
|-|-|
|Notice Board Names|notice_sub_B|

設定できましたら、Deployしてください。

## NorticeBoardの動作確認

子Workflow`SyncExec_sub_A`を実行し、NorticeBoard`notice_sub_A`に通知が飛ぶことを確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529537/9940a5a6-ae97-508f-51ed-9cc36d3c228e.png)
さらに、指定時間（60秒）経過後、自動的に通知が消えることを確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529537/6968521e-1655-7176-c9db-0e3e258ac93e.png)

※この時点では、ExpectNoticesの指定がないため、まだ連携処理はありません。
※Notice Id は、OrderId 値がそのまま入っていますが、この時点では問題ありません。

同様に、子Workflow`SyncExec_sub_B`、NorticeBoard`notice_sub_B`でも確認をしてください。

## 親Workflowの定義

最後に親Workflowの定義を行います。

Practice - Controller - Workflows より、新規Workflow `SyncExec_main` を作成します。

まず、sub_A用の処理を定義します。
Job, AddOrder, ExpectNotices の処理を並べてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529537/a730a5ba-b982-3c2c-fd4e-96efbd763316.png)

設定は下記のようにおこなってください。

|Jobの項目|値|
|-|-|
|Name/Label|start_sub_A|
|Agent|primaryAgent|
|JobClass|Shell|
|Script| `echo "SyncExec_sub_A"` ※処理は適当でよい|

|AddOrderの項目|値|
|-|-|
|Workflow Name|SyncExec_sub_A|

|ExpectNoticesの項目|値|
|-|-|
|Notice Board Names<br>(ExpectNotices Expression)|`'notice_sub_A'`|

---

sub_Aの後続に、sub_B用の処理を同じように追加します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529537/6ad3ccd2-3aeb-ef6c-19e1-472c489d9130.png)

設定は下記のようにおこなってください。

|Jobの項目|値|
|-|-|
|Name/Label|start_sub_B|
|Agent|primaryAgent|
|JobClass|Shell|
|Script| `echo "SyncExec_sub_B"` ※処理は適当でよい|

|AddOrderの項目|値|
|-|-|
|Workflow Name|SyncExec_sub_B|

|ExpectNoticesの項目|値|
|-|-|
|Notice Board Names<br>(ExpectNotices Expression)|`'notice_sub_B'`|

終わりましたら、Deployしてください。

## 親Workflowの動作確認

親Workflow`/Practice/SyncExec_main`を実行します。
ログの実行時間を見て、２つのWorkflowが順次実行されたことを確認してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529537/5ebe8bcd-91e9-89f2-628b-8d7e0e5f755b.png)

|時刻|動作|
|-|-|
|14:22:51|親Workflow 開始|
|14:22:52|子Workflow SyncExec_sub_A 開始|
|14:23:02|子Workflow SyncExec_sub_A 終了<br>子Workflow SyncExec_sub_B 開始|
|14:23:12|子Workflow SyncExec_sub_B 終了|
|14:23:13|親Workflow 終了|

## NorticeBoardの動作確認

親Workflow`/Practice/SyncExec_main`を実行します。
すると、まず親Workflowにて、子Workflow`SyncExec_sub_A` のOrder追加が行われます。
そのままsub_A用のExpectNotice処理が実行され、NoticeBoard `notice_sub_A`では、IDの通知待ちとなります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529537/b6bc0302-6125-e68f-a912-ac6ee378148d.png)

子Workflow `SyncExec_sub_A`終了時に、PostNotice処理が実行され、NoticeBoard `notice_sub_A`では、IDの通知が届きます。
また、sub_A用のExpectはIDの通知を受けたため、消えます。（後続の処理が開始されます）
さらに、後続のsub_B用のOrder追加、ExpectNotice処理が実行され、NoticeBoard `notice_sub_B`では、IDの通知待ちとなります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529537/ecb55ec2-96fc-e06e-1bfb-4f3a6b0b78fe.png)

子Workflow `SyncExec_sub_B`終了時に、PostNotice処理が実行され、NoticeBoard `notice_sub_`では、IDの通知が届きます。
また、sub_B用のExpectはIDの通知を受けたため、消えます。（親Workflow終了）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529537/aabebda9-0319-e727-4546-add88c7beab4.png)

End Of Life で設定した時間経過後、IDは自動的に削除されることを確認してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529537/b861da06-2062-7d6f-4d94-6acb48208d11.png)

:::note warn
もしPost/Expect の連携がうまく動作しない場合は、このNotice Id が一致するように、NoticeBoardの設定を調整してください。
:::

# 並列実行の場合

複数の子Workflowを並列実行する場合は、複数のAddOrderパーツを並べます。
非同期で処理が進むため、ほぼ同時並行で実行されます。
※AddOrderは、単一Orderしか対応できません

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529537/2c3c666c-4dc5-b5d9-52eb-f79cdcbc760c.png)

Workflow名：　`SyncExec_multi`

|Jobの項目|値|
|-|-|
|Name/Label|start_sub|
|Agent|primaryAgent|
|JobClass|Shell|
|Script| `echo "SyncExec_sub"` ※処理は適当でよい|

|AddOrderの項目|値|
|-|-|
|Workflow Name|SyncExec_sub_A|

|AddOrderの項目|値|
|-|-|
|Workflow Name|SyncExec_sub_B|

|ExpectNoticesの項目|値|
|-|-|
|ExpectNotices Expression|`'notice_sub_A' && 'notice_sub_B'`|

設定できましたら、Deployし、動作確認してみてください。
２つのWorkflowが同時並行で実行されたことが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529537/f075548e-8df9-4792-a2db-70941a84fba2.png)

|時刻|動作|
|-|-|
|14:54:14|親Workflow 開始|
|14:54:15|子Workflow SyncExec_sub_A 開始<br>子Workflow SyncExec_sub_B 開始|
|14:54:25|子Workflow SyncExec_sub_A 終了<br>子Workflow SyncExec_sub_B 終了<br>親Workflow 終了|

:::note
ExpectNotices では、下記のように AND, OR の条件指定が可能です。

- `'notice_sub_A' && 'notice_sub_B'`  sub_A, sub_B 両方の通知を受けた場合
- `'notice_sub_A' || 'notice_sub_B'`  sub_A, sub_B どちらかの通知を受けた場合
- `( 'notice_sub_A' && 'notice_sub_B' ) || 'notice_sub_C'`  sub_A, sub_B の両方、または sub_C 通知を受けた場合

これにより、各Workflowがすべて終了した場合、一部のみ終了した場合などの指定が可能です。
※PostNoticeでも、複数NoticeBoardへの通知が可能
:::

# 【参考】NoticeBoardでの厳密なID指定

本記事のように、AddOrder処理を行う場合、OrderIdは下記のようになります。

- 親WorkflowのOrderID : `#2022-11-09#T97195741002-root` ※expectに利用
- 子WorkflowでのOrderID : `#2022-11-09#D97195781200-2022110997195741002!-root` ※postに利用

※親OrderIdの日付、数値列が、子OrderIDの後ろについている。

そこで本記事では下記の設定でNotice Idを指定しています。

Notice ID for posting order : `replaceAll($js7OrderId, '^#[0-9]{4}-[0-9]{2}-[0-9]{2}#.*?-(.+?)!.+?$', '$1')`
Notice ID for expecting order : `replaceAll($js7OrderId, '^#([0-9]{4})-([0-9]{2})-([0-9]{2})#\w(.+?)-.+?$', '$1$2$3$4')`

- 親Workflowでは、ExpectNoticeで、OrderID より、`2022`, `11`, `09` , `97195741002` を結合した`2022110997195741002`を待つ
- 子Workflowでは、PostNoticeで、OrderId の後部分の数値列 `2022110997195741002` を通知する

この設定ならば、親WorkflowのOrderIDでつながるため、より確実かと思います。

:::note
本記事の例では、Idは適当な文字列（例えば `'id'` ）で済ませ、End of Life of Notice を数秒にするのも有効です。
※PostNotice後、即ExpectNoticeで処理が動くため、短時間での同時実行がなければ、必ずしも厳密なIdである必要はありません。
:::

なお、デフォルトで用意されている設定でNotice Idを指定することは、個人的にはお勧めしません。

- `Matching Daily Plan Dates`は、Idが日付となるため、複数のOrderでIdが重複する。1日に何度も実行する場合には不向き。
- `Matching Daily Plan Dates and Order Name` は、上記に加え、Order Name が指定しづらい。

:::note warn
OrderId中の日付は、手動OrderではUTC日付、Workflow中のAddOrderや、DailyPlanではJST日付（TimeZone設定による）となります。
そのため手動Orderを行う場合、実行時間帯によっては不一致となります。
:::

# 【参考】親WorkflowにJobを追加する理由

親Workflowで、何故Job`start_sub_A`, `start_sub_B`を追加するのか疑問をもたれた方もいるかと思います。

1つ目の理由として、WorkflowにJobを１つも追加しないままでは、Deployがうまくできない問題があります。
そのため、適当なJobを1つ以上追加する必要があります。

2つ目の理由として、万が一途中の子Workflowがエラーとなり、途中から再実行したい場合、分かりやすいJob名があれば、多くの処理の中から、開始位置を容易に選ぶことができます。（運用上の利便性）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529537/ec196b30-228b-b6dc-4c0f-53270c98a913.png)

これらの理由で、子Workflow開始前に、それを示すJobを配置することをお勧めします。

# 【参考】Slack通知の簡略化

本記事の方法で複数のWorkflowを実行した場合、各子Workflowおよび親Workflowが終了するたび通知が飛びます。
この場合、例えば下記の設定で、正常終了時は、親Workflowの完了通知だけで済ませることができます。

子Workflowでは、Order変数で、`is_notify (Boolean) = true` を定義
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529537/4a0bc4b1-9d99-feb6-f034-eb7c4f314650.png)

親Workflowでは、AddOrder時に、`is_notify = false` を指定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529537/b3c4488e-0422-144b-db26-25fa419fd8ec.png)

Notification 中の 該当CommandFragment設定にて、最初に下記のような条件指定を追加
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529537/a8f4bf61-1420-c4d7-46a4-18dcfcb5a02a.png)

```shell
# Order変数 is_notify = false かつ正常終了時は通知しない
[ "$(echo '${MON_O_START_VARIABLES}' |jq -r .is_notify)" == "false" ] && [ ${MON_N_STATUS} == 0 ] && exit
```

:::note
Monitor変数`${MON_O_START_VARIABLES}`では、Order追加時に変数を指定した場合に、json形式で変数値を取得できます。
これを用いて、Order変数 `is_notify=false`かつ`${MON_N_STATUS}=0`（正常終了）の場合は処理を抜けるように設定します。
※JOCにて、jqコマンドが使えることを確認してください。docker版では別途追加が必要です。
:::

# 最後に

本記事の方法を用いることで、各Workflowを部品化でき、設定をすっきりさせることができます。
また、スケジュールによる各Workflowの順次実行と、状況に応じた個別実行の、両方に対応できます。
Workflowの利用が増えた際の整理手段として、参考にしていただければ幸いです。

# 参考

[JS7 - AddOrder Instruction](https://kb.sos-berlin.com/display/JS7/JS7+-+AddOrder+Instruction)
[JS7 - Notice Boards](https://kb.sos-berlin.com/display/JS7/JS7+-+Notice+Boards)
[JS7 - How to make a job wait for completion of a workflow](https://kb.sos-berlin.com/display/JS7/JS7+-+How+to+make+a+job+wait+for+completion+of+a+workflow)