Golax: A Go Implementation for The Lax Framework

Golax is the official go implementation for the Lax framework.

About Lax

Lax wants to be the best "user experience" for developers making REST APIs.

The design principles for Lax are:

  • The lowest language overhead
  • Extremely fast to develop
  • Very easy to read and trace.

Getting started

my_api := golax.NewApi()

my_api.Root.
    Interceptor(golax.InterceptorError).
    Interceptor(myLogingInterceptor)

my_api.Root.Node("hello").
    Method("GET", func(c *golax.Context) {
        // At this point, Root interceptors has been already executed
        fmt.Fprintln(c.Response, "Hello world!")
    })

my_api.Serve()

Routing example

Routing is based on nodes.

There are three types: static, regex and parameter.

  • static: Only matches with the url part if it is exactly the same.
  • regex: Surrounded by ( and ), if the regex match.
  • parameter: Surrounded by { and }, always matches.

Performance

The performance compared with the most popular alternative is very similar (actually golax performs slightly better) however code readability and maintainability is far better with golax implementation.

Tests has been executed in a Intel(R) Core(TM) i5-3210M CPU @ 2.50GHz.

Learn more about this https://github.com/fulldump/golax-performance.

How interceptor works

If I want to handle a GET /users/1234/stats request, all interceptors in nodes from <root> to .../stats are executed:

Normal flow

To abort the execution, call to c.Error(404, "Resource not found"):

Break flow

Handling parameters

my_api := golax.NewApi()

my_api.Root.
    Node("users").
    Node("{user_id}").
    Method("GET", func (c *golax.Context) {
        fmt.Fprintln(c.Response, "You are looking for user " + c.Parameter)
    })

my_api.Serve()

It is also possible get all parameters:

func (c *golax.Context) {
    fmt.Fprintln(c.Response, "All parameters:", c.Parameters)
}

Support for Google custom methods

According to Google's API design guidelines to map RPC services to REST HTTP, it describes custom methods as extra operations that can not be easyly mapped to HTTP verbs. More info about custom methods

For example, this URL has a custom method :activate:

https://my.service.com/v1/users/31231231231:activate

Golax support custom methods as operations:

my_api.Root.
    Node("v1").
    Node("users").
    Node("{user_id}").
    Operation("activate").
    Method("POST", func(c *golax.Context) {
        user_id := c.Parameters["{user_id}"]"
        fmt.Fprintln(c.Response, "Here is custom method ':activate' for user "+user_id)
    })

Sample use cases

TODO: put here some examples to cover cool things:

  • fluent implementation
  • node cycling
  • readability
  • node preference
  • sample logging interceptor
  • sample auth interceptor
  • sample api errors

Related docs:

Author: Fulldump
Source Code: https://github.com/fulldump/golax 
License: MIT license

#go #golang #framework 

What is GEEK

Buddha Community

Golax: A Go Implementation for The Lax Framework
Fannie  Zemlak

Fannie Zemlak

1599854400

What's new in the go 1.15

Go announced Go 1.15 version on 11 Aug 2020. Highlighted updates and features include Substantial improvements to the Go linker, Improved allocation for small objects at high core counts, X.509 CommonName deprecation, GOPROXY supports skipping proxies that return errors, New embedded tzdata package, Several Core Library improvements and more.

As Go promise for maintaining backward compatibility. After upgrading to the latest Go 1.15 version, almost all existing Golang applications or programs continue to compile and run as older Golang version.

#go #golang #go 1.15 #go features #go improvement #go package #go new features

Golax: A Go Implementation for The Lax Framework

Golax is the official go implementation for the Lax framework.

About Lax

Lax wants to be the best "user experience" for developers making REST APIs.

The design principles for Lax are:

  • The lowest language overhead
  • Extremely fast to develop
  • Very easy to read and trace.

Getting started

my_api := golax.NewApi()

my_api.Root.
    Interceptor(golax.InterceptorError).
    Interceptor(myLogingInterceptor)

my_api.Root.Node("hello").
    Method("GET", func(c *golax.Context) {
        // At this point, Root interceptors has been already executed
        fmt.Fprintln(c.Response, "Hello world!")
    })

my_api.Serve()

Routing example

Routing is based on nodes.

There are three types: static, regex and parameter.

  • static: Only matches with the url part if it is exactly the same.
  • regex: Surrounded by ( and ), if the regex match.
  • parameter: Surrounded by { and }, always matches.

Performance

The performance compared with the most popular alternative is very similar (actually golax performs slightly better) however code readability and maintainability is far better with golax implementation.

Tests has been executed in a Intel(R) Core(TM) i5-3210M CPU @ 2.50GHz.

Learn more about this https://github.com/fulldump/golax-performance.

How interceptor works

If I want to handle a GET /users/1234/stats request, all interceptors in nodes from <root> to .../stats are executed:

Normal flow

To abort the execution, call to c.Error(404, "Resource not found"):

Break flow

Handling parameters

my_api := golax.NewApi()

my_api.Root.
    Node("users").
    Node("{user_id}").
    Method("GET", func (c *golax.Context) {
        fmt.Fprintln(c.Response, "You are looking for user " + c.Parameter)
    })

my_api.Serve()

It is also possible get all parameters:

func (c *golax.Context) {
    fmt.Fprintln(c.Response, "All parameters:", c.Parameters)
}

Support for Google custom methods

According to Google's API design guidelines to map RPC services to REST HTTP, it describes custom methods as extra operations that can not be easyly mapped to HTTP verbs. More info about custom methods

For example, this URL has a custom method :activate:

https://my.service.com/v1/users/31231231231:activate

Golax support custom methods as operations:

my_api.Root.
    Node("v1").
    Node("users").
    Node("{user_id}").
    Operation("activate").
    Method("POST", func(c *golax.Context) {
        user_id := c.Parameters["{user_id}"]"
        fmt.Fprintln(c.Response, "Here is custom method ':activate' for user "+user_id)
    })

Sample use cases

TODO: put here some examples to cover cool things:

  • fluent implementation
  • node cycling
  • readability
  • node preference
  • sample logging interceptor
  • sample auth interceptor
  • sample api errors

Related docs:

Author: Fulldump
Source Code: https://github.com/fulldump/golax 
License: MIT license

#go #golang #framework 

Best Android Mobile App Development Frameworks

Are you looking for the best Android app development frameworks? Get the best Android app development frameworks that help to build the top-notch Android mobile app.

For more info:
Website: https://www.appcluesinfotech.com/
Email: info@appcluesinfotech.com
Call: +1-978-309-9910

#best android mobile app development frameworks #top mobile app development frameworks #android app development frameworks #top frameworks for android app development #most popular android app development frameworks #app development frameworks

Go-web-framework-benchmark: Go Web Framework Benchmark

go-web-framework-benchmark

This benchmark suite aims to compare the performance of Go web frameworks. It is inspired by Go HTTP Router Benchmark but this benchmark suite is different with that. Go HTTP Router Benchmark suit aims to compare the performance of routers but this Benchmark suit aims to compare whole HTTP request processing.

Last Test Updated: 2020-05

test environment

  • CPU: QEMU Virtual CPU version(1.8GHZ, 8 cores)
  • Memory: 16G
  • Go: go1.16.3 linux/amd64
  • OS: CentOS Linux release 7.5.1804 (Core)

Motivation

When I investigated performance of Go web frameworks, I found Go HTTP Router Benchmark, created by Julien Schmidt. He also developed a high performance http router: httprouter. I had thought I got the performance result until I created a piece of codes to mock the real business logics:

api.Get("/rest/hello", func(c *XXXXX.Context) {
	sleepTime := strconv.Atoi(os.Args[1]) //10ms
	if sleepTime > 0 {
		time.Sleep(time.Duration(sleepTime) * time.Millisecond)
	}

	c.Text("Hello world")
})

When I use the above codes to test those web frameworks, the token time of route selection is not so important in the whole http request processing, although performance of route selection of web frameworks are very different.

So I create this project to compare performance of web frameworks including connection, route selection, handler processing. It mocks business logics and can set a special processing time.

The you can get some interesting results if you use it to test.

Implementation

When you test a web framework, this test suit will starts a simple http server implemented by this web framework. It is a real http server and only contains GET url: "/hello".

When this server processes this url, it will sleep n milliseconds in this handler. It mocks the business logics such as:

  • read data from sockets
  • write data to disk
  • access databases
  • access cache servers
  • invoke other microservices
  • ……

It contains a test.sh that can do those tests automatically.

It uses wrk to test.

Basic Test

The first test case is to mock 0 ms, 10 ms, 100 ms, 500 ms processing time in handlers.

Benchmark (Round 3) the concurrency clients are 5000.

Latency (Round 3) Latency is the time of real processing time by web servers. The smaller is the better.

Allocs (Round 3) Allocs is the heap allocations by web servers when test is running. The unit is MB. The smaller is the better.

If we enable http pipelining, test result as below:

benchmark pipelining (Round 2)

Concurrency Test

In 30 ms processing time, the test result for 100, 1000, 5000 clients is:

concurrency (Round 3)

Latency (Round 3)

Latency (Round 3)

If we enable http pipelining, test result as below:

concurrency pipelining(Round 2)

cpu-bound case Test

cpu-bound (5000 concurrency)

Usage

You should install this package first if you want to run this test.

go get github.com/smallnest/go-web-framework-benchmark

It takes a while to install a large number of dependencies that need to be downloaded. Once that command completes, you can run:

cd $GOPATH/src/github.com/smallnest/go-web-framework-benchmark
go build -o  gowebbenchmark *.go
./test.sh

It will generate test results in processtime.csv and concurrency.csv. You can modify test.sh to execute your customized test cases.

  • If you also want to generate latency data and allocation data, you can run the script:
./test-latency.sh
  • If you don't want use keepalive, you can run:
./test-latency-nonkeepalive.sh
  • If you want to test http pipelining, you can run:
./test-pipelining.sh
  • If you want to test some of web frameworks, you can modify the test script and only keep your selected web frameworks:
……
web_frameworks=( "default" "ace" "beego" "bone" "denco" "echov1" "echov2standard" "echov2fasthttp" "fasthttp-raw" "fasthttprouter" "fasthttp-routing" "gin" "gocraftWeb" "goji" "gojiv2" "gojsonrest" "gorestful" "gorilla" "httprouter" "httptreemux" "lars" "lion" "macaron" "martini" "pat" "r2router" "tango" "tiger" "traffic" "violetear" "vulcan")
……
  • If you want to test all cases, you can run:
./test-all.sh

Plot

you can run the shell script plot.sh in testresults directory and it can generate all images in its parent directory.

Add new web framework

Welcome to add new Go web frameworks. You can follow the below steps and send me a pull request.

  1. add your web framework link in README
  2. add a hello implementation in server.go
  3. add your webframework in libs.sh

Please add your web framework alphabetically.

Tested web frameworks (in alphabetical order)

Only test those webframeworks which are stable

some libs have not been maintained and the test code has removed them

Author: Smallnest
Source Code: https://github.com/smallnest/go-web-framework-benchmark 
License: Apache-2.0 license

#go #golang #web #framework #benchmark 

Nigel  Uys

Nigel Uys

1651179240

Cacheme-go: Schema Based, Typed Redis Caching/memoize Framework for Go

cacheme - Redis Caching Framework For Go

  • Statically Typed - 100% statically typed using code generation. Drop-in replacement, no reflect/type-assertion.
  • Scale Efficiently - thundering herd protection via pub/sub.
  • Cluster Support - same API for redis & redis cluster.
  • Memoize - dynamic key params based on code generation.
  • Versioning - cache versioning for better management.
  • Pipeline - reduce io cost by redis pipeline.

🌀 Read this first: Caches, Promises and Locks. This is how caching part works in cacheme.

🌀 Real world example with Echo and Ent: https://github.com/Yiling-J/echo-ent-cacheme-example

// old
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
comment, err := ent.Comment.Get(context.Background(), int(id))

// new
comment, err := cacheme.CommentCacheStore.Get(c.Request().Context(), c.Param("id"))

Installation

go get github.com/Yiling-J/cacheme-go/cmd

After installing cacheme-go codegen, go to the root directory(or the directory you think cacheme should stay) of your project, and run:

go run github.com/Yiling-J/cacheme-go/cmd init

The command above will generate cacheme directory under current directory:

└── cacheme
    ├── fetcher
    │   └── fetcher.go
    └── schema
        └── schema.go

It's up to you where the cacheme directory should be, just remember to use the right directory in Store Generation step.

Add Schema

Edit schema.go and add some schemas:

package schema

import (
	"time"
	cacheme "github.com/Yiling-J/cacheme-go"
)

var (
	// default prefix for redis keys
	Prefix = "cacheme"
	// store schemas
	Stores = []*cacheme.StoreSchema{
		{
			Name:         "Simple",
			Key:          "simple:{{.ID}}",
			To:           "",
			Version:      1,
			TTL:          5 * time.Minute,
			Singleflight: false,
			MetaData: false,
		},
	}
)

More details here

Store Generation

Run code generation from the root directory of the project as follows:

# this will use default schema path ./cacheme/schema
go run github.com/Yiling-J/cacheme-go/cmd generate

Or you can use custom schema path:

go run github.com/Yiling-J/cacheme-go/cmd generate ./yours/cacheme/schema

This produces the following files:

└── cacheme
    ├── fetcher
    │   └── fetcher.go
    ├── schema
    │   └── schema.go
    ├── store
    │   ├── base.go
    │   └── simple.go
    └── store.go

If you update schema, just run generate again.

Add Fetcher

Each cache store can provide a fetch function in fetcher.go, you should call this Setup function before create client:

import "your/cacheme/store"

func Setup() {
	store.SimpleCacheStore.Fetch = func(ctx context.Context, ID string) (string, error) {
		return ID, nil
	}
}

You can setup fetch functions in any place, using any pattern, not restricted to this file. Just make sure you have a fetch function when using store.

Use Your Stores

Create client and setup fetcher

import (
	"your_project/cacheme"
	"your_project/cacheme/fetcher"
)

func main() {
	// setup fetcher
	fetcher.Setup()
	// create client
	client := cacheme.New(
		redis.NewClient(&redis.Options{
			Addr:     "localhost:6379",
			Password: "",
			DB:       0,
		}),
	)
	// or cluster client
	client := cacheme.NewCluster(
		redis.NewClusterClient(&redis.ClusterOptions{
			Addrs: []string{
				":7000",
				":7001",
				":7002"},
		}),
	)
}

Store API

Get single result: Get

Get cached result. If not in cache, call fetch function and store data to Redis.

// "foo" is the {{.ID}} part of the schema
result, err := client.SimpleCacheStore.Get(ctx, "foo")

Get pipeline results: GetP

Get multiple keys from multiple stores using pipeline. For each key, if not in cache, call fetch function and store data to Redis.

  • single store
pipeline := client.NewPipeline()
ids := []string{"1", "2", "3", "4"}
var ps []*store.SimplePromise
for _, i := range ids {
	promise, err := client.SimpleCacheStore.GetP(ctx, pipeline, i)
	ps = append(ps, promise)
}
err = pipeline.Execute(ctx)
fmt.Println(err)

for _, promise := range ps {
	r, err := promise.Result()
	fmt.Println(r, err)
}

Consider using GetM API for single store, see GetM example below.

  • multiple stores
// same pipeline for different stores
pipeline := client.NewPipeline()

ids := []string{"1", "2", "3", "4"}
var ps []*store.SimplePromise // cache string
var psf []*store.FooPromise // cache model.Foo struct
for _, i := range ids {
	promise, err := client.SimpleCacheStore.GetP(ctx, pipeline, i)
	ps = append(ps, promise)
}
for _, i := range ids {
	promise, err := client.FooCacheStore.GetP(ctx, pipeline, i)
	psf = append(psf, promise)
}
// execute only once
err = pipeline.Execute(ctx)
// simple store results
for _, promise := range ps {
	r, err := promise.Result()
	fmt.Println(r, err)
}
// foo store results
for _, promise := range psf {
	r, err := promise.Result()
	fmt.Println(r, err)
}

Get multiple results from single store: GetM

Get multiple keys from same store, also using Redis pipeline. For each key, if not in cache, call fetch function and store data to Redis.

qs, err := client.SimpleCacheStore.GetM("foo").GetM("bar").GetM("xyz").Do(ctx)
// qs is a queryset struct, support two methods: GetSlice and Get
// GetSlice return ordered results slice
r, err := qs.GetSlice() // r: {foo_result, bar_result, xyz_result}
// Get return result of given param
r, err := qs.Get("foo") // r: foo_result
r, err := qs.Get("bar") // r: bar_result
r, err := qs.Get("fake") // error, because "fake" not in queryset

You can also initialize a getter using MGetter

getter := client.SimpleCacheStore.MGetter()
for _, id := range ids {
	getter.GetM(id)
}
qs, err := getter.Do(c.Request().Context())

Invalid single cache: Invalid

err := client.SimpleCacheStore.Invalid(ctx, "foo")

Update single cache: Update

err := client.SimpleCacheStore.Update(ctx, "foo")

Invalid all keys: InvalidAll

Only works when you enable MetaData option in schema.

// invalid all version 1 simple cache
client.SimpleCacheStore.InvalidAll(ctx, "1")

Schema Definition

Each schema has 5 fields:

  • Name - store name, will be struct name in generated code, capital first.
  • Key - key with variable using go template syntax, Variable name will be used in code generation.
  • To - cached value, type of value will be used in code generation. Examples:
    • string: ""
    • int: 1
    • struct: model.Foo{}
    • struct pointer: &model.Foo{}
    • slice: []model.Foo{}
    • map: map[model.Foo]model.Bar{}
  • Version - version interface, can be string, int, or callable func() string.
  • TTL - redis ttl using go time.
  • Singleflight - bool, if true, concurrent requests to same key on same executable will call Redis only once
  • MetaData - bool, if true, each store will save all generated keys to a Redis Set, so InvalidAll method can work.

Notes:

  • Duplicate name/key is not allowed.
  • Everytime you update schema, run code generation again.
  • Not all store API support Singleflight option:
    • Get: support.
    • GetM: support. singleflight key will be the combination of all keys, order by alphabetical.
    • GetP: not support.
  • Version callable can help you managing version better. Example:
 // models.go
 const FooCacheVersion = "1"
 type Foo struct {}
 const BarCacheVersion = "1"
 type Bar struct {Foo: Foo}
 // schema.go
 // version has 3 parts: foo version & bar version & global version number
 // if you change struct, update FooCacheVersion or BarCacheVersion
 // if you change fetcher function or ttl or something else, change global version number
 {
 	Name:    "Bar",
 	Key:     "bar:{{.ID}}:info",
 	To:      model.Bar{},
 	Version: func() string {return model.FooCacheVersion + model.BarCacheVersion + "1"},
 	TTL:     5 * time.Minute,
 },
  • If set Singleflight to true, Cacheme Get command will be wrapped in a singleflight, so concurrent requests to same key will call Redis only once. Let's use some example to explain this:
    • you have some products to sell, and thousands people will view the detail at same time, so the product key product:1:info may be hit 100000 times per second. Now you should turn on singleflight, and the actually redis hit may reduce to 5000.
    • you have cache for user shopping cart user:123:cart, only the user himself can see that. Now no need to use singleflight, becauese there shouldn't be concurrent requests to that key.
    • you are using serverless platform, AWS Lambda or similar. So each request runs in isolated environment, can't talk to each other through channels. Then singleflight make no sense.
  • Full redis key has 3 parts: prefix + schema key + version. Schema Keycategory:{{.categoryID}}:book:{{.bookID}} with prefix cacheme, version 1 will generate key:
 cacheme:category:1:book:3:v1
  • Also you will see categoryID and bookID in generated code, as fetch func params.

Logger

You can use custom logger with cacheme, your logger should implement cacheme logger interface:

type Logger interface {
	Log(store string, key string, op string)
}

Here store is the store tag, key is cache key without prefix, op is operation type. Default logger is NOPLogger, just return and do nothing.

Set client logger:

logger := &YourCustomLogger{}
client.SetLogger(logger)

Operation Types:

  • HIT: cache hit to redis, if you enable singleflight, grouped requests only log once.
  • MISS: cache miss
  • FETCH: fetch data from fetcher

Performance

Parallel benchmarks of Cacheme

  • params: 10000/1000000 hits, 10 keys loop, TTL 10s, SetParallelism(100), singleflight on
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkCachemeGetParallel-12    	   10000	    198082 ns/op
BenchmarkCachemeGetParallel-12    	 1000000	      9501 ns/op

English | 中文

Author: Yiling-J
Source Code: https://github.com/Yiling-J/cacheme-go 
License: Apache-2.0 License

#go #golang #redis #framework