Как инкапсулировать конечный автомат без состояния.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);
    }
Другие вопросы по тегам