vendredi 5 mai 2023

How can I create a scalable solution for implementing state transitions in Laravel 7, given that the client frequently requests changes to the states?

I have a project in Laravel 7, and I need to help with good practices and design patterns to implement it, I have a class ManagementPlan who has states, depending on its state when its approved or rejected, updates its state to another depending on the previous state.

The flow of states and transitions is as follows:

  1. Draft: this status can only be approved and goes to the "Awaiting admissibility" state
  2. Awaiting admissibility: this state can be approved and change to "Under Review" or rejected and go to "Admissibility Observed"
  3. Admissibility Observed: this state can only be approved and becomes "Review Admissibility"
  4. Review Admissibility: this state can be approved and changes to "Under Review" or rejected and becomes "Inadmissible"
  5. Under Review: final state approved
  6. Inadmissible: final state rejected

Now, to handle the states and transitions of the ManagementPlan class in a maintainable and scalable way, I've think to implement the State Design Pattern.

Here's an example implementation of the State Design Pattern for my ManagementPlan class:

  • Create an abstract base class called State that defines the interface for all state classes:
abstract class State {
    protected $plan;

    public function __construct(ManagementPlan $plan) {
        $this->plan = $plan;
    }

    abstract public function approve();
    abstract public function reject();
}
  • Create concrete state classes for each state of the ManagementPlan:
class DraftState extends State {
    public function approve() {
        $this->plan->setState(new AwaitingAdmissibilityState($this->plan));
    }

    public function reject() {
        // Do nothing or throw an exception, since a draft can't be rejected
    }
}

class AwaitingAdmissibilityState extends State {
    public function approve() {
        $this->plan->setState(new UnderReviewState($this->plan));
    }

    public function reject() {
        $this->plan->setState(new AdmissibilityObservedState($this->plan));
    }
}

class AdmissibilityObservedState extends State {
    public function approve() {
        $this->plan->setState(new ReviewAdmissibilityState($this->plan));
    }

    public function reject() {
        // Do nothing or throw an exception, since admissibility observed can't be rejected
    }
}

class ReviewAdmissibilityState extends State {
    public function approve() {
        $this->plan->setState(new UnderReviewState($this->plan));
    }

    public function reject() {
        $this->plan->setState(new InadmissibleState($this->plan));
    }
}

class UnderReviewState extends State {
    public function approve() {
        // Do nothing or throw an exception, since under review is already approved
    }

    public function reject() {
        // Do nothing or throw an exception, since under review can't be rejected
    }
}

class InadmissibleState extends State {
    public function approve() {
        // Do nothing or throw an exception, since inadmissible is already rejected
    }

    public function reject() {
        // Do nothing or throw an exception, since inadmissible can't be rejected
    }
}
  • Modify the ManagementPlan class to keep track of its current state and delegate the approve() and reject() methods to the current state:
class ManagementPlan {
    protected $state;

    public function __construct() {
        $this->state = new DraftState($this);
    }

    public function setState(State $state) {
        $this->state = $state;
    }

    public function approve() {
        $this->state->approve();
    }

    public function reject() {
        $this->state->reject();
    }
}

the problem I have with this solution is that the client always adds more states, so I don't know if this is recommended.

I also thought, in create a ManagementPlanState where to storage the states that coould be in the ManagementPlan

class ManagementPlanState {
    protected $fillable = [
        'name',
        'description',
        'state_to_approved',
        'state_to_rejected',
        'users_allow_change_state',
        'process',
        'first_state',
    ];
}

Then in my ManagementPlan class update the state depending of the current state and if it is being approved or rejected.

class ManagementPlan {
    protected $state;

    public function approve() {
        $managementPlanState = ManagementPlanState::where('state', $this->state)->first();
        $this->update([
            'state' => $managementPlanState->state_to_approved
        ]);
    }

    public function reject() {
        $managementPlanState = ManagementPlanState::where('state', $this->state)->first();
        $this->update([
            'state' => $managementPlanState->state_to_rejected
        ]);
    }
}

I would appreciate your experiences in this case and what do you think is the best solution, always taking into account that the client likes to add and change the states.

I'm concerned about the maintainability and scalability of your solution, especially since the client frequently adds and changes the states.

I'm looking for feedback and advice on which solution would be better in terms of maintainability and scalability, given your particular situation.

Aucun commentaire:

Enregistrer un commentaire