requestmigrations

package module
v0.6.0 Latest Latest
Warning

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

Go to latest
Published: Oct 23, 2024 License: MIT Imports: 11 Imported by: 2

README

requestmigrations
Go Reference

requestmigrations is a Golang implementation of rolling versions for REST APIs. It's a port of the Ruby implementation by ezekg. We use in production with Convoy.

Features

  • API Versioning with date and semver versioning support.
  • Prometheus Instrumentation to track and optimize slow transformations.
  • Support arbitrary data migration. (Coming soon)

Installation

 go get github.com/subomi/requestmigrations 

Usage

This package exposes primarily one API - Migrate. It is used to migrate and rollback changes to your request and response respectively. Here's a short example:

package main 

func createUser(r *http.Request, w http.ResponseWriter) {
  // Identify version and transform the request payload.
  err, vw, rollback := rm.Migrate(r, "createUser")
  if err != nil {
     w.Write("Bad Request")
  }

  // Setup response transformation callback.
  defer rollback(w)

  // ...Perform core business logic...
  data, err := createUserObject(body)
  if err != nil {
    return err 
  }

  // Write response
  body, err := json.Marshal(data)
  if err != nil {
    w.Write("Bad Request")
  }

  vw.Write(body)
}

Writing migrations

A migration is a struct that performs a migration on either a request or a response, but not both. Here's an example:

  type createUserRequestSplitNameMigration struct{} 

  func (c *createUserRequestSplitNameMigration) Migrate(body []byte, h http.Header) ([]byte, http.Header, error) {
    var oUser oldUser 
    err := json.Unmarshal(body, &oUser)
    if err != nil {
      return nil, nil, err 
    }

    var nUser user 
    nUser.Email = oUser.Email 

    splitName := strings.Split(oUser.FullName, " ")
    nUser.FirstName = splitName[0]
    nUser.LastName = splitName[1]

    body, err = json.Marshal(&nUser)
    if err != nil {
      return nil, nil, err 
    }

    return body, h, nil 
  }

Notice from the above that the migration struct name follows a particular structure. The structure adopted is {handlerName}{MigrationType}. The handlerName refers to the exact name of your handler. For example, if you have a handler named LoginUser, any migration on this handler should start with LoginUser. It'll also be what we use in VersionRequest and VersionResponse. The MigrationType can be Request or Response. We use this field to determine if the migration should run on the request or the response payload.

This library doesn't support multiple transformations per version as of the time of this writing. For example, no handler can have multiple changes for the same version.

Example

Check the example directory for a full example. Do the following to run the example:

  1. Run the server.
$ git clone https://github.com/subomi/requestmigrations 

$ cd example/basic 

$ go run *.go
  1. Open another terminal and call the server
# Call the API without specifying a version.
$ curl -s localhost:9000/users \
  -H "Content-Type: application/json" | jq

# Call the API with 2023-04-01 version.
$ curl -s localhost:9000/users \ 
  -H "Content-Type: application/json" \
  -H "X-Example-Version: 2023-04-01" | jq

License

MIT License

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrServerError                 = errors.New("server error")
	ErrInvalidVersion              = errors.New("invalid version number")
	ErrInvalidVersionFormat        = errors.New("invalid version format")
	ErrCurrentVersionCannotBeEmpty = errors.New("current version field cannot be empty")
)

Functions

func Newmigrator added in v0.3.0

func Newmigrator(from, to *Version, avs []*Version, migrations MigrationStore) (*migrator, error)

Types

type GetUserVersionFunc added in v0.3.0

type GetUserVersionFunc func(req *http.Request) (string, error)

type Migration

type Migration interface {
	Migrate(data []byte, header http.Header) ([]byte, http.Header, error)
}

Migration is the core interface each transformation in every version needs to implement. It includes two predicate functions and two transformation functions.

type MigrationStore added in v0.4.0

type MigrationStore map[string]Migrations
migrations := Migrations{
  "2023-02-28": []Migration{
    Migration{},
	   Migration{},
	 },
}

type Migrations

type Migrations []Migration

Migrations is an array of migrations declared by each handler.

type RequestMigration

type RequestMigration struct {
	// contains filtered or unexported fields
}

RequestMigration is the exported type responsible for handling request migrations.

func NewRequestMigration

func NewRequestMigration(opts *RequestMigrationOptions) (*RequestMigration, error)

func (*RequestMigration) Migrate added in v0.5.0

func (rm *RequestMigration) Migrate(r *http.Request, handler string) (error, *response, rollbackFn)

Migrate is the core API for apply transformations to your handlers. It should be called at the start of your handler to transform the body attached to your request before further processing. To transform the response as well, you need to use the rollback and res function to roll changes back and set the handler response respectively.

func (*RequestMigration) RegisterMetrics added in v0.3.0

func (rm *RequestMigration) RegisterMetrics(reg *prometheus.Registry)

func (*RequestMigration) RegisterMigrations

func (rm *RequestMigration) RegisterMigrations(migrations MigrationStore) error

func (*RequestMigration) WriteVersionHeader added in v0.6.0

func (rm *RequestMigration) WriteVersionHeader() func(next http.Handler) http.Handler

type RequestMigrationOptions

type RequestMigrationOptions struct {
	// VersionHeader refers to the header value used to retrieve the request's
	// version. If VersionHeader is empty, we call the GetUserVersionFunc to
	// retrive the user's version.
	VersionHeader string

	// CurrentVersion refers to the API's most recent version. This value should
	// map to the most recent version in the Migrations slice.
	CurrentVersion string

	// GetUserHeaderFunc is a function to retrieve the user's version. This is useful
	// where the user has a persistent version that necessarily being available in the
	// request.
	GetUserVersionFunc GetUserVersionFunc

	// VersionFormat is used to specify the versioning format. The two supported types
	// are DateFormat and SemverFormat.
	VersionFormat VersionFormat
}

RequestMigrationOptions is used to configure the RequestMigration type.

type Version

type Version struct {
	Format VersionFormat
	Value  interface{}
}

func (*Version) Equal

func (v *Version) Equal(vv *Version) bool

func (*Version) IsValid

func (v *Version) IsValid() bool

func (*Version) String

func (v *Version) String() string

type VersionFormat

type VersionFormat string
const (
	SemverFormat VersionFormat = "semver"
	DateFormat   VersionFormat = "date"
)

Directories

Path Synopsis
example

Jump to

Keyboard shortcuts

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