Okay, so this writing came up after my senior coworkers told me that you can use finite state machine abstractions to see if the database I/O connection is still running or closed. And this makes me think back to the lessons about “Teori Bahasa dan Otomata” when i was still in college, and its kind of like a “goddamn” moment, considering after more than 5 years finally i can understand the usefulness of these courses hahahah (thank you very much pak Hanson!)

Particulary, this abstraction for the implementation of the finite state machine itself is very simple and only involves 2 state transitions: when the database is being closed or when its open. Maybe, this implementation is related to over-complexity considering that the solution regarding database connection can be solved by putting log info for debugging the process and im also fully aware that this finite state machine is probably “off the hooks” considering the problem that i want to solve is just as information whether database connectivity is running or not.

Quick Recap

This is just a quick recap, for those who want to know what a finite state machine is, you can read it or find out on whole internet. In simple terms: finite state machine is useful to find out whether a machine computer can run on a complex system but the design looks very simple, it could be simple in term of implementation, simple in term of memory resources or whatsoever (this is the conclusion that i got from reading several articles, depend on what you read might be lead into different conclusion). In addition, finite state machine or FSM must at least meet these requirements:

  • at least in the system there will be states and transitions
  • there’s at least 1 state that acts as the initial state, and 1 other state that acts as the acceptance state.

Actually when we read further, there will be 2 differences to using this finite state machine by calculating the input output with deterministic finite automation (DFAs) or non-deterministic automation, but for right now i’d prefer not explain it in more detail…

Implementation

To be honest when i start to thinking about this solution, i’ve been looking for some open source projects, stackoverflow answers or find some articles related to FSM for databases but still i can’t find any information based on my case studies, so i guess i’ll have to implement it myself 😂

And thus for my own implementation, i’m thinking of using a decorator because i want to avoid unnecessary inheritance in the parent class and also this decorator acts as a private method which can only be accessed in the internal module. So for that the code like :

def fsm_transition(start_state, accept_state):
    """A very simple finite state machine which represents the transition
    from the DB class to operate when opening a connection, making a transaction,
    closing a connection and so on. This finite state machine in this library works
    based on the initial state which later the value will change depending on the
    transition state that's initiated in a particular method.
 
    This decorator only works for internal methods in the DB class with take several
    parameters such as
 
    :param start_state: parameter to set initial state transition
    :param accept_state: parameter to set end of state transition
    """
 
    allowed_states = ["closed", "open"]
 
    if (
        start_state is None
        or start_state not in allowed_states
        or not isinstance(start_state, str)
    ):
        raise Exception
 
    if (
        accept_state is None
        or accept_state not in allowed_states
        or not isinstance(accept_state, str)
    ):
        raise Exception
 
    def get_current_state(state):
        """Get current transition of state database"""
        while state is not None:
            if state.state in start_state:
                state.state = accept_state
                break
            else:
                raise Exception("accept_state can't use before start_state")
        return state
 
    def decorator(func):
        def wrapped(*args, **kwargs):
            # unused variable (_) need to declared
            # since it will wrap into tuple object,
            # otherwise it will raise Exception
            # when validate the current state
            try:
                state_machine, _ = args
            except ValueError:
                state_machine = args[0]
 
            get_current_state(state_machine)
            result = func(*args, **kwargs)
            return result
        return wrapped
    return decorator

Based on the code above, this decorator accepts arguments and keyword arguments in the parameter function. the get_current_state(state) function aims to validate whether the current state is in the accept state or the initial state, otherwise it will raise an Exception error. Because the parameter argument itself is a tuple object, we only need to unpack the first index to check whether the current state is in the start state.

We can use this decorator in a particular method, in my case it’s used in methods to close database connection and connect database connection like this :

class DbClient:
    _start_state = "closed"
 
    def __init__(self):
        self.start_state = _start_state
 
    @fsm_transition(start_state="open", accept_state="closed")
    def closed(self): # database being close
        ...
 
    @fsm_transition(start_state="closed", accept_state="open")
    def connect(self): # database open
        ...
 
# open up database connectivity based on particular
# orders: config first (state being closed)
config = DbClient()
# open up the connection
config.connect()
# close the connection
config.close()

If we execute the method then the whole process information that looks like this :

>>> 19-06-2022 01:53:36 : db.py : db_connect : Successfully connected to the server xxx.xx.xxx.xx, 3315, db name is default and MySQL version is 5.7.21
>>> open  # current state after open db connection
>>> 19-06-2022 01:53:36 : db.py : close : Successfully disconnect from database server, all transactions are committed
>>> closed  # closed state after close the db connection

Other solutions might be work, but…

as i described earlier, this solution using FSM right might be right off the bat since we can use print statements or log stream handlers. However, the basic difference lies in how this FSM can operate by maintaining the integrity and consistency of the database connectivity itself. If we use logs, there is a possibility that if the database error occurs, we can do a tricky solution by changing the statement to debug the process. While using FSM, the way database connectivity would operate is by looking at the accept state and its initial state, if the database is really in a closed state or when we change the accept state parameter or the initial state in different way, then it will get a raise Exception Wrapup

This short article was made with many shortcomings, of course, and made in a simplistic manner. So, if a question arises such as: what if we need a new state that acts as a transaction when the query is committed? initially, we can modify accept state or initial state to be an array then do a looping condition if the transaction state is in progress then push to the array and check whether the transaction state is in the current state or which is the final state. And of course, it’s being depend on what you need, for my own case, this simple FSM implementation indirectly becomes an unexpected alternative solution. Thanks for reading!

Some of take notes

  • database being closed has two different meaning. When connection its closed or the database cursor being closed. The cursor being closed derived for the querying process to the database will be closed (or its refer to closed their data structures), while the connection itself terminates the database so that when you want to do another transaction, you need to re-authenticate again.