Skip to content

Instantly share code, notes, and snippets.

@mrcrilly
Last active February 27, 2018 04:13
Show Gist options
  • Save mrcrilly/f453e1f274bada2476357410b1a7fa8a to your computer and use it in GitHub Desktop.
Save mrcrilly/f453e1f274bada2476357410b1a7fa8a to your computer and use it in GitHub Desktop.
This is why I think an FSM is a good idea on APIs
package main
import (
"fmt"
"log"
"net/http"
"github.com/julienschmidt/httprouter"
"github.com/looplab/fsm"
)
func newHouse(name, state string) *house {
h := &house{
name: name,
}
h.forSale = (state == "forsale")
h.fsm = fsm.NewFSM(
state,
fsm.Events{
{
Name: "buy",
Src: []string{"forsale"},
Dst: "owned",
},
{
Name: "sell",
Src: []string{"owned"},
Dst: "forsale",
},
},
fsm.Callbacks{
"sell": sell,
"buy": buy,
},
)
return h
}
type database struct {
houses map[string]*house
}
func (d *database) get(name string) *house { return d.houses[name] }
func (d *database) upsert(name string, h *house) { d.houses[name] = h }
var db = &database{houses: map[string]*house{}}
type house struct {
name string // poor man's address
price float64
forSale bool
fsm *fsm.FSM // keep track of our state
}
func sell(e *fsm.Event) {
name := e.Args[0].(string)
h := db.get(name)
h.forSale = true
db.upsert(name, h)
}
func buy(e *fsm.Event) {
name := e.Args[0].(string)
h := db.get(name)
h.forSale = false
db.upsert(name, h)
}
func getHouseByName(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
h := db.get(ps.ByName("name"))
fmt.Fprintf(w, "house for sale: %v\n", h.forSale)
}
func sellHouseByName(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
h := db.get(ps.ByName("name"))
err := h.fsm.Event("sell", ps.ByName("name"))
if err != nil {
fmt.Fprintf(w, "unable to sell house: %s\n", err.Error())
return
}
fmt.Fprintf(w, "house marked for sale\n")
}
func buyHouseByName(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
h := db.get(ps.ByName("name"))
err := h.fsm.Event("buy", ps.ByName("name"))
if err != nil {
fmt.Fprintf(w, "unable to buy house: %s\n", err.Error())
return
}
fmt.Fprintf(w, "congrats on the house\n")
}
func main() {
db.upsert("mike", newHouse("mike", "forsale"))
db.upsert("taylor", newHouse("taylor", "owned"))
router := httprouter.New()
router.GET("/house/:name", getHouseByName)
router.GET("/house/:name/sell", sellHouseByName)
router.GET("/house/:name/buy", buyHouseByName)
log.Fatal(http.ListenAndServe(":8080", router))
}
$ curl -sXGET http://localhost:8080/house/taylor
house for sale: false
$ curl -sXGET http://localhost:8080/house/taylor/sell
house marked for sale
$ curl -sXGET http://localhost:8080/house/taylor
house for sale: true
$ curl -sXGET http://localhost:8080/house/taylor/buy
congrats on the house
$ curl -sXGET http://localhost:8080/house/taylor
house for sale: false
$ curl -sXGET http://localhost:8080/house/mike/sell
unable to sell house: event sell inappropriate in current state forsale
$ curl -sXGET http://localhost:8080/house/mike/sell
unable to sell house: event sell inappropriate in current state forsale
$ curl -sXGET http://localhost:8080/house/mike/buy
congrats on the house
$ curl -sXGET http://localhost:8080/house/mike/sell
house marked for sale
$ curl -sXGET http://localhost:8080/house/mike/buy
congrats on the house
$ curl -sXGET http://localhost:8080/house/mike/buy
unable to buy house: event buy inappropriate in current state owned
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment