Как инкапсулировать конечный автомат без состояния.NET
У меня есть проект, где есть в основном линейный рабочий процесс. Я пытаюсь использовать библиотеку.NET Stateless для работы в качестве механизма рабочего процесса / конечного автомата. Количество примеров там ограничено, но я собрал следующий код:
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
}
Как видите, я использую перегрузку Stateless Fire(), которая позволяет мне передать триггер. Это позволяет мне использовать бизнес-логику процесса конечного автомата, в данном случае код для регистрации нового пациента.
Все это работает, но теперь я хотел бы переместить весь код конечного автомата в другой класс для его инкапсуляции, и у меня возникли проблемы с этим. Проблемы, с которыми я столкнулся при этом:
- создание экземпляра
StateMachine
объект требует от вас указать состояние иState
это свойство только для чтения, которое может быть установлено только при создании экземпляра. - мой
registrationTrigger
должен быть создан во время конфигурации конечного автомата, а также должен быть доступен вызывающему классу.
Как я могу преодолеть эти элементы и инкапсулировать код конечного автомата?
2 ответа
Есть статья Скотта Хансельмана с примером и введением в библиотеку. Также есть несколько примеров, доступных на их GitHub, включая пример реализации Bug, упомянутый в статье Скотта, которая инкапсулирует конечный автомат.
Ниже приведен пример того, как можно извлечь состояние из поведения:
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
}
}
Так я добился этого в своем проекте.
Отдельная логика рабочего процесса для отдельного класса. У меня было несколько рабочих процессов, основанных на одном из флагов, присутствующих в объекте запроса; ниже приведен один из классов рабочего процесса:
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);
}