I wanted to seriously question the current approach for declaring state machines in code using the StateMachine<T> class and IState/IStateMachineProvider interfaces (Predefined State Transitions Created in Code) and hear what you think of it in general. The main reasons behind this interest are the limitations this originally implemented approach has compared to other available solutions and hence associated support costs.
"Yet Another Controller"©
In fact, in certain scenarios, creating a custom Controller with the SingleChoiceAction Action or a set of SimpleAction Actions can be simpler and more straightforward than defining a coded State Machine at a domain level. To give you a concrete example of such an alternative solution, check out this Controller that eventually does the same thing as the code in the original documentation article above, but with old-good SimpleAction objects manipulated on View and other suitable events. You can find another example with a SingleChoiceAction in this test project (it's from this SC ticket, so process it with the Project Converter tool first). Even though the alternative Controller-based approach requires more code, it allows you to provide a more granular control over created states and their visual representations. It is cleaner and easier to understand/extend for other business requirements, because it is your own code that uses only the basic XAF APIs.
Implications of using the questioned approach
Contrary to that, the questioned approach is really a bit shorter with the help of 'state', 'transition' and 'state appearance' abstractions defined near your business class, but everything has its own trade offs.
Contrary to that, the questioned approach is really a bit shorter with the help of 'state', 'transition' and 'state appearance' abstractions defined near your business class, but everything has its own trade offs.
First, the aforementioned abstractions provide only common options that make accomplishing non-typical customizations harder than operating with Actions directly. For instance, you will have to understand the structure and override specialized StateMachineXXX controllers defined in our module if you need to modify the default behavior and visual appearance of the Action items created at runtime based on the state and transitions structure. Another complexity is that our default StateMachine module code is quite generic and updates the Change State Action on various events, which may be unnecessary in your specific scenarios and can even cause unnecessary overhead sometimes.
Our current conclusions
As you might distill from this, our position developed after several years of module maintenance and digesting a lot of user feedback: the Controller/Action-based solution can be and should be considered in the first place instead of using the State Machine module in a good number of scenarios, especially if you need to define a fixed state management process/structure that should not be changed by end-users. So, we are also thinking of not showing the original approach in the online documentation and demos for new XAF users.
Your thoughts are welcome!
Please let me know what you think about the points I made above and tell more about your own experience with this module. In particular, which approach you are favoring more for state management:
1. configuring built-in abstractions for states, transitions, etc. in code;
OR
2. creating Controllers/Actions and managing their state manually.
?
?
I will also be eager to hear about things that you think deserve more attention in the StateMachine module. Thanks for your answers in advance!
We use the current State Machine module quite a lot - the combination with conditions and appearances is great and helps distribute work between developers and professionals. There is only one, but very important missing piece: Almost EVERY state transition requires interaction and/or additional processing. If we take for example the common case State = {draft | AskForApproval | Rejected | Approved | …}. Before rejecting, there is a dialog where a reject reason is to be entered. Before approval, there is a digital signature to be entered, after approval, there is some processing logic to be executed. Therefor we need some easy extension points, where to inject this kind of logic.
ReplyDeleteFor example you could define an interface IStatemachineLogic which can be implemented by the BO with following methods:
- GetBeforeStateTransitionDialog (e) {
if (e.newState = reject) e.DialogObject = new NonPersistentRejectReasonObject();}
- BeforeTransition(e) {
//….
var dialogResult = (NonPersistentRejectReasonObject)e.DialogObject;
e.Cancel = dialogResult.Reason = Empty;}
-AfterTransition(e) {if (e.oldState == abc && e.newState == cde) DoSomething();}
Yes, I agree with the prior post. We use the State Machine very often, because the users can change the Prozess without interaction of developers.
ReplyDeleteIn customer Projects the process of states is one of the most discussed (and changed) part of the project.
An injection interface, as set obove in the prior post, would be a great enhancement.
Thanks for sharing this information! Our team will consider it for the future.
Deletein addition to the above - the question for us is not to configure the states in code, but to design them in the GUI and deliver the data. We created a small tool to transfer database objects like state machines, model differences, reports, role permissions from one database to the other.
ReplyDeleteI think that the main disadvantage of the current statemachine in XAF is that it separates state and transition related logic from the object it concerns. I've several statemachines that contains logic and a related type that also contains logic that also touch state. So there is a chance of state logic fragmentation. Furthermore, if I use appearance rules in my statemachine I've the same problem.
ReplyDeleteThe only advantage of the current statemachine is that you can define the transitions in a structured way.
I think it is better to define the state logic, appearance rules etc within the scope of the type concerned so there is less chance of fragmentation.
And yes, if you need some user interaction to update states, you can create a custom controller. No big deal...
Thanks for your describing your current experience with this module, Arjan!
Delete