A communication toolkit for Go services.
kun is a communication toolkit for Go services. It concentrates on handling the communications between Go services, to free developers to focus on the business logic.
Ultimately, kun may support the following communication types:
中文博客:Go 服务通信工具 Kun。
Focus on the most valuable things
Service communication is important, but is less important than the service itself (i.e., your business value).
Furthermore, it should be effortless to change the communication types.
Write code in Go whenever possible
Prefer Go to other DSLs (e.g., OpenAPI, Protocol Buffers or AsyncAPI) for service definitions.
Maintainability counts
Embrace the spirit of the Clean Architecture for non-trivial applications.
Code Generation Tool
Useful Packages
$ go install github.com/RussellLuo/kun/cmd/kungen@latest
$ kungen -h
kungen [flags] source-file interface-name
-flat
whether to use flat layout (default true)
-fmt
whether to make code formatted (default true)
-force
whether to remove previously generated files before generating new ones
-out string
output directory (default ".")
-snake
whether to use snake-case for default names (default true)
-trace
whether to enable tracing
NOTE: The following code is located in helloworld.
Define the interface
type Service interface {
SayHello(ctx context.Context, name string) (message string, err error)
}
Implement the service
type Greeter struct{}
func (g *Greeter) SayHello(ctx context.Context, name string) (string, error) {
return "Hello " + name, nil
}
Add HTTP annotations
type Service interface {
//kun:op POST /messages
SayHello(ctx context.Context, name string) (message string, err error)
}
Generate the HTTP code
$ cd examples/helloworld
$ kungen ./service.go Service
Consume the service
Run the HTTP server:
$ go run cmd/main.go
2020/09/15 18:06:22 transport=HTTP addr=:8080
Consume by HTTPie:
$ http POST :8080/messages name=Tracey
HTTP/1.1 200 OK
Content-Length: 27
Content-Type: application/json; charset=utf-8
Date: Tue, 15 Sep 2020 10:06:34 GMT
{
"message": "Hello Tracey"
}
See the OAS documentation
$ http GET :8080/api
HTTP/1.1 200 OK
Content-Length: 848
Content-Type: text/plain; charset=utf-8
Date: Tue, 15 Sep 2020 10:08:24 GMT
swagger: "2.0"
info:
title: "No Title"
version: "0.0.0"
description: "Service is used for saying hello."
license:
name: "MIT"
host: "example.com"
basePath: "/"
schemes:
- "https"
consumes:
- "application/json"
produces:
- "application/json"
paths:
/messages:
post:
description: "SayHello says hello to the given name."
operationId: "SayHello"
parameters:
- name: body
in: body
schema:
$ref: "#/definitions/SayHelloRequestBody"
produces:
- application/json; charset=utf-8
responses:
200:
description: ""
schema:
$ref: "#/definitions/SayHelloResponse"
definitions:
SayHelloRequestBody:
type: object
properties:
name:
type: string
SayHelloResponse:
type: object
properties:
message:
type: string
NOTE: The following code is located in helloworldgrpc.
Define the interface
type Service interface {
SayHello(ctx context.Context, name string) (message string, err error)
}
Implement the service
type Greeter struct{}
func (g *Greeter) SayHello(ctx context.Context, name string) (string, error) {
return "Hello " + name, nil
}
Add gRPC annotations
type Service interface {
//kun:grpc
SayHello(ctx context.Context, name string) (message string, err error)
}
Generate the gRPC code
$ cd examples/helloworldgrpc
$ kungen ./service.go Service
Consume the service
Run the gRPC server:
$ go run cmd/main.go
2020/09/15 18:06:22 transport=HTTP addr=:8080
Consume by grpcurl:
$ grpcurl -plaintext -d '{"name": "Tracey"}' :8080 pb.Service/SayHello
{
"message": "Hello Tracey"
}
See more examples here.
//kun:op <method> <pattern>
If a Go method needs to correspond to more than one URI (or HTTP method), you can specify multiple //kun:op
directives, which will produce multiple HTTP request operations.
Note that there are only three possible differences among these HTTP request operations:
//kun:param
.Single operation:
type Service interface {
//kun:op DELETE /users/{id}
DeleteUser(ctx context.Context, id int) (err error)
}
// HTTP request:
// $ http DELETE /users/101
Multiple operations:
type Service interface {
//kun:op GET /messages/{messageID}
//kun:op GET /users/{userID}/messages/{messageID}
GetMessage(ctx context.Context, userID string, messageID string) (text string, err error)
}
// See a runnable example in examples/messaging.
// HTTP request:
// $ http GET /messages/123456
// $ http GET /users/me/messages/123456
//kun:param <argName> [<parameter> [, <parameter2> [, ...]]]
If multiple method arguments are involved, you may need to apply multiple bindings. This can be done by adding a new //kun:param
directive, or by appending the binding to the end of the last //kun:param
directive in a semicolon-separated list.
<parameter>
s in a comma-separated list, multiple request parameters (each one is of basic type or repeated basic type) can be aggregated into one method argument (of any type).__
, the corresponding request parameter(s) will not be mapped to any method argument. See here for more details.in=<in> name=<name> required=<required> type=<type> descr=<descr>
RemoteAddr
is available now.-snake=false
) if not specified.false
, if not specified.true
internally, whether it's specified or not.""
, if not specified.Bind request parameters to simple arguments:
type Service interface {
//kun:op PUT /users/{id}
//kun:param name in=header name=X-User-Name
UpdateUser(ctx context.Context, id int, name string) (err error)
}
// HTTP request:
// $ http PUT /users/101 X-User-Name:tracey
Bind multiple request parameters to a struct according to tags:
type User struct {
ID int `kun:"in=path"` // name defaults to snake case `id`
Name string `kun:"in=query"` // name defaults to snake case `name`
Age int `kun:"in=header name=X-User-Age"`
}
type Service interface {
//kun:op PUT /users/{id}
//kun:param user
UpdateUser(ctx context.Context, user User) (err error)
}
// HTTP request:
// $ http PUT /users/101?name=tracey X-User-Age:1
Bind multiple query parameters to a struct with no tags:
type User struct {
Name string // equivalent to `kun:"in=query name=name"`
Age int // equivalent to `kun:"in=query name=age"`
Hobbies []string // equivalent to `kun:"in=query name=hobbies"`
}
type Service interface {
//kun:op POST /users
//kun:param user
CreateUser(ctx context.Context, user User) (err error)
}
// HTTP request:
// $ http POST /users?name=tracey&age=1&hobbies=music&hobbies=sport
Argument aggregation:
type Service interface {
//kun:op POST /logs
//kun:param ip in=header name=X-Forwarded-For, in=request name=RemoteAddr
Log(ctx context.Context, ip net.IP) (err error)
}
// The equivalent annotations =>
// (using backslash-continued annotations)
type Service interface {
//kun:op POST /logs
//kun:param ip in=header name=X-Forwarded-For, \
// in=request name=RemoteAddr
Log(ctx context.Context, ip net.IP) (err error)
}
// You must customize the decoding of `ip` later (conventionally in another file named `codec.go`).
// See a runnable example in examples/usersvc.
// HTTP request:
// $ http POST /logs
Multiple bindings in a single //kun:param
:
type Service interface {
//kun:op POST /users
//kun:param name; age; ip in=header name=X-Forwarded-For, in=request name=RemoteAddr
CreateUser(ctx context.Context, name string, age int, ip net.IP) (err error)
}
// The equivalent annotations =>
// (using backslash-continued annotations)
type Service interface {
//kun:op POST /users
//kun:param name; \
// age; \
// ip in=header name=X-Forwarded-For, in=request name=RemoteAddr
CreateUser(ctx context.Context, name string, age int, ip net.IP) (err error)
}
// HTTP request:
// $ http POST /users?name=tracey&age=1
//kun:body <field>
or
//kun:body <manipulation> [; <manipulation2> [; ...]]
-
can be used, to define that there is no HTTP request body. As a result, every argument, which is not located in path/query/header, will automatically be mapped to one or more query parameters.<argName> name=<name> type=<type> descr=<descr> required=<required>
-snake=false
) if not specified.""
, if not specified.false
, if not specified.Omitted:
type Service interface {
//kun:op POST /users
CreateUser(ctx context.Context, name string, age int) (err error)
}
// HTTP request:
// $ http POST /users name=tracey age=1
Specified as a normal argument:
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
type Service interface {
//kun:op POST /users
//kun:body user
CreateUser(ctx context.Context, user User) (err error)
}
// HTTP request:
// $ http POST /users name=tracey age=1
Specified as -
:
type User struct {
Name string
Age int
Hobbies []string `kun:"name=hobby"`
}
type Service interface {
//kun:op POST /users
//kun:body -
CreateUser(ctx context.Context, user User) (err error)
}
// HTTP request:
// $ http POST /users?name=tracey&age=1&hobby=music&hobby=sport
Manipulation:
type Service interface {
//kun:op POST /users
//kun:body age name=user_age type=string descr='The user age'
CreateUser(ctx context.Context, name string, age int) (err error)
}
// HTTP request:
// $ http POST /users name=tracey user_age=1
//kun:success statusCode=<statusCode> body=<body> manip=`<manipulation> [; <manipulation2> [; ...]]`
200
, if not specified.<argName> name=<name> type=<type> descr=<descr>
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
type Service interface {
//kun:op POST /users
//kun:success statusCode=201 body=user
CreateUser(ctx context.Context) (user User, err error)
}
//kun:oas <property>=<value>
"/api"
if not specified.title
field of Info Object, see Basic Structure.
"No Title"
if not specified.version
field of Info Object, see Basic Structure.
"0.0.0"
if not specified.description
field of Info Object, see Basic Structure.
basePath
property, see API Host and Base URL.// This is the API documentation of User.
//kun:oas docsPath=/api-docs
//kun:oas title=User-API
//kun:oas version=1.0.0
//kun:oas basePath=/v1
//kun:oas tags=user
type Service interface {
//kun:op POST /users
CreateUser(ctx context.Context, name string, age int) (err error)
}
//kun:alias <name>=`<value>`
type Service interface {
//kun:op POST /users
//kun:param operatorID in=header name=Authorization required=true
CreateUser(ctx context.Context, operatorID int) (err error)
//kun:op DELETE /users/{id}
//kun:param operatorID in=header name=Authorization required=true
DeleteUser(ctx context.Context, id, operatorID int) (err error)
}
// The equivalent annotations =>
//kun:alias opID=`operatorID in=header name=Authorization required=true`
type Service interface {
//kun:op POST /users
//kun:param $opID
CreateUser(ctx context.Context, operatorID int) (err error)
//kun:op DELETE /users/{id}
//kun:param $opID
DeleteUser(ctx context.Context, id, operatorID int) (err error)
}
See the HTTP Codec interface.
Also see here for examples.
See the OAS Schema interface.
//kun:grpc request=<request> response=<response>
Omitted:
type Service interface {
//kun:grpc
CreateUser(ctx context.Context, name string, age int) (err error)
}
// gRPC request:
// $ grpcurl -d '{"name": "tracey", "age": 1}' ... pb.Service/CreateUser
Specified:
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
type Service interface {
//kun:grpc request=user
CreateUser(ctx context.Context, user User) (err error)
}
// gRPC request:
// $ grpcurl -d '{"name": "tracey", "age": 1}' ... pb.Service/CreateUser
//kun:event type=<type> data=<data>
-snake=false
) if not specified.Omitted:
type Service interface {
//kun:event
EventCreated(ctx context.Context, id int) (err error)
}
// event: {"type": "event_created", "data": `{"id": 1}`}
Specified:
type Data struct {
ID int `json:"id"`
}
type Service interface {
//kun:event type=created data=data
EventCreated(ctx context.Context, data Data) (err error)
}
// event: {"type": "created", "data": `{"id": 1}`}
//kun:cron name=<name> expr=<expr>
-snake=false
) if not specified.Name omitted:
type Service interface {
//kun:cron expr='@every 5s'
SendEmail(ctx context.Context) error
}
// job: {"name": "send_email", "expr": "@every 5s"}
Name specified:
type Service interface {
//kun:cron name=send expr='@every 5s'
SendEmail(ctx context.Context) error
}
// job: {"name": "send", "expr": "@every 5s"}
Checkout the Godoc.