Tengo un proyecto en el que hay un flujo de trabajo principalmente lineal. Estoy intentando usar la biblioteca .NET Stateless para actuar como motor de flujo de trabajo/máquina de estado. La cantidad de ejemplos disponibles es limitada, pero he compilado el siguiente código:
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 }Como puede ver, estoy usando la sobrecarga Stateless Fire() que me permite pasar un disparador. Esto es para que pueda hacer que la máquina de estado procese la lógica comercial, en este caso, el código para registrar un nuevo paciente.
Todo esto funciona, pero ahora me gustaría mover todo el código de la máquina de estado a otra clase para encapsularlo y tengo problemas para hacerlo. Los desafíos que he tenido al hacer esto son:
StateMachine requiere que especifique el estado y State es una propiedad de solo lectura que solo se puede establecer en la instanciación.registrationTrigger debe ser instanciado durante la configuración de la máquina de estado y también debe estar disponible para la clase que llama.¿Cómo puedo superar estos elementos y encapsular el código de máquina de estado?
Hay un artículo de Scott Hanselman con un ejemplo y una introducción a una biblioteca. También hay algunos ejemplos disponibles en su GitHub, incluido el ejemplo de implementación de errores mencionado en el artículo de Scott que encapsula la máquina de estado.
A continuación se muestra un ejemplo de cómo se puede extraer el estado del comportamiento:
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 } }Así lo logré en mi proyecto.
Lógica de flujo de trabajo separada para separar la clase. Tuve un par de flujos de trabajo basados en uno de los indicadores presentes en el objeto de solicitud; a continuación se muestra una de las clases de flujo de trabajo:
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); ..................... }Que se activa desde el controlador/cualquier otra capa:
private static void UpdateRequest(SwiftRequestDTO dtoRequest) { var workflow = WorkflowFactory.Get(request); workflow.UpdateRequest(); } Como se mencionó anteriormente, tenía diferentes reglas de flujo de trabajo basadas en las condiciones del objeto de solicitud y, por lo tanto, usaba un patrón de fábrica WorkflowFactory.Get(request) ; puede crear una instancia de su flujo de trabajo/inyectarlo como desee
Y dentro de la clase de flujo de trabajo (clase BaseWorkflow en mi caso), he expuesto las acciones:
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); }