{"id":408367,"date":"2024-06-29T20:07:45","date_gmt":"2024-06-29T20:07:45","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=408367"},"modified":"-0001-11-30T00:00:00","modified_gmt":"2024-06-29T20:07:45","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=408367","title":{"rendered":"<span>State Management for processes flow<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/61a\/061\/fb9\/61a061fb96a7dc067cb1e7f4f3e5e895.jpeg\" width=\"1280\" height=\"409\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/61a\/061\/fb9\/61a061fb96a7dc067cb1e7f4f3e5e895.jpeg\" data-blurred=\"true\"\/><figcaption><\/figcaption><\/figure>\n<h3>Introduction<\/h3>\n<p>Most of the processes that people use in their work lives can be represented as some object that goes through some flow, just to name a few examples<\/p>\n<ul>\n<li>\n<p><strong>Software engineers using tasks.<\/strong> The tasks are created first, then they are assigned to an engineer, then planned, then in progress and in the end closed, but can be re-opened later.<\/p>\n<\/li>\n<\/ul>\n<ul>\n<li>\n<p><strong>Delivery order. <\/strong>A customer makes a trackable order, then an item gets dispatched, then delivered to a post office, then collected.\u00a0<\/p>\n<\/li>\n<li>\n<p><strong>Influencer post. <\/strong>An influencer uploads video to a social platform, updating a draft, open collaboration with someone who makes a set of changes to the video, publication of the video.\u00a0\u00a0<\/p>\n<\/li>\n<li>\n<p><strong>Recruitment process.\u00a0 <\/strong>Can be rejected or continued.<\/p>\n<\/li>\n<\/ul>\n<p>Each of the flows above contains many different states, but ont things they have in common &#8212; there is always one state where the flow starts and at least one where the flow ends. That\u2019s actually called <a href=\"https:\/\/en.wikipedia.org\/wiki\/Finite-state_machine\" rel=\"noopener noreferrer nofollow\">a finite-state machine<\/a>.<\/p>\n<p>In this article I want to suggest one of many approaches to how to effectively handle flow based systems.\u00a0<\/p>\n<p>For the sake of concrete example, I will use the recruitment process where candidate applications go through a review flow and I will use C# to express some ideas in code. Please note that it&#8217;s a very lightweight implementation where I\u2019m not touching communication with databases and don\u2019t go too much into details on the UI part, my main goal is to share the approach.<\/p>\n<h3>State<\/h3>\n<p>Before going further it\u2019s important to agree on what we understand by a state. Here by state I will mean a description of the object that includes:<\/p>\n<ul>\n<li>\n<p>Position of the object in the flow.\u00a0<\/p>\n<\/li>\n<li>\n<p>Transitions to move further in the flow.<\/p>\n<\/li>\n<li>\n<p>List of allowed mutations within the state.\u00a0<\/p>\n<\/li>\n<li>\n<p>List of permissions for mutations and views. <\/p>\n<\/li>\n<\/ul>\n<h3>Status <\/h3>\n<p>Status is the most important attribute of a flow system as it essentially keeps a snapshot of the system. It\u2019s most often stored in a field of an object as enum value, but there are scenarios in which it can be calculated. For now the field approach is used for our candidate application:<\/p>\n<pre><code class=\"cs\">public enum CandidateApplicationStatus {\u00a0 \u00a0    CV_SCREENING,\u00a0 \u00a0    HR_SCREENING,\u00a0 \u00a0    TECHNICAL_INTERVIEW,\u00a0 \u00a0    HIRED,\u00a0 \u00a0    REJECTED, };<\/code><\/pre>\n<p>And we can make a status property for our application:<\/p>\n<pre><code class=\"cs\">public class CandidateApplication {\u00a0 \u00a0    public CandidateApplicationStatus Status\u00a0 \u00a0    { get; set; } = CandidateApplicationStatus.CV_SCREENING; }<\/code><\/pre>\n<h3>Transitions<\/h3>\n<p>By transition in this article I mean mutations that move an object from one state to another. For example when a candidate application is reviewed by a recruiter it goes to a technical interview stage.\u00a0 As we already exposed Status to public API we can directly set it there, but in general transition from one status to another goes along with some other side actions (for example through observer code pattern to send notification) and validation (we cannot skip some states), so it might be better to make status change through a function.\u00a0<\/p>\n<pre><code class=\"cs\">public class CandidateApplication {\u00a0 \u00a0    public void moveToHRScreening()\u00a0 \u00a0    {\u00a0 \u00a0 \u00a0 \u00a0      _status = CandidateApplicationStatus.HR_SCREENING;\u00a0 \u00a0 \u00a0 \u00a0           \/* ... *\/\u00a0 \u00a0    }\u00a0 \u00a0     public CandidateApplicationStatus getStatus()\u00a0 \u00a0    {\u00a0 \u00a0 \u00a0 \u00a0      return _status;\u00a0 \u00a0    }\u00a0 \u00a0     private CandidateApplicationStatus _status = CandidateApplicationStatus.CV_SCREENING;      \/* ... *\/ }<\/code><\/pre>\n<h3>Actions<\/h3>\n<p>Here by mutation I mean a mutation that changes the object, but doesn\u2019t change its\u00a0 state. Essentially it\u2019s a set of functions that change data within an object.<\/p>\n<pre><code class=\"cs\">public class CandidateApplication {\u00a0 \u00a0    public void saveComment(string newComment)\u00a0 \u00a0    {\u00a0 \u00a0 \u00a0 \u00a0      comment = newComment;\u00a0 \u00a0    }\u00a0 \u00a0     private string? comment;      \/* ... *\/ }<\/code><\/pre>\n<h3>Permissions\u00a0<\/h3>\n<p>Permissions are a set of rules that dictate what users can do and see. For example, a recruiter cannot leave a note on the candidate application that they\u2019ve successfully passed a technical interview, this should be handled by the interviewer.\u00a0<\/p>\n<h4>User<\/h4>\n<p>Here we need to introduce the concept of the user who will load the applications. I won\u2019t go into much detail, in the scope of this article we only care about the user role.<\/p>\n<pre><code class=\"cs\">public enum UserRole {\u00a0 \u00a0    RECRUITER,\u00a0 \u00a0    TECHNICAL_INTERVIEWER,\u00a0 \u00a0    CANDIDATE,\u00a0 \u00a0    ADMINISTRATOR, }<\/code><\/pre>\n<p>And we assign a role to a new user on creation.<\/p>\n<pre><code class=\"cs\">public class User {\u00a0 \u00a0    public User(UserRole role)\u00a0 \u00a0    {\u00a0 \u00a0 \u00a0 \u00a0      Role = role;\u00a0 \u00a0    }    public UserRole Role\u00a0 \u00a0    { get; init; }\u00a0 \u00a0     \/* ... *\/ }<\/code><\/pre>\n<p>This user is a representation of an actual user who owns the session. For example a recruiter who opened an \u201cInterview\u201d tool.\u00a0<\/p>\n<h4>Permission aware object<\/h4>\n<p>Now that we have a way to understand who is currently working with the data we can load it in a privacy aware manner. In general it means that when creating an instance of a candidate application we pass the user and before fetching data from a database we check if the user should have access to it at this point of time.<\/p>\n<pre><code class=\"cs\">public class CandidateApplication {\u00a0 \u00a0    public CandidateApplication(User user)\u00a0 \u00a0    {\u00a0 \u00a0 \u00a0 \u00a0      User = user;\u00a0 \u00a0 \u00a0 \u00a0       \/* Do (user role, status) check and Load data *\/\u00a0 \u00a0    }\u00a0 \u00a0     public User User { get; init; }\u00a0 \u00a0     \/* ... *\/ }<\/code><\/pre>\n<p>It also gives us a way to do permission check in all the functions (both mutation and view) of the candidate application.<\/p>\n<pre><code class=\"cs\">public void editTechnicalReviewNote(string updatedReviewNote) {\u00a0 \u00a0    if (User.Role == UserRole.CANDIDATE)\u00a0 \u00a0    {\u00a0 \u00a0 \u00a0 \u00a0      return;   }\u00a0 \u00a0       if (_status == CandidateApplicationStatus.HIRED ||        _status == CandidateApplicationStatus.REJECTED)\u00a0 \u00a0   {\u00a0 \u00a0 \u00a0 \u00a0      return;\u00a0 \u00a0    }\u00a0 \u00a0       if (User.Role == UserRole.ADMINISTRATOR &amp;&amp;        _status != CandidateApplicationStatus.HR_SCREENING)\u00a0 \u00a0    {\u00a0 \u00a0 \u00a0 \u00a0      return;\u00a0 \u00a0    }\u00a0 \u00a0       if (User.Role == UserRole.TECHNICAL_INTERVIEWER        &amp;&amp; _status != CandidateApplicationStatus.TECHNICAL_INTERVIEW)\u00a0 \u00a0    {\u00a0 \u00a0 \u00a0 \u00a0      return;\u00a0 \u00a0    }\u00a0 \u00a0       \/* Update note *\/ }<\/code><\/pre>\n<p>But in a very cumbersome and hard to maintain way. Here we go to abstractions for help!\u00a0<\/p>\n<h3>State Management Abstraction\u00a0<\/h3>\n<p>Some problems with the approach above:<\/p>\n<ul>\n<li>\n<p>Maintainability of mutation permissions.\u00a0 Just imagine that you need to write that many ifs in every mutation or view function<\/p>\n<\/li>\n<li>\n<p>Maintainability of transitions. Similar to permissions they need to check the status and permissions.\u00a0<\/p>\n<\/li>\n<\/ul>\n<p>Let\u2019s abstract the permission logic to a state abstraction with three main features:<\/p>\n<ul>\n<li>\n<p>Every state will handle permission for viewing, actions and transitions by giving user specific permission tokens that are valid in some state.\u00a0<\/p>\n<\/li>\n<li>\n<p>States contain all possible transitions to available states.\u00a0<\/p>\n<\/li>\n<li>\n<p>Every state maps to one and only one status.<\/p>\n<\/li>\n<\/ul>\n<p>You might notice that this is actually a structure for a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Directed_graph\" rel=\"noopener noreferrer nofollow\">directed graph<\/a> (or a finite state machine)!\u00a0<\/p>\n<p>Here is a piece of code that suggests requirements for the states<\/p>\n<pre><code class=\"cs\">using TransitionDictionary = Dictionary&lt;CandidateApplicationStatus, Action&lt;CandidateApplication>>; using UserRolePermissionTokensDictionary = Dictionary&lt;UserRole, List&lt;CandidatePermissionToken>>;  public enum CandidatePermissionToken {\u00a0 \u00a0    TRANSIT,\u00a0 \u00a0    EDIT_TECHNICAL_REVIEW_NOTES }  public abstract class CandidateApplicationState {\u00a0 \u00a0    abstract public UserRolePermissionTokensDictionary gePermissionTokensForUserRole();\u00a0 \u00a0    abstract public TransitionDictionary getTransitions();\u00a0 \u00a0       public bool hasPermissionTokenForRole(CandidatePermissionToken permissionToken, UserRole role)\u00a0 \u00a0    {\u00a0 \u00a0 \u00a0 \u00a0      return\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        gePermissionTokensForUserRole()\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        .GetValueOrDefault(role, new List&lt;CandidatePermissionToken>())\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        .Contains(permissionToken);\u00a0 \u00a0    }\u00a0 \u00a0       public bool canTransitToStatus(CandidateApplicationStatus status, UserRole role)\u00a0 \u00a0    {\u00a0 \u00a0 \u00a0 \u00a0      return getTransitions().ContainsKey(status) &amp;&amp;\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        hasPermissionTokenForRole(CandidatePermissionToken.TRANSIT, role);\u00a0 \u00a0    } }<\/code><\/pre>\n<p>Here I put an example of a CV_SREEENING state implementation<\/p>\n<pre><code class=\"cs\">public sealed class CandidateApplicationCVScreeningState : CandidateApplicationState {\u00a0 \u00a0    public override UserRolePermissionTokensDictionary gePermissionTokensForUserRole()\u00a0 \u00a0    {\u00a0 \u00a0 \u00a0 \u00a0      return new UserRolePermissionTokensDictionary(){\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        {\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0          UserRole.RECRUITER,\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0          new List&lt;CandidatePermissionToken>(){\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0            CandidatePermissionToken.TRANSIT\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0          }\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        },\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        {\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0          UserRole.ADMINISTRATOR,\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0          new List&lt;CandidatePermissionToken>(){\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0            CandidatePermissionToken.TRANSIT,\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0            CandidatePermissionToken.EDIT_TECHNICAL_REVIEW_NOTES\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0          }\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        }\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0      };\u00a0 \u00a0    }\u00a0 \u00a0     public override TransitionDictionary getTransitions()\u00a0 \u00a0    {\u00a0 \u00a0 \u00a0 \u00a0      return new TransitionDictionary() {\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        {\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0          CandidateApplicationStatus.HR_SCREENING,\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0          application => application.moveToHRScreening()\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        }\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0      };\u00a0 \u00a0    } }<\/code><\/pre>\n<p>Now if by status we can get state, the implementation of permission checks would be just a couple of lines that are also very easy to read.\u00a0 We can do this by just putting status to state transition in one place.\u00a0<\/p>\n<pre><code class=\"cs\">public sealed class CandidateApplicationFlow {\u00a0 \u00a0    public static CandidateApplicationState getStateForStatus(\u00a0 \u00a0 \u00a0 \u00a0      CandidateApplicationStatus status\u00a0 \u00a0    ) => status switch\u00a0 \u00a0    {\u00a0 \u00a0 \u00a0 \u00a0      CandidateApplicationStatus.CV_SCREENING =>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        new CandidateApplicationCVScreeningState(),\u00a0 \u00a0 \u00a0 \u00a0      CandidateApplicationStatus.HR_SCREENING =>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        new CandidateApplicationHRScreeningState(),\u00a0 \u00a0 \u00a0 \u00a0      CandidateApplicationStatus.TECHNICAL_INTERVIEW => \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        new CandidateApplicationTechnicalInterviewState(),\u00a0 \u00a0 \u00a0 \u00a0      _ => throw new ArgumentOutOfRangeException(nameof(status)),\u00a0 \u00a0    }; }<\/code><\/pre>\n<p>With that <strong>editTechnicalReviewNote <\/strong>method turn into this  <\/p>\n<pre><code class=\"cs\">public void editTechnicalReviewNote(string updatedReviiewNote) {\u00a0 \u00a0    var state = CandidateApplicationFlow.getStateForStatus(_status);\u00a0 \u00a0    if (!state.hasPermissionTokenForRole(CandidatePermissionToken.EDIT_TECHNICAL_REVIEW_NOTES, User.Role))\u00a0 \u00a0    {\u00a0 \u00a0 \u00a0 \u00a0      return;\u00a0 \u00a0    }\u00a0 \u00a0     \/* Update note *\/ }<\/code><\/pre>\n<p>And we can use canTransitToStatus to do additional validation check when transitioning between states.\u00a0<\/p>\n<pre><code class=\"cs\">public void moveToHRScreening() {\u00a0 \u00a0    var state = CandidateApplicationFlow.getStateForStatus(_status);\u00a0 \u00a0    if (!state.canTransitToStatus(_status, User.Role))\u00a0 \u00a0    {\u00a0 \u00a0 \u00a0 \u00a0      return;\u00a0 \u00a0    }\u00a0 \u00a0    _status = CandidateApplicationStatus.HR_SCREENING;\u00a0 \u00a0       \/* ... *\/ }<\/code><\/pre>\n<h3>Testing<\/h3>\n<p>Representing the system as a finite state machine has a huge advantage when it comes to testing as it allows to easily move the system to the needed state and go from there.\u00a0<\/p>\n<p>One of the ways it can be implemented is by creating an object in the start state and then using state transitions this object can be moved to the target state.\u00a0For finding a path of transitions we can traverse the state machine with bfs:<\/p>\n<pre><code class=\"cs\">namespace DemoCandidateApplication {\u00a0 \u00a0    using TransitionDictionary = Dictionary&lt;CandidateApplicationStatus, List&lt;Action&lt;CandidateApplication>>>;\u00a0 \u00a0       public class CandidateApplicationTestPromoter\u00a0 \u00a0    {\u00a0 \u00a0 \u00a0 \u00a0      public CandidateApplicationTestPromoter(CandidateApplication testApplication)\u00a0 \u00a0 \u00a0 \u00a0      {\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        _application = testApplication;\u00a0 \u00a0 \u00a0 \u00a0      }\u00a0 \u00a0 \u00a0 \u00a0           public CandidateApplication promote(CandidateApplicationStatus status)\u00a0 \u00a0 \u00a0 \u00a0      {\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        var transitionsToStatus = transitionsToStatusBFSMap(_application.Status);\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        foreach (var transition in transitionsToStatus[status])\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        {\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0          transition(_application);\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        }\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        return _application;\u00a0 \u00a0 \u00a0 \u00a0      }\u00a0 \u00a0 \u00a0 \u00a0           private TransitionDictionary transitionsToStatusBFSMap(CandidateApplicationStatus startStatus)\u00a0 \u00a0 \u00a0 \u00a0      {\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        var statusQueue = new Queue&lt;CandidateApplicationStatus>();\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        statusQueue.Enqueue(startStatus);              var transitionsToStatus = new TransitionDictionary() {\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0          {startStatus, new List&lt;Action&lt;CandidateApplication>>()}\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        };\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0               while (statusQueue.Count > 0)\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        {\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0          var currentStatus = statusQueue.Dequeue();\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0          var currentState = CandidateApplicationFlow.getStateForStatus(currentStatus);\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0          foreach (var entry in currentState.getTransactions())\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0          {\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0            if (transitionsToStatus.ContainsKey(entry.Key))\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0            {\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0              continue;\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0            }\u00a0 \u00a0 \u00a0 \u00a0 \u00a0  \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0             \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0            transitionsToStatus.Add(\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0              entry.Key,\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0              transitionsToStatus[currentStatus].Append(entry.Value).ToList()\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0            );\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0            statusQueue.Enqueue(entry.Key);\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0          }\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        }\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0               return transitionsToStatus;\u00a0 \u00a0 \u00a0 \u00a0      }\u00a0 \u00a0 \u00a0 \u00a0           private CandidateApplication _application;\u00a0 \u00a0    } }<\/code><\/pre>\n<p>This makes it extremely easy to quickly transition to a valid state of the candidate application and then use it to test some specific features.\u00a0<\/p>\n<pre><code class=\"cs\">public class CandidateApplicationTransitionTest {\u00a0 \u00a0    [Fact]\u00a0 \u00a0    public void ApplicationPromotionPathTest()\u00a0 \u00a0    {\u00a0 \u00a0 \u00a0 \u00a0 \u00a0      var user = new User(UserRole.RECRUITER);\u00a0 \u00a0 \u00a0 \u00a0      var application = new CandidateApplication(user);\u00a0 \u00a0 \u00a0 \u00a0      var promoter = new CandidateApplicationTestPromoter(application);\u00a0 \u00a0 \u00a0 \u00a0           promoter.promote(CandidateApplicationStatus.HR_SCREENING);\u00a0 \u00a0 \u00a0 \u00a0      Assert.Equal(CandidateApplicationStatus.HR_SCREENING, application.getStatus());\u00a0 \u00a0 \u00a0 \u00a0           promoter.promote(CandidateApplicationStatus.TECHNICAL_INTERVIEW);\u00a0 \u00a0 \u00a0 \u00a0      Assert.Equal(CandidateApplicationStatus.TECHNICAL_INTERVIEW, application.getStatus());\u00a0 \u00a0    } }<\/code><\/pre>\n<h3>Combination with UI\u00a0<\/h3>\n<p>This approach is extremely beneficial when you start working with UI, because all actions and transitions are gated with permission tokens and you don\u2019t have to duplicate logic on frontend and backend to tell when some button should be available or not, you just need to expose permission tokens (say with graphql) and check for need token presence.\u00a0<\/p>\n<h3>Multiple active states\u00a0\u00a0<\/h3>\n<p>There are certain cases, when you want your state machine to have active not one, but two or more active states. In this case one of the approaches is to not store the status as a field, but calculate it based on the change log of the object and do small modification to state code to unify data from different states.\u00a0<\/p>\n<p>One approach that I find interesting for this case, but to be fair quite hard to support future migrations is using gating for start and finish in the form of Disjoint Normal Form. For example if we add a new state to a candidate application called DESIGN_INTERVIEW that we want to handle simultaneously with TECHNICAL_INTERVIEW we can represent this with history gating like this <\/p>\n<ul>\n<li>\n<p>Status is DESIGN_INTERVIEW if history includes <em>(CV_SCREENING_FINISH &amp; DESIGN_INTERVIEW_STARTED)<\/em> records and doesn\u2019t include <em>(DESIGN_INTERVIEW_FINISHED_WITH_SUCCESS) | (DESIGN_INTERVIEW_FINISHED_WITH_FAILURE)<\/em>.<\/p>\n<\/li>\n<li>\n<p>Status is TECHNICAL_INTERVIEW if history includes <em>(CV_SCREENING_FINISH &amp; TECHNICAL_INTERVIEW_STARTED)<\/em> records and doesn\u2019t include <em>(TECHNICAL_INTERVIEW_FINISHED_WITH_SUCCESS) | (TECHNICAL_INTERVIEW _FINISHED_WITH_FAILURE)<\/em>.<\/p>\n<\/li>\n<li>\n<p>Status is HIRED if history includes <em>(TECHNICAL_INTERVIEW_FINISHED_WITH_SUCCESS &amp; DESIGN_INTERVIEW_FINISHED_WITH_SUCCESS<\/em>).\u00a0<\/p>\n<\/li>\n<\/ul>\n<p>But again, here we sacrifice a lot on maintainability, I just find this method interesting.\u00a0<\/p>\n<h3>Summary\u00a0<\/h3>\n<p>In this article I shared an approach to work with flow systems representing them as finite state machine that is pretty easy to maintain, test and leverage on the UI. This approach can be extended even further removing dependency of object from its state definition by abstracting mutation in a separate builder class and checking mutation on commitments but this topic might be  worth of another article!\u00a0<\/p>\n<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/718760\/\"> https:\/\/habr.com\/ru\/articles\/718760\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<h3>Introduction<\/h3>\n<p>Most of the processes that people use in their work lives can be represented as some object that goes through some flow, just to name a few examples<\/p>\n<ul>\n<li>\n<p><strong>Software engineers using tasks.<\/strong> The tasks are created first, then they are assigned to an engineer, then planned, then in progress and in the end closed, but can be re-opened later.<\/p>\n<\/li>\n<\/ul>\n<ul>\n<li>\n<p><strong>Delivery order. <\/strong>A customer makes a trackable order, then an item gets dispatched, then delivered to a post office, then collected.\u00a0<\/p>\n<\/li>\n<li>\n<p><strong>Influencer post. <\/strong>An influencer uploads video to a social platform, updating a draft, open collaboration with someone who makes a set of changes to the video, publication of the video.\u00a0\u00a0<\/p>\n<\/li>\n<li>\n<p><strong>Recruitment process.\u00a0 <\/strong>Can be rejected or continued.<\/p>\n<\/li>\n<\/ul>\n<p>Each of the flows above contains many different states, but ont things they have in common &#8212; there is always one state where the flow starts and at least one where the flow ends. That\u2019s actually called <a href=\"https:\/\/en.wikipedia.org\/wiki\/Finite-state_machine\" rel=\"noopener noreferrer nofollow\">a finite-state machine<\/a>.<\/p>\n<p>In this article I want to suggest one of many approaches to how to effectively handle flow based systems.\u00a0<\/p>\n<p>For the sake of concrete example, I will use the recruitment process where candidate applications go through a review flow and I will use C# to express some ideas in code. Please note that it&#8217;s a very lightweight implementation where I\u2019m not touching communication with databases and don\u2019t go too much into details on the UI part, my main goal is to share the approach.<\/p>\n<h3>State<\/h3>\n<p>Before going further it\u2019s important to agree on what we understand by a state. Here by state I will mean a description of the object that includes:<\/p>\n<ul>\n<li>\n<p>Position of the object in the flow.\u00a0<\/p>\n<\/li>\n<li>\n<p>Transitions to move further in the flow.<\/p>\n<\/li>\n<li>\n<p>List of allowed mutations within the state.\u00a0<\/p>\n<\/li>\n<li>\n<p>List of permissions for mutations and views. <\/p>\n<\/li>\n<\/ul>\n<h3>Status <\/h3>\n<p>Status is the most important attribute of a flow system as it essentially keeps a snapshot of the system. It\u2019s most often stored in a field of an object as enum value, but there are scenarios in which it can be calculated. For now the field approach is used for our candidate application:<\/p>\n<pre><code class=\"cs\">public enum CandidateApplicationStatus {\u00a0 \u00a0    CV_SCREENING,\u00a0 \u00a0    HR_SCREENING,\u00a0 \u00a0    TECHNICAL_INTERVIEW,\u00a0 \u00a0    HIRED,\u00a0 \u00a0    REJECTED, };<\/code><\/pre>\n<p>And we can make a status property for our application:<\/p>\n<pre><code class=\"cs\">public class CandidateApplication {\u00a0 \u00a0    public CandidateApplicationStatus Status\u00a0 \u00a0    { get; set; } = CandidateApplicationStatus.CV_SCREENING; }<\/code><\/pre>\n<h3>Transitions<\/h3>\n<p>By transition in this article I mean mutations that move an object from one state to another. For example when a candidate application is reviewed by a recruiter it goes to a technical interview stage.\u00a0 As we already exposed Status to public API we can directly set it there, but in general transition from one status to another goes along with some other side actions (for example through observer code pattern to send notification) and validation (we cannot skip some states), so it might be better to make status change through a function.\u00a0<\/p>\n<pre><code class=\"cs\">public class CandidateApplication {\u00a0 \u00a0    public void moveToHRScreening()\u00a0 \u00a0    {\u00a0 \u00a0 \u00a0 \u00a0      _status = CandidateApplicationStatus.HR_SCREENING;\u00a0 \u00a0 \u00a0 \u00a0           \/* ... *\/\u00a0 \u00a0    }\u00a0 \u00a0     public CandidateApplicationStatus getStatus()\u00a0 \u00a0    {\u00a0 \u00a0 \u00a0 \u00a0      return _status;\u00a0 \u00a0    }\u00a0 \u00a0     private CandidateApplicationStatus _status = CandidateApplicationStatus.CV_SCREENING;      \/* ... *\/ }<\/code><\/pre>\n<h3>Actions<\/h3>\n<p>Here by mutation I mean a mutation that changes the object, but doesn\u2019t change its\u00a0 state. Essentially it\u2019s a set of functions that change data within an object.<\/p>\n<pre><code class=\"cs\">public class CandidateApplication {\u00a0 \u00a0    public void saveComment(string newComment)\u00a0 \u00a0    {\u00a0 \u00a0 \u00a0 \u00a0      comment = newComment;\u00a0 \u00a0    }\u00a0 \u00a0     private string? comment;      \/* ... *\/ }<\/code><\/pre>\n<h3>Permissions\u00a0<\/h3>\n<p>Permissions are a set of rules that dictate what users can do and see. For example, a recruiter cannot leave a note on the candidate application that they\u2019ve successfully passed a technical interview, this should be handled by the interviewer.\u00a0<\/p>\n<h4>User<\/h4>\n<p>Here we need to introduce the concept of the user who will load the applications. I won\u2019t go into much detail, in the scope of this article we only care about the user role.<\/p>\n<pre><code class=\"cs\">public enum UserRole {\u00a0 \u00a0    RECRUITER,\u00a0 \u00a0    TECHNICAL_INTERVIEWER,\u00a0 \u00a0    CANDIDATE,\u00a0 \u00a0    ADMINISTRATOR, }<\/code><\/pre>\n<p>And we assign a role to a new user on creation.<\/p>\n<pre><code class=\"cs\">public class User {\u00a0 \u00a0    public User(UserRole role)\u00a0 \u00a0    {\u00a0 \u00a0 \u00a0 \u00a0      Role = role;\u00a0 \u00a0    }    public UserRole Role\u00a0 \u00a0    { get; init; }\u00a0 \u00a0     \/* ... *\/ }<\/code><\/pre>\n<p>This user is a representation of an actual user who owns the session. For example a recruiter who opened an \u201cInterview\u201d tool.\u00a0<\/p>\n<h4>Permission aware object<\/h4>\n<p>Now that we have a way to understand who is currently working with the data we can load it in a privacy aware manner. In general it means that when creating an instance of a candidate application we pass the user and before fetching data from a database we check if the user should have access to it at this point of time.<\/p>\n<pre><code class=\"cs\">public class CandidateApplication {\u00a0 \u00a0    public CandidateApplication(User user)\u00a0 \u00a0    {\u00a0 \u00a0 \u00a0 \u00a0      User = user;\u00a0 \u00a0 \u00a0 \u00a0       \/* Do (user role, status) check and Load data *\/\u00a0 \u00a0    }\u00a0 \u00a0     public User User { get; init; }\u00a0 \u00a0     \/* ... *\/ }<\/code><\/pre>\n<p>It also gives us a way to do permission check in all the functions (both mutation and view) of the candidate application.<\/p>\n<pre><code class=\"cs\">public void editTechnicalReviewNote(string updatedReviewNote) {\u00a0 \u00a0    if (User.Role == UserRole.CANDIDATE)\u00a0 \u00a0    {\u00a0 \u00a0 \u00a0 \u00a0      return;   }\u00a0 \u00a0       if (_status == CandidateApplicationStatus.HIRED ||        _status == CandidateApplicationStatus.REJECTED)\u00a0 \u00a0   {\u00a0 \u00a0 \u00a0 \u00a0      return;\u00a0 \u00a0    }\u00a0 \u00a0       if (User.Role == UserRole.ADMINISTRATOR &amp;&amp;        _status != CandidateApplicationStatus.HR_SCREENING)\u00a0 \u00a0    {\u00a0 \u00a0 \u00a0 \u00a0      return;\u00a0 \u00a0    }\u00a0 \u00a0       if (User.Role == UserRole.TECHNICAL_INTERVIEWER        &amp;&amp; _status != CandidateApplicationStatus.TECHNICAL_INTERVIEW)\u00a0 \u00a0    {\u00a0 \u00a0 \u00a0 \u00a0      return;\u00a0 \u00a0    }\u00a0 \u00a0       \/* Update note *\/ }<\/code><\/pre>\n<p>But in a very cumbersome and hard to maintain way. Here we go to abstractions for help!\u00a0<\/p>\n<h3>State Management Abstraction\u00a0<\/h3>\n<p>Some problems with the approach above:<\/p>\n<ul>\n<li>\n<p>Maintainability of mutation permissions.\u00a0 Just imagine that you need to write that many ifs in every mutation or view function<\/p>\n<\/li>\n<li>\n<p>Maintainability of transitions. Similar to permissions they need to check the status and permissions.\u00a0<\/p>\n<\/li>\n<\/ul>\n<p>Let\u2019s abstract the permission logic to a state abstraction with three main features:<\/p>\n<ul>\n<li>\n<p>Every state will handle permission for viewing, actions and transitions by giving user specific permission tokens that are valid in some state.\u00a0<\/p>\n<\/li>\n<li>\n<p>States contain all possible transitions to available states.\u00a0<\/p>\n<\/li>\n<li>\n<p>Every state maps to one and only one status.<\/p>\n<\/li>\n<\/ul>\n<p>You might notice that this is actually a structure for a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Directed_graph\" rel=\"noopener noreferrer nofollow\">directed graph<\/a> (or a finite state machine)!\u00a0<\/p>\n<p>Here is a piece of code that suggests requirements for the states<\/p>\n<pre><code class=\"cs\">using TransitionDictionary = Dictionary&lt;CandidateApplicationStatus, Action&lt;CandidateApplication>>; using UserRolePermissionTokensDictionary = Dictionary&lt;UserRole, List&lt;CandidatePermissionToken>>;  public enum CandidatePermissionToken {\u00a0 \u00a0    TRANSIT,\u00a0 \u00a0    EDIT_TECHNICAL_REVIEW_NOTES }  public abstract class CandidateApplicationState {\u00a0 \u00a0    abstract public UserRolePermissionTokensDictionary gePermissionTokensForUserRole();\u00a0 \u00a0    abstract public TransitionDictionary getTransitions();\u00a0 \u00a0       public bool hasPermissionTokenForRole(CandidatePermissionToken permissionToken, UserRole role)\u00a0 \u00a0    {\u00a0 \u00a0 \u00a0 \u00a0      return\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        gePermissionTokensForUserRole()\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        .GetValueOrDefault(role, new List&lt;CandidatePermissionToken>())\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        .Contains(permissionToken);\u00a0 \u00a0    }\u00a0 \u00a0       public bool canTransitToStatus(CandidateApplicationStatus status, UserRole role)\u00a0 \u00a0    {\u00a0 \u00a0 \u00a0 \u00a0      return getTransitions().ContainsKey(status) &amp;&amp;\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        hasPermissionTokenForRole(CandidatePermissionToken.TRANSIT, role);\u00a0 \u00a0    } }<\/code><\/pre>\n<p>Here I put an example of a CV_SREEENING state implementation<\/p>\n<pre><code class=\"cs\">public sealed class CandidateApplicationCVScreeningState : CandidateApplicationState {\u00a0 \u00a0    public override UserRolePermissionTokensDictionary gePermissionTokensForUserRole()\u00a0 \u00a0    {\u00a0 \u00a0 \u00a0 \u00a0      return new UserRolePermissionTokensDictionary(){\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        {\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0          UserRole.RECRUITER,\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0          new List&lt;CandidatePermissionToken>(){\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0            CandidatePermissionToken.TRANSIT\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0          }\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        },\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        {\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0          UserRole.ADMINISTRATOR,\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0          new List&lt;CandidatePermissionToken>(){\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0            CandidatePermissionToken.TRANSIT,\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0            CandidatePermissionToken.EDIT_TECHNICAL_REVIEW_NOTES\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0          }\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        }\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0      };\u00a0 \u00a0    }\u00a0 \u00a0     public override TransitionDictionary getTransitions()\u00a0 \u00a0    {\u00a0 \u00a0 \u00a0 \u00a0      return new TransitionDictionary() {\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        {\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0          CandidateApplicationStatus.HR_SCREENING,\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0          application => application.moveToHRScreening()\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        }\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0      };\u00a0 \u00a0    } }<\/code><\/pre>\n<p>Now if by status we can get state, the implementation of permission checks would be just a couple of lines that are also very easy to read.\u00a0 We can do this by just putting status to state transition in one place.\u00a0<\/p>\n<pre><code class=\"cs\">public sealed class CandidateApplicationFlow {\u00a0 \u00a0    public static CandidateApplicationState getStateForStatus(\u00a0 \u00a0 \u00a0 \u00a0      CandidateApplicationStatus status\u00a0 \u00a0    ) => status switch\u00a0 \u00a0    {\u00a0 \u00a0 \u00a0 \u00a0      CandidateApplicationStatus.CV_SCREENING =>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        new CandidateApplicationCVScreeningState(),\u00a0 \u00a0 \u00a0 \u00a0      CandidateApplicationStatus.HR_SCREENING =>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        new CandidateApplicationHRScreeningState(),\u00a0 \u00a0 \u00a0 \u00a0      CandidateApplicationStatus.TECHNICAL_INTERVIEW => \u00a0 \u00a0 \u00a0 \u00a0 \u00a0        new CandidateApplicationTechnicalInterviewState(),\u00a0 \u00a0 \u00a0 \u00a0      _ => throw new ArgumentOutOfRangeException(nameof(status)),\u00a0 \u00a0    }; }<\/code><\/pre>\n<p>With that <strong>editTechnicalReviewNote <\/strong>method turn into this  <\/p>\n<pre><code class=\"cs\">publ<\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-408367","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/408367","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=408367"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/408367\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=408367"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=408367"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=408367"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}