Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9374

複数のコンソールアプリケーションを呼び出す簡易ワークフローを作ってみる

$
0
0

このドキュメントの内容

業務システムでは他システム連携や実績データ集計などを夜間バッチで運用することが比較的多いです。システムの規模に比例して夜間バッチアプリケーションの数も多くなり、ジョブ管理ソフトを導入したりするわけですが、決して安いものではありません。また、ソフトがサポートしている全ての機能が必要かというとそうでもないケースが少なくありません。
そこで、シンプルなワークフローの仕組みを作ってみます。

なお、プロセス呼び出しには .NET の Process クラスではなく、先日公開されたOSSの ProcessXを使ってみます。C#8.0 の非同期ストリームを用いて標準出力を受け取ることができたり、async-await なプロセス呼び出しが可能になります。
ProcessX - C#でProcessを C# 8.0非同期ストリームで簡単に扱うライブラリ

実装予定の機能

終了コードによる分岐

コンソールアプリケーションの終了コードによる分岐を実現します。終了コードが 0 のときは成功、0 でないときは失敗とみなします。

ワークフロー上の処理単位を「ワークフローアイテム」と考え、次のメンバを定義します。

  • 処理を実行するメソッド。戻り値で終了コードを返します。
  • 処理が成功したときの次処理を表すワークフローアイテムと、処理が失敗したときの次処理を表すワークフローアイテムを表すプロパティ。単方向のリンクリスト構造とします。
  • 終了コードごとの次処理を表すワークフローアイテムを持たせるかどうかも検討しています。
ワークフローアイテムを表すインターフェース
/// <summary>/// ワークフローアイテムに必要な機能を提供します。/// </summary>publicinterfaceIWorkflowItem{/// <summary>/// 処理を実行します。/// </summary>/// <param name="prevExitCode">前処理の終了コード</param>/// <returns>終了コード</returns>Task<int>ExecuteAsync(intprevExitCode);/// <summary>/// 処理が成功したときの次処理を取得または設定します。/// </summary>IWorkflowItemNextOnSucceed{get;set;}/// <summary>/// 処理が失敗したときの次処理を取得または設定します。/// </summary>IWorkflowItemNextOnFailed{get;set;}}

直列実行

一つのコンソールアプリケーションを実行する処理を「コマンド」と考え、次のメンバを定義します。

  • 処理を実行するメソッド。戻り値で ProcessXで提供されている非同期ストリームオブジェクトを返します。このメソッドの中でプロセス呼び出しを行うように実装します。

ワークフローアイテムの中にコマンドを複数格納し、直列実行します。

コマンドを表すインターフェース
/// <summary>/// コマンドに必要な機能を提供します。/// </summary>publicinterfaceIProcessCommand{/// <summary>/// 処理を開始します。/// </summary>/// <param name="prevExitCode">前処理の終了コード</param>/// <returns>標準出力を返す非同期シーケンス</returns>ProcessAsyncEnumerableStartAsync(intprevExitCode);}

並列実行

ワークフローアイテムの中にコマンドを複数格納するのは直列実行と同じです。
Task.WhenAll メソッドを用いて並列実行します。

直列/並列実行の終了コード判定

ワークフローアイテム内で複数のコマンド(=プロセス呼び出し)を実行する場合、それぞれのコマンドの終了コードからワークフローアイテム単位の終了コードを決定できる仕組みを実装します。

終了コード決定処理を表すインターフェース
/// <summary>/// 終了コードの制御に必要な機能を提供します。/// </summary>publicinterfaceIExitCodeHandler{/// <summary>/// 指定されたコマンドの実行結果から終了コードを決定します。/// </summary>/// <param name="results">コマンドの実行結果</param>/// <returns>終了コード</returns>intHandleExitCode(ProcessCommandResult[]results);}/// <summary>/// コマンドの実行結果。/// </summary>publicreadonlystructProcessCommandResult{publicProcessCommandResult(IProcessCommandcommand,intexitCode){Command=command;ExitCode=exitCode;}/// <summary>/// コマンドを取得します。/// </summary>publicIProcessCommandCommand{get;}/// <summary>/// 終了コードを取得します。/// </summary>publicintExitCode{get;}}

設定ファイルによるワークフローの組み立て

前述のワークフローアイテム、コマンド、終了コード制御の設定値を設定ファイルから読み込み、ワークフローを組み立てられるようにします。

  • ワークフローアイテムのリンクリスト構造
  • 実行するコマンドライン文字列
  • 環境変数として渡すキーと値の組み合わせ
  • 終了コードの制御

サンプルコード

現時点のソースコードは GitHubで見ることができます。但し、検討初期段階のため、破壊的変更を含む大幅な変更を行う可能性が非常に高いです。

ワークフロー実行アプリケーション

ワークフローアイテムのリンクリスト構造を組み立て、起点となるワークフローアイテムを実行します。
次のコードではコード上でワークフローアイテムを組み立てていますが、設定ファイル読み込みによって同等の内容を実現できるように検討しています。

classProgram{staticasyncTaskMain(string[]args){stringfilePath=@"d:\SampleApp1.exe";varitem=newParallelWorkflowItem("root","最初の処理"){// 実行するコマンド(並列実行)Commands=new[]{ProcessCommandFactory.FromCommandLine("command1","コマンド1",filePath+" 5"),ProcessCommandFactory.FromCommandLine("command2","コマンド2",filePath+" 4")},// 終了コードの制御は既定// 何れかのコマンドの終了コードが 0 でない場合、最初に見つかった終了コードを返すExitCodeHandler=null,// 成功時の次処理NextOnSucceed=newSequencialWorkflowItem("root-succeed","成功時の後処理"){// 実行するコマンド(直列実行)Commands=new[]{ProcessCommandFactory.FromFile("command3","コマンド3",filePath,"3"),ProcessCommandFactory.FromFile("command4","コマンド4",filePath,"-2")},// 終了コードの制御ExitCodeHandler=ExitCodeHandlerFactory.Create(// 既定の終了コード-1,new[]{// command3 の終了コード = 0 && command4 の終了コード = 0 => 0(new[]{("command3",0),("command4",0)},0)// command3 の終了コード = 1 && command4 の終了コード = 1 => 11,(new[]{("command3",1),("command4",1)},11)// command4 の終了コード = 1 => 10,(new[]{("command4",1)},10)}),// 成功時の次処理NextOnSucceed=newWorkflowItem("root-succeed-succeed","成功時の後処理"){Command=ProcessCommandFactory.FromFile("command5","コマンド5",filePath,"1")},// 失敗時の次処理NextOnFailed=newWorkflowItem("root-succeed-failed","失敗時の後処理"){// 実行するコマンドCommand=ProcessCommandFactory.FromFile("command6","コマンド6",filePath,"2")}},// 失敗時の次処理NextOnFailed=newWorkflowItem("root-failed","失敗時の後処理"){// 実行するコマンドCommand=ProcessCommandFactory.FromFile("command7","コマンド7",filePath,"1")}};// ワークフローを実行するIWorkflowItemcurrent=item;intexitCode=0;try{while(current!=null){Console.WriteLine($"----- {current.ID}:{current.Name} -----");exitCode=awaitcurrent.ExecuteAsync(exitCode);Console.WriteLine($"{current.ID}.ExitCode = {exitCode}");if(exitCode==0){current=current.NextOnSucceed;}else{current=current.NextOnFailed;}}}catch(Exceptionex){Console.WriteLine(ex.Message);}}}

呼び出すコンソールアプリケーション

SampleApp1
classProgram{staticasyncTask<int>Main(string[]args){intexitCode=awaitMainAsync(args);Console.WriteLine($"ExitCode = {exitCode}");returnexitCode;}privatestaticasyncTask<int>MainAsync(string[]args){// 環境変数から前処理の終了コードを取得して出力varvariables=System.Environment.GetEnvironmentVariables();varkey=ProcessCommandVariables.PrevExitCode;Console.WriteLine($"EnvironmentVariables[{key}] = {variables[key]}");// 引数を出力for(inti=0;i<args.Length;++i){Console.WriteLine($"args[{i}] = {args[i]}");}// 先頭の引数は繰り返し回数を表すものとするintrepeatCount=Convert.ToInt32(args[0]);if(repeatCount<0){return1;}for(inti=0;i<repeatCount;++i){awaitTask.Delay(1000);Console.WriteLine($"{i+1}/{repeatCount}");}return0;}}

出力結果

----- root:最初の処理 -----
[コマンド1] EnvironmentVariables[mxProject.ProcessWorkflow.PrevExitCode] = 0
[コマンド1] args[0] = 5
[コマンド2] EnvironmentVariables[mxProject.ProcessWorkflow.PrevExitCode] = 0
[コマンド2] args[0] = 4
[コマンド1] 1/5
[コマンド2] 1/4
[コマンド1] 2/5
[コマンド2] 2/4
[コマンド1] 3/5
[コマンド2] 3/4
[コマンド1] 4/5
[コマンド2] 4/4
[コマンド2] ExitCode = 0
[コマンド1] 5/5
[コマンド1] ExitCode = 0
root.ExitCode = 0
----- root-succeed:成功時の後処理 -----
[コマンド3] EnvironmentVariables[mxProject.ProcessWorkflow.PrevExitCode] = 0
[コマンド3] args[0] = 3
[コマンド3] 1/3
[コマンド3] 2/3
[コマンド3] 3/3
[コマンド3] ExitCode = 0
[コマンド4] EnvironmentVariables[mxProject.ProcessWorkflow.PrevExitCode] = 0
[コマンド4] args[0] = -2
[コマンド4] ExitCode = 1
Process returns error, ExitCode:1

root-succeed.ExitCode = 10
----- root-succeed-failed:失敗時の後処理 -----
[コマンド6] EnvironmentVariables[mxProject.ProcessWorkflow.PrevExitCode] = 10
[コマンド6] args[0] = 2
[コマンド6] 1/2
[コマンド6] 2/2
[コマンド6] ExitCode = 0
root-succeed-failed.ExitCode = 0

Viewing all articles
Browse latest Browse all 9374

Latest Images

Trending Articles