Skip to content

Latest commit

History

366 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

kun

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:

  • In-process function call
  • RPC (e.g., HTTP and gRPC)
  • Asynchronous messaging
  • Cron Jobs

中文博客:Go 服务通信工具 Kun

The Zen of 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.

Features

  1. Code Generation Tool

    • HTTP
      • HTTP Server
      • HTTP Client
      • OAS2 Document
    • gRPC
      • Protocol Buffers
      • gRPC Server
      • gRPC Client
    • Event
      • Event Subscriber
      • Event Publisher
    • Cron
      • Cron Jobs
  2. Useful Packages

    • appx: Application framework for HTTP and CRON applications (a wrapper of appx).
    • prometheus: Prometheus metrics utilities.
    • trace: A thin wrapper of x/net/trace for Go kit.
    • werror: Classified business errors.

How it works

HTTP Generation

http-generation

gRPC Generation

grpc-generation

Installation

$ go install github.com/RussellLuo/kun/cmd/kungen@latest
Usage
$ 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

Quick Start

HTTP

NOTE: The following code is located in helloworld.

  1. Define the interface

    typeServiceinterface {
    SayHello(ctx context.Context, namestring) (messagestring, errerror)
    }
  2. Implement the service

    typeGreeterstruct{}
    func (g*Greeter) SayHello(ctx context.Context, namestring) (string, error) {
    return"Hello "+name, nil
    }
  3. Add HTTP annotations

    typeServiceinterface {
    //kun:op POST /messagesSayHello(ctx context.Context, namestring) (messagestring, errerror)
    }
  4. Generate the HTTP code

    $ cd examples/helloworld
    $ kungen ./service.go Service
  5. 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"
    }
  6. See the OAS documentation

    (Click to expand)
    $ 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

gRPC

NOTE: The following code is located in helloworldgrpc.

  1. Define the interface

    typeServiceinterface {
    SayHello(ctx context.Context, namestring) (messagestring, errerror)
    }
  2. Implement the service

    typeGreeterstruct{}
    func (g*Greeter) SayHello(ctx context.Context, namestring) (string, error) {
    return"Hello "+name, nil
    }
  3. Add gRPC annotations

    typeServiceinterface {
    //kun:grpcSayHello(ctx context.Context, namestring) (messagestring, errerror)
    }
  4. Generate the gRPC code

    $ cd examples/helloworldgrpc
    $ kungen ./service.go Service
  5. 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.

HTTP

Annotations

Define the HTTP request operation

Directive //kun:op
Syntax
//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:

  • HTTP method
  • URI
  • Path parameters (defined in URI)
Arguments
  • method: The request method.
  • pattern: The request URI.
    • NOTE: All variables in pattern will automatically be bound to their corresponding method arguments (match by names in lower camel case), as path parameters, if these variables have not yet been specified explicitly by //kun:param.
Examples
  • Single operation:

    typeServiceinterface {
    //kun:op DELETE /users/{id}DeleteUser(ctx context.Context, idint) (errerror)
    }
    // HTTP request:// $ http DELETE /users/101
  • Multiple operations:

    typeServiceinterface {
    //kun:op GET /messages/{messageID}//kun:op GET /users/{userID}/messages/{messageID}GetMessage(ctx context.Context, userIDstring, messageIDstring) (textstring, errerror)
    }
    // See a runnable example in examples/messaging.// HTTP request:// $ http GET /messages/123456// $ http GET /users/me/messages/123456

Define the HTTP request parameters

Directive //kun:param
Syntax
//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.

Arguments
  • argName: The name of the method argument.
    • Argument aggregation: By specifying multiple <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).
    • Blank identifier: By specifying the argName with a double underscore prefix __, the corresponding request parameter(s) will not be mapped to any method argument. See here for more details.
  • parameter: The definition of a single request parameter, to which the method argument will be mapped.
    • Syntax: in=<in> name=<name> required=<required> type=<type> descr=<descr>
    • Options:
      • in:
        • path: The request parameter is a path parameter.
          • Optional: All variables in pattern will automatically be bound to their corresponding method arguments (match by names in lower camel case), as path parameters.
        • query: The request parameter is a query parameter.
          • To receive values from a multi-valued query parameter, the method argument can be defined as a slice of basic type.
        • header: The request parameter is a header parameter.
          • To receive values from a multi-valued header parameter, the method argument can be defined as a slice of basic type.
        • cookie: The request parameter is a cookie parameter.
          • Not supported yet.
        • request: The request parameter is a property of Go's http.Request.
          • This is a special case, and only one property RemoteAddr is available now.
          • Note that parameters located in request have no relationship with OAS.
      • name: The name of the request parameter.
        • Optional: Defaults to argName (snake-case, or lower-camel-case if -snake=false) if not specified.
      • required: Determines whether this parameter is mandatory.
        • Optional: Defaults to false, if not specified.
        • If the parameter location is path, this property will be set to true internally, whether it's specified or not.
      • type: The OAS type of the request parameter.
        • Optional: Defaults to the type of the method argument, if not specified.
      • descr: The OAS description of the request parameter.
        • Optional: Defaults to "", if not specified.
Examples
  • Bind request parameters to simple arguments:

    typeServiceinterface {
    //kun:op PUT /users/{id}//kun:param name in=header name=X-User-NameUpdateUser(ctx context.Context, idint, namestring) (errerror)
    }
    // HTTP request:// $ http PUT /users/101 X-User-Name:tracey
  • Bind multiple request parameters to a struct according to tags:

    typeUserstruct {
    IDint`kun:"in=path"`// name defaults to snake case `id`Namestring`kun:"in=query"`// name defaults to snake case `name`Ageint`kun:"in=header name=X-User-Age"`
    }
    typeServiceinterface {
    //kun:op PUT /users/{id}//kun:param userUpdateUser(ctx context.Context, userUser) (errerror)
    }
    // HTTP request:// $ http PUT /users/101?name=tracey X-User-Age:1
  • Bind multiple query parameters to a struct with no tags:

    typeUserstruct {
    Namestring// equivalent to `kun:"in=query name=name"`Ageint// equivalent to `kun:"in=query name=age"`Hobbies []string// equivalent to `kun:"in=query name=hobbies"`
    }
    typeServiceinterface {
    //kun:op POST /users//kun:param userCreateUser(ctx context.Context, userUser) (errerror)
    }
    // HTTP request:// $ http POST /users?name=tracey&age=1&hobbies=music&hobbies=sport
  • Argument aggregation:

    typeServiceinterface {
    //kun:op POST /logs//kun:param ip in=header name=X-Forwarded-For, in=request name=RemoteAddrLog(ctx context.Context, ip net.IP) (errerror)
    }
    // The equivalent annotations =>// (using backslash-continued annotations)typeServiceinterface {
    //kun:op POST /logs//kun:param ip in=header name=X-Forwarded-For, \// in=request name=RemoteAddrLog(ctx context.Context, ip net.IP) (errerror)
    }
    // 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:

    typeServiceinterface {
    //kun:op POST /users//kun:param name; age; ip in=header name=X-Forwarded-For, in=request name=RemoteAddrCreateUser(ctx context.Context, namestring, ageint, ip net.IP) (errerror)
    }
    // The equivalent annotations =>// (using backslash-continued annotations)typeServiceinterface {
    //kun:op POST /users//kun:param name; \// age; \// ip in=header name=X-Forwarded-For, in=request name=RemoteAddrCreateUser(ctx context.Context, namestring, ageint, ip net.IP) (errerror)
    }
    // HTTP request:// $ http POST /users?name=tracey&age=1

Define the HTTP request body

Directive //kun:body
Syntax
//kun:body <field>

or

//kun:body <manipulation> [; <manipulation2> [; ...]]
Arguments
  • field: The name of the method argument whose value is mapped to the HTTP request body.
    • Optional: When omitted, a struct containing all the arguments (except context.Context), which are not located in path/query/header, will automatically be mapped to the HTTP request body.
    • The special name - 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.
  • manipulation:
    • Syntax: <argName> name=<name> type=<type> descr=<descr> required=<required>
    • Options:
      • argName: The name of the method argument to be manipulated.
      • name: The name of the request parameter.
        • Optional: Defaults to argName (snake-case, or lower-camel-case if -snake=false) if not specified.
      • type: The OAS type of the request parameter.
        • Optional: Defaults to the type of the method argument, if not specified.
      • descr: The OAS description of the request parameter.
        • Optional: Defaults to "", if not specified.
      • required: Determines whether this parameter is mandatory.
        • Optional: Defaults to false, if not specified.
Examples
  • Omitted:

    typeServiceinterface {
    //kun:op POST /usersCreateUser(ctx context.Context, namestring, ageint) (errerror)
    }
    // HTTP request:// $ http POST /users name=tracey age=1
  • Specified as a normal argument:

    typeUserstruct {
    Namestring`json:"name"`Ageint`json:"age"`
    }
    typeServiceinterface {
    //kun:op POST /users//kun:body userCreateUser(ctx context.Context, userUser) (errerror)
    }
    // HTTP request:// $ http POST /users name=tracey age=1
  • Specified as -:

    typeUserstruct {
    NamestringAgeintHobbies []string`kun:"name=hobby"`
    }
    typeServiceinterface {
    //kun:op POST /users//kun:body -CreateUser(ctx context.Context, userUser) (errerror)
    }
    // HTTP request:// $ http POST /users?name=tracey&age=1&hobby=music&hobby=sport
  • Manipulation:

    typeServiceinterface {
    //kun:op POST /users//kun:body age name=user_age type=string descr='The user age'CreateUser(ctx context.Context, namestring, ageint) (errerror)
    }
    // HTTP request:// $ http POST /users name=tracey user_age=1

Define the success HTTP response

Directive //kun:success
Syntax
//kun:success statusCode=<statusCode> body=<body> manip=`<manipulation> [; <manipulation2> [; ...]]`
Arguments
  • statusCode: The status code of the success HTTP response.
    • Optional: Defaults to 200, if not specified.
  • body: The name of the response field whose value is mapped to the HTTP response body.
    • Optional: When omitted, a struct containing all the results (except error) will automatically be mapped to the HTTP response body.
  • manipulation:
    • Syntax: <argName> name=<name> type=<type> descr=<descr>
    • Not supported yet.
Examples
typeUserstruct {
Namestring`json:"name"`Ageint`json:"age"`
}
typeServiceinterface {
//kun:op POST /users//kun:success statusCode=201 body=userCreateUser(ctx context.Context) (userUser, errerror)
}

Define the OAS metadata

Directive //kun:oas
Syntax
//kun:oas <property>=<value>
Arguments
  • property: The property to set. Supported properties:
    • docsPath: The URL path to the OAS documentation itself.
      • Optional: Defaults to "/api" if not specified.
    • title: The title field of Info Object, see Basic Structure.
      • Optional: Defaults to "No Title" if not specified.
    • version: The version field of Info Object, see Basic Structure.
      • Optional: Defaults to "0.0.0" if not specified.
    • description: The description field of Info Object, see Basic Structure.
      • Unavailable: Automatically extracted from the Go documentation of the interface definition.
    • basePath: The basePath property, see API Host and Base URL.
    • tags: A list of tags (comma-separated), see Grouping Operations With Tags.
  • value: The value of the property.
Examples
// 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=usertypeServiceinterface {
//kun:op POST /usersCreateUser(ctx context.Context, namestring, ageint) (errerror)
}

Define the annotation alias

Directive //kun:alias
Syntax
//kun:alias <name>=`<value>`
Arguments
  • name: The name of the alias.
  • value: The string value that the alias represents.
Examples
typeServiceinterface {
//kun:op POST /users//kun:param operatorID in=header name=Authorization required=trueCreateUser(ctx context.Context, operatorIDint) (errerror)
//kun:op DELETE /users/{id}//kun:param operatorID in=header name=Authorization required=trueDeleteUser(ctx context.Context, id, operatorIDint) (errerror)
}
// The equivalent annotations =>//kun:alias opID=`operatorID in=header name=Authorization required=true`typeServiceinterface {
//kun:op POST /users//kun:param $opIDCreateUser(ctx context.Context, operatorIDint) (errerror)
//kun:op DELETE /users/{id}//kun:param $opIDDeleteUser(ctx context.Context, id, operatorIDint) (errerror)
}

Encoding and decoding

See the HTTP Codec interface.

Also see here for examples.

OAS Schema

See the OAS Schema interface.

gRPC

Annotations

Directive //kun:grpc
Syntax
//kun:grpc request=<request> response=<response>
Arguments
  • request: The name of the method argument, whose value will be mapped to the gRPC request.
    • Optional: When omitted, a struct containing all the arguments (except context.Context) will automatically be mapped to the gRPC request.
  • response: The name of the method result, whose value will be mapped to the gRPC response.
    • Optional: When omitted, a struct containing all the results (except error) will automatically be mapped to the gRPC response.
Examples
  • Omitted:

    typeServiceinterface {
    //kun:grpcCreateUser(ctx context.Context, namestring, ageint) (errerror)
    }
    // gRPC request:// $ grpcurl -d '{"name": "tracey", "age": 1}' ... pb.Service/CreateUser
  • Specified:

    typeUserstruct {
    Namestring`json:"name"`Ageint`json:"age"`
    }
    typeServiceinterface {
    //kun:grpc request=userCreateUser(ctx context.Context, userUser) (errerror)
    }
    // gRPC request:// $ grpcurl -d '{"name": "tracey", "age": 1}' ... pb.Service/CreateUser

Event

Annotations

Directive //kun:event
Syntax
//kun:event type=<type> data=<data>
Arguments
  • type: The type of the event.
    • Optional: Defaults to the name of the corresponding method (snake-case, or lower-camel-case if -snake=false) if not specified.
  • data: The name of the method argument whose value is mapped to the event data.
    • Optional: When omitted, a struct containing all the arguments (except context.Context) will automatically be mapped to the event data.
Examples
  • Omitted:

    typeServiceinterface {
    //kun:eventEventCreated(ctx context.Context, idint) (errerror)
    }
    // event: {"type": "event_created", "data": `{"id": 1}`}
  • Specified:

    typeDatastruct {
    IDint`json:"id"`
    }
    typeServiceinterface {
    //kun:event type=created data=dataEventCreated(ctx context.Context, dataData) (errerror)
    }
    // event: {"type": "created", "data": `{"id": 1}`}

Cron

Annotations

Directive //kun:cron
Syntax
//kun:cron name=<name> expr=<expr>
Arguments
  • name: The job name.
    • Optional: Defaults to the name of the corresponding method (snake-case, or lower-camel-case if -snake=false) if not specified.
  • expr: The cron expression.
Examples
  • Name omitted:

    typeServiceinterface {
    //kun:cron expr='@every 5s'SendEmail(ctx context.Context) error
    }
    // job: {"name": "send_email", "expr": "@every 5s"}
  • Name specified:

    typeServiceinterface {
    //kun:cron name=send expr='@every 5s'SendEmail(ctx context.Context) error
    }
    // job: {"name": "send", "expr": "@every 5s"}

Documentation

Checkout the Godoc.

License

MIT

About

A communication toolkit for Go services.

Topics

Resources

Stars

95 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages