kok (pronounced keÉŞ-oĘŠ-keÉŞ) is a toolkit of Go kit.
- Embrace the Clean Architecture for non-trivial applications.
- Just write code for your business logic, generate everything else.
- Implement the service once, consume it in various ways (in-process function call or RPC).
-
Code Generation Tool
- Endpoint
- Transport
- HTTP
- HTTP Server
- HTTP Test
- HTTP Client
- OAS-v2 Documentation
- gRPC
- gRPC Server
- gRPC Client
- HTTP
-
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.
$ go get -u github.com/RussellLuo/kok/cmd/kokgenUsage
$ kokgen -h
kokgen [flags] source-file interface-name
-flat
whether to use flat layout (default true)
-fmt
whether to make code formatted (default true)
-out string
output directory (default ".")
-snake
whether to use snake-case for default names (default true)
-test string
the YAML file that provides test-cases for HTTP (default "./http.test.yaml")
-trace
whether to enable tracingNOTE: 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 { // @kok(op): POST /messages SayHello(ctx context.Context, name string) (message string, err error) }
-
Generate the HTTP code
$ cd examples/helloworld $ kokgen ./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
(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
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 { // @kok(grpc) SayHello(ctx context.Context, name string) (message string, err error) }
-
Generate the gRPC code
$ cd examples/helloworldgrpc $ kokgen ./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.
Define the HTTP request operation
-
Key:
@kok(op) -
Value:
<method> <pattern>- method: The request method
- pattern: The request URL
- NOTE: All variables in pattern will automatically be bound to their corresponding method arguments (matches by name in lower camel case), as path parameters, if the variables are not yet specified as path parameters explicitly by
@kok(param).
- NOTE: All variables in pattern will automatically be bound to their corresponding method arguments (matches by name in lower camel case), as path parameters, if the variables are not yet specified as path parameters explicitly by
-
Example:
type Service interface { // @kok(op): DELETE /users/{id} DeleteUser(ctx context.Context, id int) (err error) } // HTTP request: // $ http DELETE /users/101
Define the HTTP request parameters
- Key:
@kok(param) - Value:
<argName> < in:<in>,name:<name>,required:<required>- argName: The name of the method argument.
- Argument aggregation: By specifying the same argName, multiple request parameters (each one is of basic type or repeated basic type) can be aggregated into one method argument (of any type).
- You do not need to repeat the argName, only the first one is required.
- 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.
- Argument aggregation: By specifying the same argName, multiple request parameters (each one is of basic type or repeated basic type) can be aggregated into one method argument (of any type).
- in:
- path: The method argument is sourced from a path parameter.
- Optional: All variables in pattern will automatically be bound to their corresponding method arguments (matches by name in lower camel case), as path parameters.
- query: The method argument is sourced from 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 method argument is sourced from a header parameter.
- cookie: The method argument is sourced from a cookie parameter.
- Not supported yet.
- request: The method argument is sourced from a property of Go's http.Request.
- This is a special case, and only one property
RemoteAddris available now. - Note that parameters located in request have no relationship with OAS.
- This is a special case, and only one property
- path: The method argument is sourced from a path parameter.
- name: The name of the corresponding request parameter.
- Optional: Defaults to argName (snake-case, or lower-camel-case if
-snake=false) if not specified.
- Optional: Defaults to argName (snake-case, or lower-camel-case if
- descr: The OAS description of the corresponding request parameter.
- Optional: Defaults to
""if not specified.
- Optional: Defaults to
- 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
trueinternally, whether it's specified or not.
- Optional: Defaults to
- argName: The name of the method argument.
- Example:
-
Bind request parameters to simple arguments:
type Service interface { // @kok(op): PUT /users/{id} // @kok(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 `kok:"path.id"` Name string `kok:"query.name"` Age int `kok:"header.X-User-Age"` } type Service interface { // @kok(op): PUT /users/{id} // @kok(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 Age int Hobbies []string } type Service interface { // @kok(op): POST /users // @kok(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 { // @kok(op): POST /logs // @kok(param): ip < in:header,name:X-Forwarded-For // @kok(param): ip < in:request,name:RemoteAddr Log(ctx context.Context, ip net.IP) (err error) } // The equivalent annotations. type Service interface { // @kok(op): POST /logs // @kok(param): ip < in:header,name:X-Forwarded-For // @kok(param): < 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 examples in the `Encoding and decoding` section. // HTTP request: // $ http POST /logs
-
Define the HTTP request body
- Key:
@kok(body) - Value:
<field>orbody:<field>,name:<argName>=<name>,descr:<argName>=<descr>- 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, 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.
- argName: The name of the method argument to be manipulated.
- name: The name of the corresponding request parameter.
- Optional: Defaults to argName (snake-case, or lower-camel-case if
-snake=false) if not specified.
- Optional: Defaults to argName (snake-case, or lower-camel-case if
- descr: The OAS description of the corresponding request parameter.
- Optional: Defaults to
""if not specified.
- Optional: Defaults to
- field: The name of the method argument whose value is mapped to the HTTP request body.
- Example:
-
Omitted:
type Service interface { // @kok(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 { // @kok(op): POST /users // @kok(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 `kok:"name"` Age int `kok:"age"` Hobbies []string `kok:"hobby"` } type Service interface { // @kok(op): POST /users // @kok(body): - CreateUser(ctx context.Context, user User) (err error) } // HTTP request: // $ http POST /users?name=tracey&age=1&hobby=music&hobby=sport
-
Define the success HTTP response
-
Key:
@kok(success) -
Value:
statusCode:<statusCode>,body:<body>- 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.
- statusCode: The status code of the success HTTP response.
-
Example:
type User struct { Name string `json:"name"` Age int `json:"age"` } type Service interface { // @kok(op): POST /users // @kok(success): statusCode:201,body:user CreateUser(ctx context.Context) (user User, err error) }
Define the OAS metadata
-
Key:
@kok(oas) -
Value:
<property>:<value><property>: The property to set. Supported properties:- docsPath: The URL path to the OAS documentation itself.
- Optional: Defaults to
"/api"if not specified.
- Optional: Defaults to
- title: The
titlefield of Info Object, see Basic Structure.- Optional: Defaults to
"No Title"if not specified.
- Optional: Defaults to
- version: The
versionfield of Info Object, see Basic Structure.- Optional: Defaults to
"0.0.0"if not specified.
- Optional: Defaults to
- description: The
descriptionfield of Info Object, see Basic Structure.- Unavailable: Automatically extracted from the Go documentation of the interface definition.
- basePath: The
basePathproperty, see API Host and Base URL. - tags: A list of tags (comma-separated), see Grouping Operations With Tags.
- docsPath: The URL path to the OAS documentation itself.
<value>: The value of the property.
-
Example:
// This is the API documentation of User. // @kok(oas): docsPath:/api-docs // @kok(oas): title:User API // @kok(oas): version:1.0.0 // @kok(oas): basePath:/v1 // @kok(oas): tags:user type Service interface { // @kok(op): POST /users CreateUser(ctx context.Context, name string, age int) (err error) }
Define the annotation alias
-
Key:
@kok(alias) -
Value:
<name>=`<value>`<name>: The name of the alias.<value>: The string value that the alias represents.
-
Example:
type Service interface { // @kok(op): POST /users // @kok(param): operatorID < in:header,name:Authorization,required:true CreateUser(ctx context.Context, operatorID int) (err error) // @kok(op): DELETE /users/{id} // @kok(param): operatorID < in:header,name:Authorization,required:true DeleteUser(ctx context.Context, id, operatorID int) (err error) } // The equivalent annotations => // @kok(alias): opID=`operatorID < in:header,name:Authorization,required:true` type Service interface { // @kok(op): POST /users // @kok(param): $opID CreateUser(ctx context.Context, operatorID int) (err error) // @kok(op): DELETE /users/{id} // @kok(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.
- Key:
@kok(grpc) - Value:
request:<request>,response:<response>- request: The name of the method argument whose value is 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 is mapped to the gRPC response.
- Optional: When omitted, a struct containing all the results (except error) will automatically be mapped to the gRPC response.
- request: The name of the method argument whose value is mapped to the gRPC request.
- Example:
-
Omitted:
type Service interface { // @kok(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 { // @kok(grpc): request:user CreateUser(ctx context.Context, user User) (err error) } // gRPC request: // $ grpcurl -d '{"name": "tracey", "age": 1}' ... pb.Service/CreateUser
-
Checkout the Godoc.