S4M

A Short, Simple, and Straightforward State (S4) Machine Library for .NET

This project is maintained by philiplaureano

S4M.Core - A Short, Simple, and Straightforward State (S4) Machine Library for .NET

Overview

S4M is a state machine library that I built to simplify building more resilient components in a distributed system. It was inspired by this article from Akka.NET that uses Actors with switchable behaviours to create components that can effectively manage themselves.

Why another state machine library?

What makes S4M different from all the other .NET FSM libraries out there?

Features

Why not just use Akka.NET instead of reinventing your own library?

Installing the S4M NuGet Package

Prerequisites

You can download the package here from NuGet.

The Quick Start Guide

  1. Grab the S4M NuGet package here
  2. Inherit your state machine from the StateMachine class, just like in this example:
     public class SampleCircuitBreaker : StateMachine
     {
         // ...
     }
    
  3. Define the initial states as well as the message handlers that will respond to each message as your custom state machine changes state. You can define these handlers inside individual Receive<T> handler methods which will determine how each state responds to a particular message:
     private void Closed()
     {
         Receive<object>(msg =>
         {
             try
             {
                 _commandHandler(msg);
                 _handledMessages.Add(msg);
             }
             catch (Exception e)
             {
                 _exceptionsThrown.Add(e);	            
                 // Something happened - trip the circuit breaker
                 CurrentState = CircuitBreakerState.Open;
                 Become(Open);
             }
         });
     }
     private void Open()
     {
         Receive<object>(msg =>
         {
             try
             {
                 _commandHandler(msg);
                 _handledMessages.Add(msg);	            
                 // If the call worked, let's close the circuit breaker again 
                 CurrentState = CircuitBreakerState.Closed;
                 Become(Closed);
             }
             catch (Exception e)
             {
                 // Ignore the error and keep the circuit breaker open since errors are still occurring
                 _exceptionsThrown.Add(e);
             }
         });
     }
    
  4. Set your initial state in the class constructor, using the Become method, like this:
     public SampleCircuitBreaker(/*...*/)
     {
     	// ...
     	Become(Closed);
     }
    
  5. If you need to stash/defer the current message instead of handling it, call Stash.Stash() method from within a Receive handler.
  6. To unstash all the messages that have been deferred from another state (such as a list of database commands that have been deferred due to a database server outage), call the Stash.UnstashAll() method. Here is a complete example:
     public class SampleUnstasher : StateMachine
     {
         private readonly ConcurrentBag<object> _messagesHandled = new();
         private readonly ConcurrentBag<object> _messagesStashed = new();    
         public SampleUnstasher()
         {
             Become(StashEverything);
         }
         public void StartHandlingMessages()
         {
             Become(NotStashing);
         }        
         private void StashEverything()
         {
             Receive<object>(msg =>
             {
                 _messagesStashed.Add(msg);
                 // Stash every message that comes in
                 Stash.Stash();
             });
         }
         private void NotStashing()
         {
             Receive<object>(msg =>
             {
                 // Unstash every message that wasn't handled
                 Stash.UnstashAll();
                 // Handle every message that comes in
                 _messagesHandled.Add(msg);
             });
         }
         public IEnumerable<object> MessagesStashed => _messagesStashed;
         public IEnumerable<object> MessagesHandled => _messagesHandled;
     }
    
  7. And lastly, if you want to use your new state machine, create a new instance of it and call the TellAsync method:
     var myFSM = new SampleUnstasher();
     var someRandomMessage = Guid.NewGuid();
     await myFSM.TellAsync(someRandomMessage);
    

And that’s pretty much all you need to get started 😁

ChangeLog

You can find the list of the latest changes here.

License

S4M is licensed under the MIT License. It comes with no free warranties expressed or implied, whatsoever.

Questions, Comments, or Feedback?