Skip to content

Commit

Permalink
Release 0.6.0
Browse files Browse the repository at this point in the history
  • Loading branch information
massenz committed Oct 4, 2022
2 parents 6027b64 + 50ed989 commit 942f1ce
Show file tree
Hide file tree
Showing 37 changed files with 1,517 additions and 765 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
#
# Copyright (c) 2022 AlertAvert.com. All rights reserved.
# Author: Marco Massenzio ([email protected])
#name: Build
#
name: Build & Test

on:
push:
Expand Down
2 changes: 1 addition & 1 deletion .run/Run SQS Client.run.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<configuration default="false" name="Run SQS Client" type="GoApplicationRunConfiguration" factoryName="Go Application" editBeforeRun="true">
<module name="statemachine" />
<working_directory value="$PROJECT_DIR$" />
<parameters value="-endpoint http://localhost:4566 -q events -dest 674ab2cf-f8e3-40c2-95d7-1eaa0cec7bf4 -evt sign" />
<parameters value="-endpoint http://localhost:4566 -q events -dest 8033049e-caae-4cb1-9fe4-bc02ff26bc26 -evt accept" />
<kind value="FILE" />
<package value="github.com/massenz/go-statemachine/clients" />
<directory value="$PROJECT_DIR$" />
Expand Down
3 changes: 1 addition & 2 deletions .run/Run gRPC Client.run.xml
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run gRPC Client" type="GoApplicationRunConfiguration" factoryName="Go Application" editBeforeRun="true">
<configuration default="false" name="Run gRPC Client" type="GoApplicationRunConfiguration" factoryName="Go Application">
<module name="statemachine" />
<working_directory value="$PROJECT_DIR$" />
<parameters value="-addr :7398 -dest 674ab2cf-f8e3-40c2-95d7-1eaa0cec7bf4 -evt ship" />
<kind value="FILE" />
<package value="github.com/massenz/go-statemachine/clients" />
<directory value="$PROJECT_DIR$" />
Expand Down
40 changes: 29 additions & 11 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,41 +1,59 @@
# Copyright (c) 2022 AlertAvert.com. All rights reserved.
# (Reluctantly) Created by M. Massenzio, 2022-03-14
# Created by M. Massenzio, 2022-03-14

pkgs := ./api ./grpc ./pubsub ./server ./storage
bin := build/bin
out := $(bin)/sm-server
tag := $(shell ./get-tag)
image := massenz/statemachine
module := $(shell go list -m)

compose := docker/docker-compose.yaml
dockerfile := docker/Dockerfile

build: cmd/main.go
go build -ldflags "-X main.Release=$(tag)" -o $(out) cmd/main.go
# Source files & Test files definitions
#
# Edit only the packages list, when adding new functionality,
# the rest is deduced automatically.
#
pkgs := ./api ./grpc ./pubsub ./server ./storage
all_go := $(shell for d in $(pkgs); do find $$d -name "*.go"; done)
test_srcs := $(shell for d in $(pkgs); do find $$d -name "*_test.go"; done)
srcs := $(filter-out $(test_srcs),$(all_go))

# Builds the server
#
$(out): cmd/main.go $(srcs)
go build -ldflags "-X $(module)/server.Release=$(tag)" -o $(out) cmd/main.go
@chmod +x $(out)

$(out): build
build: $(out)

# Convenience targets to run locally containers and
# setup the test environments.
#
# TODO: will be replaced once we adopt TestContainers
# (see Issue # 26)
services:
@docker-compose -f $(compose) up -d

queues:
@for queue in events notifications; do \
aws --no-cli-pager --endpoint-url=http://localhost:4566 --region us-west-2 \
aws --no-cli-pager --endpoint-url=http://localhost:4566 \
--region us-west-2 \
sqs create-queue --queue-name $$queue; done >/dev/null

test: $(out) services queues
ginkgo -p $(pkgs)
test: $(srcs) $(test_srcs) services queues
ginkgo $(pkgs)

container: $(out)
docker build -f $(dockerfile) -t $(image):$(tag) .

# Runs test coverage and displays the results in browser
cov: build services queues
cov: $(srcs) $(test_srcs)
@go test -coverprofile=/tmp/cov.out $(pkgs)
@go tool cover -html=/tmp/cov.out

clean:
@rm $(out)
@rm -f $(out)
@docker-compose -f $(compose) down
@docker rmi $(image):$(tag)
@docker rmi $(shell docker images -q --filter=reference=$(image))
51 changes: 45 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

A basic implementation of a Finite State Machine in Go

[![Author](https://img.shields.io/badge/Author-M.%20Massenzio-green)](https://github.com/massenz)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)

[![Build](https://github.com/massenz/go-statemachine/actions/workflows/build.yml/badge.svg)](https://github.com/massenz/go-statemachine/actions/workflows/build.yml)
Expand Down Expand Up @@ -255,8 +254,40 @@ See [`EventRequest` in `statemachine-proto`](https://github.com/massenz/statemac

#### SQS Error notifications

`TODO:` Once we refactor `EventErrorMessage` we should update this section too.
Event processing outcomes are returned in [`EventResponse` protocol buffers](https://github.com/massenz/statemachine-proto/blob/main/api/statemachine.proto#L112), which are then serialized inside the `body` of the SQS message; to retrieve the actual Go struct, you can use code such as this (see [test code](pubsub/sqs_pub_test.go#L148) for actual working code):

```go
// `res` is what AWS SQS Client will return to the Messages slice
var res *sqs.Message = getSqsMessage(getQueueName(notificationsQueue))
var receivedEvt protos.EventResponse
err := proto.UnmarshalText(*res.Body, &receivedEvt)
if err == nill {
// you know what to do
}

receivedEvt.EventId --> is the ID of the Event that failed
if receivedEvt.Outcome.Code == protos.EventOutcome_InternalError {
// whatever...
}
return fmt.Errorf("cannot process event to statemachine [%s]: %s,
receivedEvt.Outcome.Dest, receivedEvt.Outcome.Details)
```
The possible error codes are (see the `.proto` definition for the up-to-date values):
```protobuf
enum StatusCode {
Ok = 0;
GenericError = 1;
EventNotAllowed = 2;
FsmNotFound = 3;
TransitionNotAllowed = 4;
InternalError = 5;
MissingDestination = 6;
ConfigurationNotFound = 7;
}
```
### gRPC Methods
Expand All @@ -267,7 +298,7 @@ Please refer to [gRPC documentation](https://grpc.io/docs/), the [example gRPC c
The TL;DR version of all the above is that code like this:
```golang
response, err := client.ConsumeEvent(context.Background(),
response, err := client.ProcessEvent(context.Background(),
&api.EventRequest{
Event: &api.Event{
EventId: uuid.NewString(),
Expand All @@ -283,16 +314,24 @@ response, err := client.ConsumeEvent(context.Background(),
like in the SQS example, will cause a `backorder` event to be sent to our FSM whose `id` matches the UUID in `dest`; the `response` message will contain either the ID of the event, or a suitable error will be returned.
**NOTE**<br/>
As the event processing is asynchronous, the `response` will only contain the `event_id` and an empty `outcome`; use the `event_id` and the `GetEventOutcome(GetRequest)` to retrieve the result of event processing (or directly fetch the FSM via the `GetStatemachine()` method to confirm it has transitioned to the expected state - or not).
# Build & Run
## Prerequisites
**Ginkgo testing framework**<br/>
Run this:
We are using [Ginkgo](https://onsi.github.io/ginkgo/) at **v1** (`v1.16.5`).
To install the CLI run this:
go install github.com/onsi/ginkgo/ginkgo
go get github.com/onsi/ginkgo/v1/ginkgo &&
go get github.com/onsi/gomega/...
> **Beware** Gingko now is at `v2` and will install that one by default if you follow the instruction on the site: use instead the command above and run `go mod tidy` before running the tests/builds to download packages<br/>
> (see [this issue](https://github.com/onsi/ginkgo/issues/945) for more details)
**Protocol Buffers definitions**<br/>
They are kept in the [statemachine-proto](https://github.com/massenz/statemachine-proto) repository; nothing specific is needed to use them; however, if you want to review the messages and services definitions, you can see them there.
Expand Down
39 changes: 13 additions & 26 deletions api/fsm.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ import (
"github.com/golang/protobuf/proto"
"github.com/google/uuid"
log "github.com/massenz/slf4go/logging"
tspb "google.golang.org/protobuf/types/known/timestamppb"
"google.golang.org/protobuf/types/known/timestamppb"
"strings"

protos "github.com/massenz/statemachine-proto/golang/api"
)
Expand Down Expand Up @@ -68,7 +69,7 @@ func NewStateMachine(configuration *protos.Configuration) (*ConfiguredStateMachi
}
return &ConfiguredStateMachine{
FSM: &protos.FiniteStateMachine{
ConfigId: configuration.Name + ":" + configuration.Version,
ConfigId: strings.Join([]string{configuration.Name, configuration.Version}, ConfigurationVersionSeparator),
State: configuration.StartingState,
},
Config: configuration,
Expand All @@ -94,29 +95,6 @@ func (x *ConfiguredStateMachine) SendEvent(evt *protos.Event) error {
return UnexpectedTransitionError
}

// SendEventAsString is a convenience method which also creates the `Event` proto.
func (x *ConfiguredStateMachine) SendEventAsString(evt string) error {
for _, t := range x.Config.Transitions {
if t.From == x.FSM.State && t.Event == evt {
event := NewEvent(evt)
event.Transition.From = x.FSM.State
x.FSM.State = t.To
event.Transition.To = x.FSM.State
x.FSM.History = append(x.FSM.History, event)
return nil
}
}
return UnexpectedTransitionError
}

func NewEvent(evt string) *protos.Event {
return &protos.Event{
EventId: uuid.New().String(),
Timestamp: tspb.Now(),
Transition: &protos.Transition{Event: evt},
}
}

func (x *ConfiguredStateMachine) Reset() {
x.FSM.State = x.Config.StartingState
x.FSM.History = nil
Expand Down Expand Up @@ -185,12 +163,21 @@ func CheckValid(c *protos.Configuration) error {
return nil
}

// NewEvent creates a new Event, with the given `eventName` transition.
func NewEvent(eventName string) *protos.Event {
var event = protos.Event{
Transition: &protos.Transition{Event: eventName},
}
UpdateEvent(&event)
return &event
}

// UpdateEvent adds the ID and timestamp to the event, if not already set.
func UpdateEvent(event *protos.Event) {
if event.EventId == "" {
event.EventId = uuid.NewString()
}
if event.Timestamp == nil {
event.Timestamp = tspb.Now()
event.Timestamp = timestamppb.Now()
}
}
14 changes: 7 additions & 7 deletions api/statemachine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,19 +166,19 @@ var _ = Describe("FSM Protocol Buffers", func() {
lander, err := NewStateMachine(&spaceship)
Expect(err).ToNot(HaveOccurred())
Expect(lander.FSM.State).To(Equal("earth"))
Expect(lander.SendEventAsString("launch")).ShouldNot(HaveOccurred())
Expect(lander.SendEvent(NewEvent("launch"))).ShouldNot(HaveOccurred())
Expect(lander.FSM.State).To(Equal("orbit"))
Expect(lander.SendEventAsString("land")).ShouldNot(HaveOccurred())
Expect(lander.SendEvent(NewEvent("land"))).ShouldNot(HaveOccurred())
Expect(lander.FSM.State).To(Equal("mars"))
})
It("should fail for an unsupported transition", func() {
lander, _ := NewStateMachine(&spaceship)
Expect(lander.SendEventAsString("navigate")).Should(HaveOccurred())
Expect(lander.SendEvent(NewEvent("navigate"))).Should(HaveOccurred())
})
It("can be reset", func() {
lander, _ := NewStateMachine(&spaceship)
Expect(lander.SendEventAsString("launch")).ShouldNot(HaveOccurred())
Expect(lander.SendEventAsString("land")).ShouldNot(HaveOccurred())
Expect(lander.SendEvent(NewEvent("launch"))).ShouldNot(HaveOccurred())
Expect(lander.SendEvent(NewEvent("land"))).ShouldNot(HaveOccurred())
Expect(lander.FSM.State).To(Equal("mars"))

// Never mind, Elon, let's go home...
Expand Down Expand Up @@ -211,8 +211,8 @@ var _ = Describe("FSM Protocol Buffers", func() {
Expect(fsm.FSM).ToNot(BeNil())
Expect(fsm.FSM.State).To(Equal("start"))

Expect(fsm.SendEventAsString("accepted")).ToNot(HaveOccurred())
Expect(fsm.SendEventAsString("shipped")).ToNot(HaveOccurred())
Expect(fsm.SendEvent(NewEvent("accepted"))).ToNot(HaveOccurred())
Expect(fsm.SendEvent(NewEvent("shipped"))).ToNot(HaveOccurred())

Expect(fsm.FSM.State).To(Equal("shipping"))
Expect(len(fsm.FSM.History)).To(Equal(2))
Expand Down
3 changes: 2 additions & 1 deletion build.settings
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Build configuration

version = 0.5.1
version = 0.6.0

Loading

0 comments on commit 942f1ce

Please sign in to comment.