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 DocEventsListInput
- type DocEventsNewInput
- type DocPath
- type DocState
- type DocStateID
- type DocType
- type DocTypeID
- type Document
- type DocumentID
- type DocumentsListInput
- type DocumentsNewInput
- type Error
- type EventStatus
- type Group
- type GroupID
- type Mailbox
- type Message
- type MessageID
- type Node
- type NodeFunc
- type NodeID
- type NodeType
- type Notification
- type Role
- type RoleID
- type Transition
- type TransitionMap
- 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 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 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 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 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 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 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 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
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.