statemachine

package
v0.0.0-...-d6c7d84 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 12, 2025 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package statemachine provides a simple routing state machine implementation. This is useful for implementing complex state machines that require routing logic. The state machine is implemented as a series of state functions that take a Request and returns a Request with the next State or an error. An error causes the state machine to stop and return the error. A nil state causes the state machine to stop.

You may build a state machine using either function calls or method calls. The Request.Data object you define can be a stack or heap allocated object. Using a stack allocated object is useful when running a lot of state machines in parallel, as it reduces the amount of memory allocation and garbage collection required.

State machines of this design can reduce testing complexity and improve code readability. You can read about how here: https://medium.com/@johnsiilver/go-state-machine-patterns-3b667f345b5e

This package is has OTEL support built in. If the Context passed to the state machine has a span, the state machine will create a child span for each state. If the state machine returns an error, the span will be marked as an error.

Example:

	package main

	import (
		"context"
		"fmt"
		"io"
		"log"
		"net/http"

		"github.com/gostdlib/ops/statemachine"
	)

	var (
		author = flag.String("author", "", "The author of the quote, if not set will choose a random one")
	)

	// Data is the data passed to through the state machine. It can be modified by the state functions.
	type Data struct {
		// This section is data set before starting the state machine.

		// Author is the author of the quote. If not set it will be chosen at random.
		Author string

		// This section is data set during the state machine.

		// Quote is a quote from the author. It is set in the state machine.
		Quote string

		// httpClient is the http client used to make requests.
		httpClient *http.Client
	}

	func Start(req statemachine.Request[Data]) statemachine.Request[Data] {
		if req.Data.httpClient == nil {
			req.Data.httpClient = &http.Client{}
		}

		if req.Data.Author == "" {
			req.Next = RandomAuthor
			return req
		}
		req.Next = RandomQuote
		return req
	}

	func RandomAuthor(req statemachine.Request[Data]) statemachine.Request[Data] {
		const url = "https://api.quotable.io/randomAuthor" // This is a fake URL
		req, err := http.NewRequest("GET", url, nil)
		if err != nil {
			req.Err = err
			return req
		}

		req = req.WithContext(ctx)
		resp, err := args.Data.httpClient.Do(req)
		if err != nil {
			req.Err = err
			return req
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusOK {
			req.Err = fmt.Errorf("unexpected status code: %d", resp.StatusCode)
			return req
		}
		b, err := io.ReadAll(resp.Body)
		if err != nil {
			req.Err = err
			return req
		}
		args.Data.Author = string(b)
		req.Next = RandomQuote
		return req
	}

	func RandomQuote(req statemachine.Request[Data]) statemachine.Request[Data] {
		const url = "https://api.quotable.io/randomQuote" // This is a fake URL
		req, err := http.NewRequest("GET", url, nil)
		if err != nil {
			req.Err = err
			return req
		}

		req = req.WithContext(ctx)
		resp, err := args.Data.httpClient.Do(req)
		if err != nil {
			req.Err = err
			return req
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusOK {
			req.Err = fmt.Errorf("unexpected status code: %d", resp.StatusCode)
			return req
		}
		b, err := io.ReadAll(resp.Body)
		if err != nil {
			req.Err = err
			return req
		}
		args.Data.Quote = string(b)
		req.Next = nil  // This is not needed, but a good way to show that the state machine is done.
		return req
	}

	func main() {
		flag.Parse()

		req := statemachine.Request{
  			Ctx: context.Background(),
     			Data: Data{
				Author: *author,
				httpClient: &http.Client{},
			},
   			Next: Start,
		}

		err := statemachine.Run("Get author quotes", req)
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println(data.Author, "said", data.Quote)
	}

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Option

type Option[T any] func(Request[T]) (Request[T], error)

Option is an option for the Run() function. This is currently unused, but exists for future expansion.

type Request

type Request[T any] struct {

	// Ctx is the context passed to the state function.
	Ctx context.Context

	// Data is the data to be passed to the next state.
	Data T

	// Err is the error to be returned by the state machine. If Err is not nil, the state machine stops.
	Err error

	// Next is the next state to be executed. If Next is nil, the state machine stops.
	// Must be set to the initial state to execute before calling Run().
	Next State[T]
	// contains filtered or unexported fields
}

Request are the request passed to a state function.

func Run

func Run[T any](name string, req Request[T], options ...Option[T]) (Request[T], error)

Run runs the state machine with the given a Request. name is the name of the statemachine for the purpose of OTEL tracing. An error is returned if the state machine fails, name is empty, the Request Ctx/Next is nil or the Err field is not nil.

func (Request[T]) Event

func (r Request[T]) Event(name string, keyValues ...attribute.KeyValue)

Event records an OTEL event into the Request span with name and keyvalues. This allows for stages in your statemachine to record information in each state.

Note: This is a no-op if the Request is not recording.

type State

type State[T any] func(req Request[T]) Request[T]

State is a function that takes a Request and returns a Request. If the returned Request has a nil Next, the state machine stops. If the returned Request has a non-nil Err, the state machine stops and returns the error. If the returned Request has a non-nil next, the state machine continues with the next state.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL