Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

init repo #1

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Workflow
81 changes: 81 additions & 0 deletions definition.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package workflow

import (
"fmt"
)

type definition struct {
places map[Place]Place
initialPlaces []Place
transitions map[string]Transition
}

func NewDefinition(places []Place, initialPlaces []Place, transitions []Transition) (definition, error) {
definition := &definition{
places: map[Place]Place{},
transitions: map[string]Transition{},
}
definition.addPlaces(places)
if err := definition.addTransitions(transitions); err != nil {
return *definition, err
}
if err := definition.setInitialPlaces(initialPlaces); err != nil {
return *definition, err
}
definition.initialPlaces = initialPlaces
return *definition, nil
}

func MustNewDefinition(places []Place, initialPlaces []Place, transitions []Transition) definition {
definition, err := NewDefinition(places, initialPlaces, transitions)
if err != nil {
panic(err)
}
return definition
}

func (definition *definition) setInitialPlaces(places []Place) error {
for _, place := range places {
if _, ok := definition.places[place]; !ok {
return fmt.Errorf("place %s cannot be the initial place as it does not exist", place)
}
}
return nil
}

func (definition *definition) addPlaces(places []Place) {
for _, place := range places {
definition.addPlace(place)
}
}

func (definition *definition) addPlace(place Place) {
if _, ok := definition.places[place]; !ok {
definition.places[place] = place
}
}

func (definition *definition) addTransitions(transitions []Transition) error {
for _, transition := range transitions {
if err := definition.addTransition(transition); err != nil {
return err
}
}
return nil
}

func (definition *definition) addTransition(transition Transition) error {
transitionName := transition.Name
for _, place := range transition.Froms {
if _, ok := definition.places[place]; !ok {
return fmt.Errorf("place %s referenced in transition %s does not exist", place, transitionName)
}
}
for _, place := range transition.Tos {
if _, ok := definition.places[place]; !ok {
return fmt.Errorf("place %s referenced in transition %s does not exist", place, transitionName)
}
}
definition.transitions[transitionName] = transition
return nil
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/gol4ng/workflow

go 1.13
41 changes: 41 additions & 0 deletions marking.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package workflow

type Marking struct {
places map[Place]Place
}

func NewMarking(representation ...Place) *Marking {
marking := &Marking{
places: map[Place]Place{},
}
for _, place := range representation {
marking.Mark(place)
}

return marking
}

func (m *Marking) Mark(place Place) {
if _, ok := m.places[place]; !ok {
m.places[place] = place
}
}

func (m *Marking) Unmark(p Place) {
if _, ok := m.places[p]; ok {
delete(m.places, p)
}
}

func (m *Marking) Has(place Place) bool {
_, has := m.places[place]
return has
}

func (m *Marking) GetPlaces() []Place {
var places []Place
for _, place := range m.places {
places = append(places, place)
}
return places
}
33 changes: 33 additions & 0 deletions marking_storer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package workflow

import (
"fmt"
)

type MarkingStorer interface {
GetMarking(subject interface{}) (*Marking, error)
SetMarking(subject interface{}, marking Marking) error
}

type MethodMarkingStorer struct {
}

func (m *MethodMarkingStorer) GetMarking(subject interface{}) (*Marking, error) {
if v, ok := subject.(markedSubject); ok {
marking, err := v.GetMarking()
return &marking, err
}
return nil, fmt.Errorf("marking not found")
}

func (m *MethodMarkingStorer) SetMarking(subject interface{}, marking Marking) error {
if v, ok := subject.(markedSubject); ok {
return v.SetMarking(marking)
}
return fmt.Errorf("marking not found")
}

type markedSubject interface {
GetMarking() (Marking, error)
SetMarking(marking Marking) error
}
196 changes: 196 additions & 0 deletions workflow.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package workflow

import (
"fmt"
)

type Callback func(subject interface{})

type Place string

type Transition struct {
Name string
Froms []Place
Tos []Place
}

var nilTransition = Transition{}

type workflow struct {
name string

definition definition
markingStorer MarkingStorer

// todo event dispatcher ?
callbacks map[string]Callback
}

func NewWorkflow(name string, definition definition, markingStorer MarkingStorer, callbacks map[string]Callback) (*workflow, error) {
w := &workflow{
name,
definition,
markingStorer,
callbacks,
}
return w, nil
}

func (workflow *workflow) hasPlace(place Place) bool {
for _, definedPlace := range workflow.definition.places {
if definedPlace == place {
return true
}
}
return false
}

func (workflow *workflow) GetMarking(subject interface{}) (*Marking, error) {
marking, err := workflow.markingStorer.GetMarking(subject)
if err != nil {
return nil, err
}
// Init marking if empty
if len(marking.GetPlaces()) == 0 {
if len(workflow.definition.initialPlaces) == 0 {
return nil, fmt.Errorf("the Marking is empty and there is no initial place for workflow %s", workflow.name)
}
marking = NewMarking(workflow.definition.initialPlaces...)
workflow.markingStorer.SetMarking(subject, *marking)

// triggered workflow.entered
// triggered workflow.{workflow_name}.entered
// NOT triggered workflow.{workflow_name}.entered.{place}
workflow.entered(subject, nilTransition, marking)
}
return marking, nil
}

func (workflow *workflow) Can(subject interface{}, transitionName string) bool {
workflow.GetMarking(subject)
for _, transition := range workflow.definition.transitions {
if transition.Name != transitionName {
continue
}
// TODO transition blocker
return true
}
return false
}

func (workflow *workflow) Apply(subject interface{}, transitionName string) error {
if _, ok := workflow.definition.transitions[transitionName]; !ok {
return fmt.Errorf("transition %s is not defined for workflow %s", transitionName, workflow.name)
}
marking, err := workflow.GetMarking(subject)
if err != nil {
return err
}
var approvedTransitions []Transition
for _, transition := range workflow.definition.transitions {
if transition.Name != transitionName {
continue
}
// TODO transition blocker
approvedTransitions = append(approvedTransitions, transition)
}

if len(approvedTransitions) == 0 {
return fmt.Errorf("transition %s is not enable for workflow %s", transitionName, workflow.name)
}

for _, transition := range approvedTransitions {
workflow.leave(subject, transition, marking)
workflow.transition(subject, transition, marking)
workflow.enter(subject, transition, marking)

workflow.markingStorer.SetMarking(subject, *marking)

workflow.entered(subject, transition, marking)
workflow.completed(subject, transition, marking)
workflow.announce(subject, transition, marking)
}
return nil
}

func (workflow *workflow) leave(subject interface{}, transition Transition, marking *Marking) {
workflow.callCallback("workflow.leave", subject)
workflow.callCallback("workflow."+workflow.name+".leave", subject)

places := transition.Froms
for _, place := range places {
workflow.callCallback("workflow."+workflow.name+".leave."+string(place), subject)
}

for _, place := range places {
marking.Unmark(place)
}
}

func (workflow *workflow) transition(subject interface{}, transition Transition, marking *Marking) {
workflow.callCallback("workflow.transition", subject)
workflow.callCallback("workflow."+workflow.name+".transition", subject)
workflow.callCallback("workflow."+workflow.name+".transition."+transition.Name, subject)
}

func (workflow *workflow) enter(subject interface{}, transition Transition, marking *Marking) {
workflow.callCallback("workflow.enter", subject)
workflow.callCallback("workflow."+workflow.name+".enter", subject)
places := transition.Tos
for _, place := range places {
workflow.callCallback("workflow."+workflow.name+".enter."+string(place), subject)
}

for _, place := range places {
marking.Mark(place)
}
}

func (workflow *workflow) entered(subject interface{}, transition Transition, marking *Marking) {
workflow.callCallback("workflow.entered", subject)
workflow.callCallback("workflow."+workflow.name+".entered", subject)
for _, place := range transition.Tos {
workflow.callCallback("workflow."+workflow.name+".entered."+string(place), subject)
}
}

func (workflow *workflow) completed(subject interface{}, transition Transition, marking *Marking) {
workflow.callCallback("workflow.completed", subject)
workflow.callCallback("workflow."+workflow.name+".completed", subject)
workflow.callCallback("workflow."+workflow.name+".completed."+transition.Name, subject)
}

func (workflow *workflow) announce(subject interface{}, transition Transition, marking *Marking) {
workflow.callCallback("workflow.announce", subject)
workflow.callCallback("workflow."+workflow.name+".announce", subject)
for transitionName, _ := range workflow.getEnabledTransitions(subject) {
workflow.callCallback("workflow."+workflow.name+".announce."+transitionName, subject)
}
}

func (workflow *workflow) callCallback(key string, subject interface{}) {
if callback, ok := workflow.callbacks[key]; ok {
callback(subject)
}
}

func (workflow *workflow) hasTransition(transitionName string) bool {
_, hasTransition := workflow.definition.transitions[transitionName]
return hasTransition
}

func (workflow *workflow) getTransition(transitionName string) Transition {
return workflow.definition.transitions[transitionName]
}

func (workflow *workflow) getEnabledTransitions(subject interface{}) map[string]Transition {
workflow.GetMarking(subject)
enabledTransitions := map[string]Transition{}
for transitionName, transition := range workflow.definition.transitions {
if workflow.Can(subject, transitionName) {
enabledTransitions[transitionName] = transition
}
}

return enabledTransitions
}
Loading