SOA, STAR

Designing a Reliable API Call Component with SAM




The goal of this post is to demonstrate how the SAM Pattern (State-Action-Model) can be put in practice using a simple but practical end-to-end scenario. Let’s start by building a reliable API call component which purpose is to call an API and be capable of detecting timeouts and retrying several times in case the API is not available. It should also be able detect error messages returned by the API call and return them as errors to the caller. The development workflow is straightforward:

  • define the control states of the component,
  • its actions and guards
  • the corresponding Type(s) of the Model.

The semantics we use are based on the STAR metamodel. Perhaps something unusual worth noting is that STAR allows an action to have two possible outcomes (the resulting state being decided by the type based on the property values). This is the case of the receive:response action.

Designing a Reliable API Call Component with SAM

The transitions out of the “errored” control state are guarded to reflect different types of errors. We could have used different states for each type of error, but control states are expensive to implement and you generally want to minimize the number of control states in a given system.   The corresponding actions are pretty straightforward:

The “Call” type should look like this:  

  • data is the data provided by the requestor for call the API
  • request is the request prepared from the data to make the API call
  • requested is a control variable that turns true when the API is called
  • ticks is the number of ticks since the last API call
  • retryCount is the number of times the API was called for a given request
  • response is the data received from the API Call
  • error is the error received from the API call (if any)
  • responded is a control variable

The Call type relies on a couple of constants: retryCountMax that specifies how many times the call should be retried in case of a timeout, and the duration of the timeout (tickMax) in the number of ticks.  You may also have noticed that I didn’t mention the hash parameter. Reactive architectures are all about achieving greater efficiencies, and if the Model doesn’t change, well, there is nothing interesting to propagate. So, when we return a response to the caller, we return a hash too, which can then be provided next time a call is requested to give us an indication as to which dataset the caller currently holds. When a response is returned by API, the guard on the return action would then prevent that action to execute and the API call will simply end without returning data or errors. Now that we have the Call type defined, we can specify the ranges from which the control states are computed. The STAR semantics require that these ranges need to be designed such that for any set of variable assignments only on control state is returned.

The last step is about implementing the actions and the guards. Actions are purely functional, the type properties are provided as an input and they return an output which is then presented to the type to update its properties. Only the type can decide whether it accepts the values, whether it is partially or atomically, and subsequently derive the new current state based on the new values.

request:data {
call = new Call() ;
data = data ;
return call;
}
request that instantiates the Call component

results in “started” state which is assigned an automatic send: action
send:call {
request’ = f(data) ;
write(request,outputStream) ;
requested’ = true ;
response’ = null ;
error’ = null ;
ticks’ = 0 ;
return call’;
}
action which prepares the request from the caller’s data (and metadata), via the function f(), and initiates the call to the API

results in waiting state which is expecting two actions triggered externally: tick: and receive:
tick:call {
ticks’ = ticks+1 ;
return call’;
}

action which is invoked by the system at a periodic interval. When ticks > ticksMax, the component transitions to the error state
retry:call {
retryCount’ = retryCount + 1 ;
tick’ = -1 ;
return call’;
}
action which gets the component ready to resend the request

results in “retried” state which initiates an automatic send: action

We use a value of -1 to avoid a collision with the waiting state.
receive:response,error {
response’ = response ;
error’ = error ;
return call’;
}
the received action is triggered when the system receives the API call response (or error), the action may result in the “responded” or “errored” states
return:response {
response’ = g(response)
publish response’ ;
}
the return action prepares the response for the caller and publishes the response

Several components may subscribe to the notification
error:error {
error= g(error)
publish error’ ;
}
the error action prepares the exception response for the caller and publishes it

Several components may subscribe to the notification

A couple of guard conditions need to be added to complete the definition of the component: 

That’s it! You can see the sample app in action here.   The behavior of the component if fully described as: B = (Si,Aj,Gk,Tn)

Designing a Reliable API Call Component with SAM

To create an implementation of the pattern, we use the STAR framework (javascript edition) and node.js The sample code can be found here (src/samples/apicall), there is also a server-side implementation, using WebSocket availble here (src/samples/node-apicall):

  • the CallType class represents the model of the call component
  • the Call class is the behavior which implementation follows the SAM pattern
In the STAR programming model the Call class is called a “Behavior”, it is the class that owns the states, actions/guards and ranges (mapping between the type assignments and the corresponding control states).
The trace of the execution is provided by the STAR framework as a PlantUML diagram:


Leave a Reply

Your email address will not be published. Required fields are marked *

59 − 53 =