0

I am building a .Net Core Blazor Server application. The application creates records of a Project class which move through a workflow with various phases of review and acceptance/rejection before completion.

I am trying to determine the best place to incorporate various business rules around who can modify the Project's state and when. Specifically, I'm trying to determine if it's ok to add current user data and claims principal data into an object's state.

As a simplified example, let's say the Project has three states: DraftState, ReviewState, and CompleteState. In order to determine who can move this project through the workflow, we consider three pieces of information:

  • The current state of the project (where it is in the workflow)
  • If the current user's role, either NormalUser, ProjectReviewer, or Admin
  • If the current user is listed as the project's Designer, meaning Project.DesignerId == CurrentUser.Id

The desired rules are as follows:

  • From DraftState, the project's designer or any user in the Admin role can move it to ReviewState
  • From ReviewState, any user in the ProjectReviewer or in the Admin role can move it to CompleteState or return it to DraftState
  • From the CompleteState, the project can't be modified further.

I built my Project class to contain a standard state pattern to control the object's behavior based on state, but I'm not sure what the best practice is for incorporating the user and roles based rules to this system.

As I can tell, I have three options

  • Build the UI in Blazor the limit access to the Project's state methods by looking at the user's data and showing/hiding UI controls. This requires all sorts of If/else statements and nests. Not extensible.
  • Pass the CurrentUser data and the ClaimsPrincipal.IsInRole() results into the state of the project, so that the project's state can accurately analyze ALL of the required business rules and change behavior. Superficially, this strikes me as the most straightforward solution, BUT, but I worry about injecting UI and Roles-based information from Blazor into my data classes. I don't know if this is appropriate
  • Wrap the Project state pattern into a larger state pattern that controls workflow behavior. This is an additional level of abstraction, but it lets me incorporate the three data sources (CUrrentUser, CurrentUserRoles, and IsInRole("Admin") in separate groups wit their own concerns. I have no code sample for this, but one would be much appreciated.

My goals for all of this is to:

  1. Accurately follow an object through the workflow
  2. Simplify long-term management
  3. Allow for future modification and additional steps in the workflow

My apologies if this belong in Code Review or some other network. I'm happy to move it.

1 Answer 1

1

When the only thing which changes with the Project's state is the list of permitted states which are allowed to come next (under certain conditions), I am not sure if your current approach of using a state pattern is really justified. At a first glance, it seems to add unncessary complexity. Of course, I may not know enough about your case in full. Still I would avoid distributing the business rules over several child classes of a base class State, since changes to those rules would typically cause changes to all childs and the base class, which is not really desirable.

The most simple solution I can think of is this:

  • I would make a separate service class NextStatesService which gets the Project's state, the current user's role, their relationship to the project and whatever else is required for evaluating the business rules.

  • The service then provides a function which returns the list of available "next states" under the current conditions. Of course, if you need the information in different forms for controlling the UI and/or the workflow, feel free to add such functions to the service as well.

The advantage is that only this new service will depend on your data classes and UI/role-based classes, so this doesn't lead to a dependency of the Project on these classes (not even one which needs to be injected at run time). I don't know if these dependencies would really cause some trouble in your case, that is something you can find out by writing some tests for the Project class and check if the test setup starts to make trouble when you try to move the state logic into the Project class itself.

How you implement the service internally is something I would judge when seeing the complexity of the code and the actual rules beyond your (probably simplified) example. When a few conditionals are sufficient, one can keep those. When the code appears to become too complex, one could switch to a decision-table based approached, or delegate parts of the logic to the Project class, or other classes like User and Role, assumed those are under your control and not part of the framework.

When you decide for keeping the State pattern, parts of the logic might also be delegated to the different State subclasses, ideally things which are mostly stable in this model. But as I wrote, I would not start with this design, but try a simpler approach first and only refactor to more classes when the codebase grows and the overhead for these classes is clearly justified.

TLDR: delegate the problem to a third instance which adds another level of indirection. Think big, but start small. Use the most simply solution you can think of which does not introduce unwanted dependencies, and refactor to more classes only when needed.

1
  • Thanks much for you input. I refactored my code, and I do think that it's much easier to follow. The service did end up with a lot of very, very similar (but not identical) switch statements that map out what ProjectStatus can go to what other status depending on user and roles. Although at first glance it appears repetitive, having it all in one class allows you to compare all of them more readily rather than navigating through the State pattern classes. My state pattern approach did make each individual state more clear-cut and extensible, but it hid the big picture of the workflow rules.
    – aterbo
    Commented Mar 1, 2023 at 5:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.