Creating a state storage implementation
Storage implementations are responsible for persisting the state of your workflow instances. State can be added to by workflow functions and also by your own code that interacts with workflow instances, for example:
public void function do( required WorkflowInstance wfInstance, required struct args ) {
arguments.wfInstance.appendState( {
userId = $getLoggedInUserId()
} );
}
The IWorkflowInstanceStorage interface
To implement your own instance storage, create a CFC that implements the cfflow.models.implementation.interfaces.IWorkflowInstanceStorage
interface. The interface is as follows:
interface {
public boolean function instanceExists( required string workflowId, required struct instanceArgs );
public void function createInstance( required string workflowId, required struct instanceArgs );
public struct function getState( required string workflowId, required struct instanceArgs );
public void function setState( required string workflowId, required struct instanceArgs, required struct state );
public void function appendState( required string workflowId, required struct instanceArgs, required struct state );
public string function getStepStatus( required string workflowId, required struct instanceArgs, required string step );
public void function setStepStatus( required string workflowId, required struct instanceArgs, required string step, required string status );
public struct function getAllStepStatuses( required string workflowId, required struct instanceArgs );
public void function setComplete( required string workflowId, required struct instanceArgs );
public void function recordAction( required string workflowId, required struct instanceArgs, required struct state, required string actionId, required string resultId, required array transitions );
}
Register your instance storage implementation
To use your storage implementation class, you must register it using cfflow.registerStorageClass()
:
var myStorage = new my.CustomInstanceStorage();
cfflow.registerStorageClass(
className = "my.storage.class"
, implementation = myStorage
);
Example implementation using Preside database objects
/**
* @presideService true
* @singleton true
*/
component implements="cfflow.models.implementation.interfaces.IWorkflowInstanceStorage" {
// CONSTRUCTOR
/**
* @instanceDao.inject presidecms:object:cfflow_workflow_instance
* @stepStatusDao.inject presidecms:object:cfflow_workflow_instance_step
* @historyDao.inject presidecms:object:cfflow_workflow_instance_history
* @transitionDao.inject presidecms:object:cfflow_workflow_instance_history_transition
*
*/
public any function init(
required any instanceDao
, required any stepStatusDao
, required any historyDao
, required any transitionDao
) {
_setInstanceDao( arguments.instanceDao );
_setStepStatusDao( arguments.stepStatusDao );
_setHistoryDao( arguments.historyDao );
_setTransitionDao( arguments.transitionDao );
return this;
}
// PUBLIC API METHODS
public boolean function instanceExists( required string workflowId, required struct instanceArgs ){
return _getInstanceDao().dataExists( filter=_getInstanceFilter( argumentCollection=arguments ) );
}
public void function createInstance( required string workflowId, required struct instanceArgs ) {
_getInstanceDao().insertData( data={
workflow_id = arguments.workflowId
, owner = arguments.instanceArgs.owner ?: ""
, reference = arguments.instanceArgs.reference ?: ""
, sub_reference = arguments.instanceArgs.subreference ?: ""
} );
}
public struct function getState( required string workflowId, required struct instanceArgs ) {
var instanceRecord = _getInstance( argumentCollection=arguments );
if ( Len( Trim( instanceRecord.state ?: "" ) ) ) {
try {
var state = DeserializeJson( instanceRecord.state );
if ( IsStruct( state ) ) {
return state;
}
} catch( any e ) {
$raiseError( e );
}
}
return {};
}
public void function setState( required string workflowId, required struct instanceArgs, required struct state ){
_getInstanceDao().updateData(
filter = _getInstanceFilter( argumentCollection=arguments )
, data = { state=SerializeJson( arguments.state ) }
);
}
public void function appendState( required string workflowId, required struct instanceArgs, required struct state ){
var newState = getState( argumentCollection=arguments );
StructAppend( newState, arguments.state );
setState( argumentCollection=arguments, state=newState );
}
public string function getStepStatus( required string workflowId, required struct instanceArgs, required string step ) {
var statusRecord = _getStepStatusDao().selectData( filter=_getStepFilter( argumentCollection=arguments ) );
return statusRecord.status ?: "";
}
public void function setStepStatus( required string workflowId, required struct instanceArgs, required string step, required string status ){
var updated = _getStepStatusDao().updateData(
filter = _getStepFilter( argumentCollection=arguments )
, data = { status=arguments.status }
);
if ( !updated ) {
var instance = _getInstance( argumentCollection=arguments );
if ( instance.recordCount ) {
_getStepStatusDao().insertData( data={
instance = instance.id
, step = arguments.step
, status = arguments.status
} );
}
}
}
public struct function getAllStepStatuses( required string workflowId, required struct instanceArgs ){
var records = _getStepStatusDao().selectData( filter=_getStepFilter( argumentCollection=arguments ) );
var statuses = {};
for( var step in records ) {
statuses[ step.step ] = step.status;
}
return statuses;
}
public void function setComplete( required string workflowId, required struct instanceArgs ) {
_getInstanceDao().updateData(
filter = _getInstanceFilter( argumentCollection=arguments )
, data = { completed=true }
);
}
public void function recordAction(
required string workflowId
, required struct instanceArgs
, required struct state
, required string actionId
, required string resultId
, required array transitions
) {
var instance = _getInstance( argumentCollection=arguments );
if ( instance.recordCount ) {
var historyId = _getHistoryDao().insertData( data={
instance = instance.id
, action = arguments.actionId
, result = arguments.resultId
, state = SerializeJson( arguments.state )
} );
for( var transition in arguments.transitions ) {
_getTransitionDao().insertData( data={
history = historyId
, step = transition.getStep()
, status = transition.getStatus()
} );
}
}
}
// PRIVATE HELPERS
private any function _getInstance( required string workflowId, required struct instanceArgs ) {
return _getInstanceDao().selectData( filter=_getInstanceFilter( argumentCollection=arguments ) );
}
private struct function _getInstanceFilter( required string workflowId, required struct instanceArgs ) {
return {
workflow_id = arguments.workflowId
, owner = arguments.instanceArgs.owner ?: ""
, reference = arguments.instanceArgs.reference ?: ""
, sub_reference = arguments.instanceArgs.subreference ?: ""
};
}
private struct function _getStepFilter( required string workflowId, required struct instanceArgs, string step="" ) {
var filter = {
"instance.workflow_id" = arguments.workflowId
, "instance.owner" = arguments.instanceArgs.owner ?: ""
, "instance.reference" = arguments.instanceArgs.reference ?: ""
, "instance.sub_reference" = arguments.instanceArgs.subreference ?: ""
};
if ( Len( Trim( arguments.step ) ) ){
filter.step = arguments.step;
}
return filter;
}
// GETTERS AND SETTERS
private any function _getInstanceDao() {
return _instanceDao;
}
private void function _setInstanceDao( required any instanceDao ) {
_instanceDao = arguments.instanceDao;
}
private any function _getStepStatusDao() {
return _stepStatusDao;
}
private void function _setStepStatusDao( required any stepStatusDao ) {
_stepStatusDao = arguments.stepStatusDao;
}
private any function _getHistoryDao() {
return _historyDao;
}
private void function _setHistoryDao( required any historyDao ) {
_historyDao = arguments.historyDao;
}
private any function _getTransitionDao() {
return _transitionDao;
}
private void function _setTransitionDao( required any transitionDao ) {
_transitionDao = arguments.transitionDao;
}
}