Documentation ¶
Overview ¶
Package flow is a tiny workflow engine written in Go (golang).
Index ¶
- Constants
- Variables
- func IsValidNodeType(ntype string) bool
- func RegisterDB(sdb *sql.DB) error
- func SetBlobsDir(base string) error
- type AcGroup
- type AcGroupRoles
- type AccessContext
- type AccessContextID
- type Blob
- type DocAction
- type DocActionID
- type DocEvent
- type DocEventID
- type DocEventsHistory
- type DocEventsListInput
- type DocEventsNewInput
- type DocPath
- type DocState
- type DocStateID
- type DocTransitionID
- type DocType
- type DocTypeID
- type Document
- type DocumentID
- type DocumentsListInput
- type DocumentsNewInput
- type Documentstruct
- type Error
- type EventStatus
- type Group
- type GroupID
- type GroupRolesstruct
- type Mailbox
- type Message
- type MessageID
- type Node
- type NodeFunc
- type NodeID
- type NodeType
- type Notification
- type Permissionstruct
- type Role
- type RoleID
- type RolePermission
- type Transition
- type TransitionMap
- type Transitionstruct
- type User
- type UserID
- type Workflow
- type WorkflowID
Constants ¶
const ( // ErrUnknown : unknown internal error ErrUnknown = Error("ErrUnknown : unknown internal error") // ErrDocEventRedundant : another equivalent event has already effected this action ErrDocEventRedundant = Error("ErrDocEventRedundant : another equivalent event has already applied this action") // ErrDocEventDocTypeMismatch : document's type does not match event's type ErrDocEventDocTypeMismatch = Error("ErrDocEventDocTypeMismatch : document's type does not match event's type") // ErrDocEventStateMismatch : document's state does not match event's state ErrDocEventStateMismatch = Error("ErrDocEventStateMismatch : document's state does not match event's state") // ErrDocEventAlreadyApplied : event already applied; nothing to do ErrDocEventAlreadyApplied = Error("ErrDocEventAlreadyApplied : event already applied; nothing to do") // ErrDocumentNoParent : document is a root document ErrDocumentNoParent = Error("ErrDocumentNoParent : document is a root document") // ErrDocumentIsChild : cannot have its own state, title or tags ErrDocumentIsChild = Error("ErrDocumentIsChild : cannot have its own state, title or tags") // ErrWorkflowInactive : this workflow is currently inactive ErrWorkflowInactive = Error("ErrWorkflowInactive : this workflow is currently inactive") // ErrWorkflowInvalidAction : given action cannot be performed on this document's current state ErrWorkflowInvalidAction = Error("ErrWorkflowInvalidAction : given action cannot be performed on this document's current state") // ErrMessageNoRecipients : list of recipients is empty ErrMessageNoRecipients = Error("ErrMessageNoRecipients : list of recipients is empty") )
const ( // NodeTypeBegin : none incoming, one outgoing NodeTypeBegin NodeType = "begin" // NodeTypeEnd : one incoming, none outgoing NodeTypeEnd = "end" // NodeTypeLinear : one incoming, one outgoing NodeTypeLinear = "linear" // NodeTypeBranch : one incoming, two or more outgoing NodeTypeBranch = "branch" // NodeTypeJoinAny : two or more incoming, one outgoing NodeTypeJoinAny = "joinany" // NodeTypeJoinAll : two or more incoming, one outgoing NodeTypeJoinAll = "joinall" )
The following constants are represented **identically** as part of an enumeration in the database. DO NOT ALTER THESE WITHOUT ALSO ALTERING THE DATABASE; ELSE DATA COULD GET CORRUPTED!
const ( // DefACRoleCount is the default number of roles a group can have // in an access context. DefACRoleCount = 1 )
Variables ¶
var AccessContexts _AccessContexts
AccessContexts provides a resource-like interface to access contexts in the system.
var DocActions _DocActions
DocActions provides a resource-like interface to document actions in the system.
var DocEvents _DocEvents
DocEvents exposes a resource-like interface to document events.
var DocStates _DocStates
DocStates provides a resource-like interface to document actions in the system.
var DocTypes _DocTypes
DocTypes provides a resource-like interface to document types in the system.
var Documents _Documents
Documents provides a resource-like interface to the documents in this system.
var Groups _Groups
Groups provides a resource-like interface to groups in the system.
var Mailboxes _Mailboxes
Mailboxes is the singleton instance of `_Mailboxes`.
var Nodes _Nodes
Nodes provides a resource-like interface to the nodes defined in this system.
var Roles _Roles
Roles provides a resource-like interface to roles in the system.
var Users _Users
Users provides a resource-like interface to users in the system.
var Workflows _Workflows
Workflows provides a resource-like interface to the workflows defined in the system.
Functions ¶
func IsValidNodeType ¶
IsValidNodeType answers `true` if the given node type is a recognised node type in the system.
func RegisterDB ¶
RegisterDB provides an already initialised database handle to `flow`.
N.B. This method **MUST** be called before anything else in `flow`.
func SetBlobsDir ¶
SetBlobsDir specifies the base directory inside which blob files should be stored.
Inside this base directory, 256 subdirectories are created as hex `00` through `ff`. A blob is stored in the subdirectory whose name matches the first two hex digits of its SHA1 sum.
N.B. Once set, this MUST NOT change between runs. Doing so will result in loss of all previously stored blobs. In addition, corresponding documents get corrupted.
Types ¶
type AcGroup ¶
type AcGroup struct { Group `json:"Group"` // An assigned user ReportsTo Group `json:"ReportsTo"` // Reporting authority of this user }
AcGroup holds the information of a user together with the user's reporting authority.
type AcGroupRoles ¶
type AcGroupRoles struct { Group string `json:"Group"` // Group whose roles this represents Roles []Role `json:"Roles"` // Map holds the role assignments to groups }
AcGroupRoles holds the information of the various roles that each group has been assigned in this access context.
type AccessContext ¶
type AccessContext struct { ID AccessContextID `json:"ID"` // Unique identifier of this access context Name string `json:"Name,omitempty"` // Globally-unique namespace; can be a department, project, location, branch, etc. Active bool `json:"Active,omitempty"` // Can a workflow be initiated in this context? }
AccessContext is a namespace that provides an environment for workflow execution.
It is an environment in which users are mapped into a hierarchy that determines certain aspects of workflow control. This hierarchy, usually, but not necessarily, reflects an organogram. In each access context, applicable groups are mapped to their respective intended permissions. This mapping happens through roles.
Each workflow that operates on a document type is given an associated access context. This context is used to determine workflow routing, possible branching and rendezvous points.
Please note that the same workflow may operate independently in multiple unrelated access contexts. Thus, a workflow is not limited to one access context. Conversely, an access context can have several workflows operating on it, for various document types. Therefore, the relationship between workflows and access contexts is M:N.
For complex organisational requirements, the name of the access context can be made hierarchical with a suitable delimiter. For example:
- IN:south:HYD:BR-101
- sbu-08/client-0249/prj-006348
func (*AccessContext) GroupHasPermission ¶
func (ac *AccessContext) GroupHasPermission(id AccessContextID, gid GroupID, dtype DocTypeID, action DocActionID) (bool, error)
GroupHasPermission answers `true` if the given group has the requested action enabled on the specified document type; `false` otherwise.
type AccessContextID ¶
type AccessContextID int64
AccessContextID is the type unique access context identifiers.
type Blob ¶
type Blob struct { Name string `json:"Name"` // User-given name to the binary object Path string `json:"Path,omitempty"` // Path to the stored binary object SHA1Sum string `json:"SHA1sum"` // SHA1 checksum of the binary object }
Blob is a simple data holder for information concerning the user-supplied name of the binary object, the path of the stored binary object, and its SHA1 checksum.
type DocAction ¶
type DocAction struct { ID DocActionID `json:"ID"` // Unique identifier of this action Name string `json:"Name"` // Globally-unique name of this action Reconfirm bool `json:"Reconfirm"` // Should the user be prompted for a reconfirmation of this action? }
DocAction enumerates the types of actions in the system, as defined by the consuming application. Each document action has an associated workflow transition that it causes.
Accordingly, `flow` does not assume anything about the specifics of the any document action. Instead, it treats document actions as plain, but controlled, vocabulary. Good examples include:
CREATE, REVIEW, APPROVE, REJECT, COMMENT, CLOSE, and REOPEN.
N.B. All document actions must be defined as constant strings.
type DocActionID ¶
type DocActionID int64
DocActionID is the type of unique identifiers of document actions.
type DocEvent ¶
type DocEvent struct { ID DocEventID `json:"ID"` // Unique ID of this event DocType DocTypeID `json:"DocType"` // Document type of the document to which this event is to be applied DocID DocumentID `json:"DocID"` // Document to which this event is to be applied State DocStateID `json:"DocState"` // Current state of the document must equal this Action DocActionID `json:"DocAction"` // Action performed by the user Group GroupID `json:"Group"` // Group (singleton) who caused this action Text string `json:"Text"` // Comment or other content Ctime time.Time `json:"Ctime"` // Time at which the event occurred Status EventStatus `json:"Status"` // Status of this event }
DocEvent represents a user action performed on a document in the system.
Together with documents and nodes, events are central to the workflow engine in `flow`. Events cause documents to transition from one state to another, usually in response to user actions. It is possible for system events to cause state transitions, as well.
func (*DocEvent) StatusInDB ¶
func (e *DocEvent) StatusInDB() (EventStatus, error)
StatusInDB answers the status of this event.
type DocEventID ¶
type DocEventID int64
DocEventID is the type of unique document event identifiers.
type DocEventsHistory ¶ added in v0.9.0
type DocEventsListInput ¶
type DocEventsListInput struct { DocTypeID // Events on documents of this type are listed AccessContextID // Access context from within which to list GroupID // List events created by this (singleton) group DocStateID // List events acting on this state CtimeStarting time.Time // List events created after this time CtimeBefore time.Time // List events created before this time Status EventStatus // List events that are in this state of application }
DocEventsListInput specifies a set of filter conditions to narrow down document listings.
type DocEventsNewInput ¶
type DocEventsNewInput struct { DocTypeID // Type of the document; required DocumentID // Unique identifier of the document; required DocStateID // Document must be in this state for this event to be applied; required DocActionID // Action performed by `Group`; required GroupID // Group (user) who performed the action that raised this event; required Text string // Any comments or notes; required }
DocEventsNewInput holds information needed to create a new document event in the system.
type DocPath ¶
type DocPath string
DocPath helps in managing document hierarchies. It provides a set of utility methods that ease path management.
func (*DocPath) Append ¶
func (p *DocPath) Append(dtid DocTypeID, did DocumentID) error
Append adds the given document type-document ID pair to this path, updating it as a result.
func (*DocPath) Components ¶
func (p *DocPath) Components() ([]struct { DocTypeID DocumentID }, error)
Components answers a sequence of this path's components, in order.
type DocState ¶
type DocState struct { ID DocStateID `json:"ID"` // Unique identifier of this document state Name string `json:"Name,omitempty"` // Unique identifier of this state in its workflow }
DocState is one of a set of enumerated states for a top-level document, as defined by the consuming application.
`flow`, therefore, does not assume anything about the specifics of any state. Applications can, for example, embed `DocState` in a struct that provides more context. Document states should be loaded during application initialisation.
N.B. A `DocState` once defined and used, should *NEVER* be removed. At best, it can be deprecated by defining a new one, and then altering the corresponding workflow definition to use the new one instead.
type DocStateID ¶
type DocStateID int64
DocStateID is the type of unique identifiers of document states.
type DocTransitionID ¶ added in v0.9.0
type DocTransitionID int64
type DocType ¶
type DocType struct { ID DocTypeID `json:"ID,omitempty"` // Unique identifier of this document type Name string `json:"Name,omitempty"` // Unique name of this document type }
DocType enumerates the types of documents in the system, as defined by the consuming application. Each document type has an associated workflow definition that drives its life cycle.
Accordingly, `flow` does not assume anything about the specifics of the any document type. Instead, it treats document types as plain, but controlled, vocabulary. Nonetheless, it is highly recommended, but not necessary, that document types be defined in a system of hierarchical namespaces. For example:
PUR:RFQ
could mean that the department is 'Purchasing', while the document type is 'Request For Quotation'. As a variant,
PUR:ORD
could mean that the document type is 'Purchase Order'.
N.B. All document types must be defined as constant strings.
type DocTypeID ¶
type DocTypeID int64
DocTypeID is the type of unique identifiers of document types.
type Document ¶
type Document struct { ID DocumentID `json:"ID"` // Globally-unique identifier of this document DocType DocType `json:"DocType"` // For namespacing Path DocPath `json:"Path"` // Path leading to, but not including, this document AccCtx AccessContext `json:"AccessContext"` // Originating access context of this document; applicable only to a root document State DocState `json:"DocState"` // Current state of this document; applicable only to a root document Group Group `json:"Group"` // Creator of this document Ctime time.Time `json:"Ctime"` // Creation time of this (possibly child) document Title string `json:"Title"` // Human-readable title; applicable only for root documents Data string `json:"Data,omitempty"` // Primary content of the document }
Document represents a task in a workflow, whose life cycle it tracks.
Documents are central to the workflow engine and its operations. In the process, it accumulates various details, and tracks the times of its modifications. The life cycle typically involves several state transitions, whose details are also tracked.
`Document` is a recursive structure: it can contain other documents. Therefore, when a document is created, it is initialised with the path that leads from its root document to its immediate parent. For root documents, this path is empty.
Most applications should embed `Document` in their document structures rather than use this directly. That enables them to control their data persistence mechanisms, while delegating workflow management to `flow`.
type DocumentsListInput ¶
type DocumentsListInput struct { DocTypeID // Documents of this type are listed; required AccessContextID // Access context from within which to list; required GroupID // List documents created by this (singleton) group DocStateID // List documents currently in this state CtimeStarting time.Time // List documents created after this time CtimeBefore time.Time // List documents created before this time TitleContains string // List documents whose title contains the given text; expensive operation RootOnly bool // List only root (top-level) documents }
DocumentsListInput specifies a set of filter conditions to narrow down document listings.
type DocumentsNewInput ¶
type DocumentsNewInput struct { DocTypeID // Type of the new document; required AccessContextID // Access context in which the document should be created; required GroupID // (Singleton) group of the creator; required ParentType DocTypeID // Document type of the parent document, if any ParentID DocumentID // Unique identifier of the parent document, if any Title string // Title of the new document; applicable to only root (top-level) documents Data string // Body of the new document; required }
DocumentsNewInput specifies the initial data with which a new document has to be created in the system.
type Documentstruct ¶ added in v0.9.0
type Error ¶
type Error string
Error defines `flow`-specific errors, and satisfies the `error` interface.
type EventStatus ¶
type EventStatus uint8
EventStatus enumerates the query parameter values for filtering by event state.
const ( // EventStatusAll does not filter events. EventStatusAll EventStatus = iota // EventStatusApplied selects only those events that have been successfully applied. EventStatusApplied // EventStatusPending selects only those events that are pending application. EventStatusPending )
type Group ¶
type Group struct { ID GroupID `json:"ID"` // Globally-unique ID Name string `json:"Name"` // Globally-unique name GroupType string `json:"GroupType"` // Is this a user-specific group? Etc. }
Group represents a specified collection of users. A user belongs to zero or more groups.
type GroupRolesstruct ¶ added in v0.9.0
type Mailbox ¶
type Mailbox struct {
GroupID `json:"GroupID"` // Group (or singleton user) owner of this mailbox
}
Mailbox is the message delivery destination for both action and informational messages.
Both users and groups have mailboxes. In all normal cases, a message is 'consumed' by the recipient. Messages can be moved into and out of mailboxes to facilitate reassignments under specific or extraordinary conditions.
type Message ¶
type Message struct { ID MessageID `json:"ID"` // Globally-unique identifier of this message DocType `json:"DocType"` // Document type of the associated document DocID DocumentID `json:"DocID"` // Document in the workflow Event DocEventID `json:"DocEvent"` // Event that triggered this message Title string `json:"Title"` // Subject of this message Data string `json:"Data"` // Body of this message }
Message is the content part of a notification sent by the workflow engine to possibly multiple mailboxes.
Messages can be informational or seek action. Each message contains a reference to the document that began the current workflow, as well as the event that triggered this message.
type Node ¶
type Node struct { ID NodeID `json:"ID"` // Unique identifier of this node DocType DocTypeID `json:"DocType"` // Document type which this node's workflow manages State DocStateID `json:"DocState"` // A document arriving at this node must be in this state AccCtx AccessContextID `json:"AccessContext,omitempty"` // Specific access context associated with this state, if any Wflow WorkflowID `json:"Workflow"` // Containing flow of this node Name string `json:"Name"` // Unique within its workflow NodeType NodeType `json:"NodeType"` // Topology type of this node // contains filtered or unexported fields }
Node represents a specific logical unit of processing and routing in a workflow.
func (*Node) SetFunc ¶
SetFunc registers the given node function with this node.
If `nil` is given, a default node function is registered instead. This default function sets the document title as the message subject, and the event's data as the message body.
func (*Node) Transitions ¶
func (n *Node) Transitions() (map[DocActionID]DocStateID, error)
Transitions answers the possible document states into which a document currently in the given state can transition.
type NodeFunc ¶
NodeFunc defines the type of functions that generate notification messages in workflows.
These functions are triggered by appropriate nodes, when document events are applied to documents to possibly transform them. Invocation of a `NodeFunc` should result in a message that can then be dispatched to applicable mailboxes.
Error should be returned only when an impossible situation arises, and processing needs to abort. Note that returning an error stops the workflow. Manual intervention will be needed to move the document further.
N. B. NodeFunc instances must be referentially transparent -- stateless and not capture their environment in any manner. Unexpected bad things could happen otherwise!
type Notification ¶
type Notification struct { GroupID `json:"Group"` // The group whose mailbox this notification is in Message `json:"Message"` // The underlying message Unread bool `json:"Unread"` // Status flag reflecting if the message is still not read Ctime time.Time `json:"Ctime"` // Time when this notification was posted }
Notification tracks the 'unread' status of a message in a mailbox.
Since a single message can be delivered to multiple mailboxes, the 'unread' status cannot be associated with a message. Instead, `Notification` is the entity that tracks it per mailbox.
type Permissionstruct ¶ added in v0.9.0
type Role ¶
type Role struct { ID RoleID `json:"ID"` // globally-unique ID of this role Name string `json:"Name"` // name of this role }
Role represents a collection of privileges.
Each group in the system can have one or more roles assigned.
type RolePermission ¶ added in v0.9.0
type RolePermission struct { RoleID RoleID TypeAction typeaction }
type Transition ¶
type Transition struct { Upon DocAction // If user/system has performed this action To DocState // Document transitions into this state }
Transition holds the information of which action results in which state.
type TransitionMap ¶
type TransitionMap struct { From DocState // When document is in this state Transitions map[DocActionID]Transition }
TransitionMap holds the state transitions defined for this document type. It lays out which actions result in which target states, given current states.
type Transitionstruct ¶ added in v0.9.0
type User ¶
type User struct { ID UserID `json:"ID"` // Must be globally-unique FirstName string `json:"FirstName"` // For display purposes only LastName string `json:"LastName"` // For display purposes only Email string `json:"Email"` // E-mail address of this user UserName string `json:"UserName"` Active bool `json:"Active,omitempty"` // Is this user account active? }
User represents any kind of a user invoking or otherwise participating in a defined workflow in the system.
User details are expected to be provided by an external identity provider application or directory. `flow` neither defines nor manages users.
type Workflow ¶
type Workflow struct { ID WorkflowID `json:"ID,omitempty"` // Globally-unique identifier of this workflow Name string `json:"Name,omitempty"` // Globally-unique name of this workflow DocType DocType `json:"DocType"` // Document type of which this workflow defines the life cycle BeginState DocState `json:"BeginState"` // Where this flow begins Active bool `json:"Active,omitempty"` // Is this workflow enabled? }
Workflow represents the entire life cycle of a single document.
A workflow begins with the creation of a document, and drives its life cycle through a sequence of responses to user actions or other system events.
The engine in `flow` is visible primarily through workflows, documents and their behaviour.
Currently, the topology of workflows is a graph, and is determined by the node definitions herein.
N.B. It is highly recommended, but not necessary, that workflow names be defined in a system of hierarchical namespaces.
func (*Workflow) ApplyEvent ¶
func (w *Workflow) ApplyEvent(otx *sql.Tx, event *DocEvent, recipients []GroupID) (DocStateID, error)
ApplyEvent takes an input user action or a system event, and applies its document action to the given document. This results in a possibly new document state. This method also prepares a message that is posted to applicable mailboxes.