ほぼ線形のワークフローがあるプロジェクトがあります。 .NETステートレス ライブラリ を使用してワークフローエンジン/ステートマシンとして機能しようとしています。そこにある例の数は限られていますが、私は次のコードをまとめました:
private StateMachine<WorkflowStateType, WorkflowStateTrigger> stateMachine;
private StateMachine<WorkflowStateType, WorkflowStateTrigger>.TriggerWithParameters<Guid, DateTime> registrationTrigger;
private Patient patient;
public Patient RegisterPatient(DateTime dateOfBirth)
{
configureStateMachine(WorkflowState.Unregistered);
stateMachine.Fire<DateTime>(registrationTrigger, dateOfBirth);
logger.Info("State changed to: " + stateMachine.State);
return patient;
}
private void configureStateMachine(WorkflowState state)
{
stateMachine = new StateMachine<WorkflowState, WorkflowTrigger>(state);
registrationTrigger = stateMachine.SetTriggerParameters<DateTime>(WorkflowTrigger.Register);
stateMachine.Configure(WorkflowState.Unregistered)
.Permit(WorkflowTrigger.Register, WorkflowStateType.Registered);
stateMachine.Configure(WorkflowState.Registered)
.Permit(WorkflowTrigger.ScheduleSampling, WorkflowState.SamplingScheduled)
.OnEntryFrom(registrationTrigger, (dateOfBirth) => registerPatient(dateOfBirth));
}
private void registerPatient(DateTime dateOfBirth)
{
//Registration code
}
ご覧のとおり、トリガーを渡すことができるステートレスFire()オーバーロードを使用しています。これは、ステートマシンにビジネスロジック(この場合は、新しい患者を登録するためのコード)を処理させるためです。
これはすべて機能しますが、すべてのステートマシンコードを別のクラスに移動してカプセル化したいと思います。これを行うのに問題があります。これを行う際に私が抱えていた課題は次のとおりです。
StateMachine
オブジェクトをインスタンス化するには、状態を指定する必要があります。State
は、インスタンス化時にのみ設定できる読み取り専用プロパティです。registrationTrigger
は、ステートマシンの構成中にインスタンス化する必要があり、呼び出し元のクラスでも使用できる必要があります。これらの項目を克服し、ステートマシンコードをカプセル化するにはどうすればよいですか?
スコット・ハンゼルマンによる 記事 の例とライブラリの紹介があります。また、ステートマシンをカプセル化するScottの記事で言及されている バグの実装例 など、GitHubで利用できる例はほとんどありません。
以下は、動作から状態を抽出する方法の例です。
public class PatientRegistrationState
{
private StateMachine<WorkflowState, WorkflowTrigger> stateMachine;
private StateMachine<WorkflowState, WorkflowStateTrigger>.TriggerWithParameters<DateTime> registrationTrigger;
public PatientRegistrationState(State initialState = default(State)) {
stateMachine = new StateMachine<WorkflowState, WorkflowTrigger>(initialState);
stateMachine.Configure(WorkflowState.Unregistered)
.Permit(WorkflowTrigger.Register, WorkflowStateType.Registered);
stateMachine.Configure(WorkflowState.Registered)
.Permit(WorkflowTrigger.ScheduleSampling, WorkflowState.SamplingScheduled)
.OnEntryFrom(registrationTrigger, (date) => OnPatientRegistered(date));
}
public WorkflowState State => stateMachine.State;
public Action<DateTime> OnPatientRegistered {get; set;} = (date) => { };
// For state changes that do not require parameters.
public void ChangeTo(WorkflowTrigger trigger)
{
stateMachine.Fire<DateTime>(trigger);
}
// For state changes that require parameters.
public void ChangeToRegistered(DateTime dateOfBirth)
{
stateMachine.Fire<DateTime>(registrationTrigger, dateOfBirth);
}
// Change to other states that require parameters...
}
public class PatientRegistration
{
private PatientRegistrationState registrationState;
private Patient patient;
public PatientRegistration()
{
registrationState = PatientRegistrationState(WorkflowState.Unregistered)
{
OnPatientRegistered = RegisterPatient;
}
}
public Patient RegisterPatient(DateTime dateOfBirth)
{
registrationState.ChangeToRegistered(dateOfBirth);
logger.Info("State changed to: " + registrationState.State);
return patient;
}
private void RegisterPatient(DateTime dateOfBirth)
{
// Registration code
}
}
これが私のプロジェクトでそれを達成した方法です。
クラスを分離するための分離されたワークフローロジック。リクエストオブジェクトに存在するフラグの1つに基づいたワークフローがいくつかありました。以下はワークフロークラスの1つです。
_public class NationalWorkflow : BaseWorkflow
{
public NationalWorkflow(SwiftRequest request) : this(request, Objects.RBDb)
{ }
public NationalWorkflow(SwiftRequest request, RBDbContext dbContext)
{
this.request = request;
this.dbContext = dbContext;
this.ConfigureWorkflow();
}
protected override void ConfigureWorkflow()
{
workflow = new StateMachine<SwiftRequestStatus, SwiftRequestTriggers>(
() => request.SwiftRequestStatus, state => request.SwiftRequestStatus = state);
workflow.OnTransitioned(Transitioned);
workflow.Configure(SwiftRequestStatus.New)
.OnEntry(NotifyRequestCreation)
.Permit(SwiftRequestTriggers.ProcessRequest, SwiftRequestStatus.InProgress);
workflow.Configure(SwiftRequestStatus.InProgress)
.OnEntry(ValidateRequestEligibility)
.Permit(SwiftRequestTriggers.AutoApprove, SwiftRequestStatus.Approved)
.Permit(SwiftRequestTriggers.AdvancedServicesReview, SwiftRequestStatus.PendingAdvancedServices);
.....................
}
_
これは、コントローラー/その他のレイヤーからトリガーされます。
_private static void UpdateRequest(SwiftRequestDTO dtoRequest)
{
var workflow = WorkflowFactory.Get(request);
workflow.UpdateRequest();
}
_
前述のように、リクエストオブジェクトの条件に基づいて異なるワークフロールールがあったため、ファクトリパターンWorkflowFactory.Get(request)
を使用しました。ワークフローのインスタンスを作成/必要に応じて挿入できます
そして、ワークフロークラス(私の場合はBaseWorkflowクラス)内で、アクションを公開しました。
_ public void UpdateRequest()
{
using (var trans = this.dbContext.Database.BeginTransaction())
{
this.actionComments = "Updating the request";
this.TryFire(SwiftRequestTriggers.Update);
SaveChanges();
trans.Commit();
}
}
protected void TryFire(SwiftRequestTriggers trigger)
{
if (!workflow.CanFire(trigger))
{
throw new Exception("Cannot fire " + trigger.ToString() + " from state- " + workflow.State);
}
workflow.Fire(trigger);
}
_