Skip to content
/ wave Public

A Go Programming Language Feature Flagging Library

License

Notifications You must be signed in to change notification settings

aisola/wave

Repository files navigation

Wave

Go programming language feature flagging

GoDoc Widget Build Status Coverage Status Code Climate Go Report Card MIT license

Inspired by the pyrollout python package by Jason Brechin. I've put no genius into this. I've just ported it and made it slightly more go idiomatic.

I've renamed it from rollout to wave simply because I am lazy and do not want to type rollout so many times in my go code. Wave is shorter and ebbs and flows like a software rollout too.

Installation & Tests

Using good ol' go get:

go get -u -v github.com/aisola/wave
go test github.com/aisola/wave/...

Using the new fangled dep:

dep ensure -update github.com/aisola/wave
go test github.com/aisola/wave/...

Typical Usage

While wave allows you to create and manage your own wave instances, the typical user of wave will simply underscore-import a backend and use the default instance. Here, since we are not underscore-importing ay different backend, wave uses in-memory storage.

// ...

import (
       "github.com/aisola/wave"
)

func main() {
     // ...
}

If you want undefined features grant access to all users, you can do that by marking the default Wave.UndefinedAccess field as true.

wave.Default.UndefinedAccess = true

Now add features:

// ...

// Open to all by using the special group 'ALL'
wave.AddFeature(wave.NewFeatureGroups("feature_for_all", wave.ALL))

// Open to select groups
wave.AddFeature(wave.NewFeatureGroups("feature_for_groups", []string{"vip", "early-adopter"}))

// Open to specific user(s), by user ID
wave.AddFeature(wave.NewFeatureUsers("feature_for_users", []string{"123", "456", "789"}))

// ...

Check access to features:

// ...

func UntestedFeature(user wave.User) bool {
     // Because this feature was not defined, access will always be denied, unless you've
     // set Wave.UndefinedAccess as true.
     if !wave.Can(user, "use_untested_feature") {
     	  return false
     }

     DoSomethingCool()

     return true
}


func FooHandler(w http.ResponseWriter, r *http.Request) {
     // The user type that we get from the request context is one passed in by
     // an authentication middleware of sorts. This user object should implement
     // the wave.User interface.
     ctx := r.Context()
     user := ctx.Value("user").(*User)

     if !wave.Can(user, "feature_for_users") {
     	  http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
	  return
     }

     DoSomethingElseCoolHere(w)
}

// ...

Creating multiple Wave instances

In some cases, you may want to create and use more than one instance of wave in your application. For instance, if you have two different APIs serving out of the same binary.

import (
       "github.com/aisola/wave"
       _ "github.com/some/wave/backend"
)
// ...

api1 := wave.NewWave("backend")
api2 := wave.NewWave("backend")

if err := api1.Open("backend://username:password@127.0.0.1:8000/api1"); err != nil {
   log.Fatalf("Could not open up wave backend for api1, %s", err)
}
defer api1.Close()

if err := api2.Open("backend://username:password@127.0.0.1:8000/api2"); err != nil {
   log.Fatalf("Could not open up wave backend for api2, %s", err)
}
defer api2.Close()

// Open to all by using the special group 'ALL'
api1.AddFeature(wave.NewFeatureGroups("feature_for_all", wave.ALL))

// Open to admins
api2.AddFeature(wave.NewFeatureGroups("feature_for_admins", []string{"admins"}))

// ...