Gozz
Golang Annotation Analyzer and Template-Programing Kits
Fast and Simple
Intuitive annotation syntax, clean and fast command line tools, runtime-dependencies-free generated codes.
Awesome Plugins
Autowire DI, AOP interface proxy, auto-sync implement from interface, ORM, API router mapping and so on.
High Extensibility
Customize generating templates, core-library for code analysis, edit and generate. external .so plugins supported.
# Overview
# Example-01
There is a common service interface and entity definition file. Example Project (opens new window)
package overview01
import (
"context"
"time"
)
// Book manager service
type BookService interface {
// Get book entity by id
Get(ctx context.Context, query QueryId) (book Book, err error)
// Get book entities list by query params
List(ctx context.Context, query QueryBook) (list ListBook, err error)
// Create book entity with form
Create(ctx context.Context, form FormBook) (book Book, err error)
// Edit book entity from form
Edit(ctx context.Context, form FormBook) (book Book, err error)
// Delete book entity by id
Delete(ctx context.Context, query QueryId) (err error)
}
type (
// Uri path id param
QueryId struct {
Id int
}
// Query book entities param
QueryBook struct {
// Query pagination offser no
PageNo int
// Query pagination size
PageSize int
}
// Form to edit or create book entity
FormBook struct {
QueryId
// Title of book entity
Title string
// Type of book entity
Type string
}
// Book entity
Book struct {
FormBook
// Book entity create time
CreatedAt time.Time
// Book entity create user
CreatedBy string
// Book entity last update time
UpdatedAt time.Time
// Book entity last update user
UpdatedBy string
}
// Book entities list
ListBook struct {
// Query pagination total count in database
Total int
// Entities list data
List []Book
}
)
Add some annotations in code's comments, the syntax be like +zz:plugin_name:plugin_args
.
package overview01
import (
"context"
"time"
)
//go:generate gozz run -p "doc" -p "api:prefix=book:foo=bar" -p "impl" -p "tag" ./
// +zz:doc
// +zz:api:./
// +zz:impl:./types.go:type=Implement
// Book manager service
type BookService interface {
// Get book entity by id
// +zz:api:get|{id}
Get(ctx context.Context, query QueryId) (book Book, err error)
// Get book entities list by query params
// +zz:api:get
List(ctx context.Context, query QueryBook) (list ListBook, err error)
// Create book entity with form
// +zz:api:post
Create(ctx context.Context, form FormBook) (book Book, err error)
// Edit book entity from form
// +zz:api:put|{id}
Edit(ctx context.Context, form FormBook) (book Book, err error)
// Delete book entity by id
// +zz:api:delete|{id}
Delete(ctx context.Context, query QueryId) (err error)
}
// +zz:doc
// +zz:tag:json:{{ snake .FieldName }}
type (
// Uri path id param
// +zz:tag:uri:{{ snake .FieldName }}
QueryId struct {
Id int
}
// Query book entities param
// +zz:tag:query:{{ snake .FieldName }}
QueryBook struct {
// Query pagination offser no
PageNo int
// Query pagination size
PageSize int
}
// Form to edit or create book entity
FormBook struct {
QueryId
// Title of book entity
Title string
// Type of book entity
Type string
}
// Book entity
Book struct {
FormBook
// Book entity create time
CreatedAt time.Time
// Book entity create user
CreatedBy string
// Book entity last update time
UpdatedAt time.Time
// Book entity last update user
UpdatedBy string
}
// Book entities list
ListBook struct {
// Query pagination total count in database
Total int
// Entities list data
List []Book
}
)
Execute gozz run -p "doc" -p "api:prefix=book" -p "impl" -p "tag" ./
,
meaning that to analysis these plugin's name annotations and allow them to do something.
File would be updated as follows:
package overview01
import (
"context"
"time"
)
//go:generate gozz run -p "doc" -p "api:prefix=book" -p "impl" -p "tag" ./
// +zz:doc
// +zz:api:./
// +zz:impl:./types.go:type=Implement
// Book manager service
type BookService interface {
// Get book entity by id
// +zz:api:get|{id}
Get(ctx context.Context, query QueryId) (book Book, err error)
// Get book entities list by query params
// +zz:api:get
List(ctx context.Context, query QueryBook) (list ListBook, err error)
// Create book entity with form
// +zz:api:post
Create(ctx context.Context, form FormBook) (book Book, err error)
// Edit book entity from form
// +zz:api:put|{id}
Edit(ctx context.Context, form FormBook) (book Book, err error)
// Delete book entity by id
// +zz:api:delete|{id}
Delete(ctx context.Context, query QueryId) (err error)
}
// +zz:doc
// +zz:tag:json:{{ snake .FieldName }}
type (
// Uri path id param
// +zz:tag:uri:{{ snake .FieldName }}
QueryId struct {
// Entity Id
Id int `json:"id" uri:"id"`
}
// Query book entities param
// +zz:tag:query:{{ snake .FieldName }}
QueryBook struct {
// Query pagination offser no
PageNo int `json:"page_no" query:"page_no"`
// Query pagination size
PageSize int `json:"page_size" query:"page_size"`
}
// Form to edit or create book entity
FormBook struct {
QueryId
// Title of book entity
Title string `json:"title"`
// Type of book entity
Type string `json:"type"`
}
// Book entity
Book struct {
FormBook
// Book entity create time
CreatedAt time.Time `json:"created_at"`
// Book entity create user
CreatedBy string `json:"created_by"`
// Book entity last update time
UpdatedAt time.Time `json:"updated_at"`
// Book entity last update user
UpdatedBy string `json:"updated_by"`
}
// Book entities list
ListBook struct {
// Query pagination total count in database
Total int `json:"total"`
// Entities list data
List []Book `json:"list"`
}
)
var (
_ BookService = (*Implement)(nil)
)
type Implement struct{}
func (implement Implement) Get(ctx context.Context, query QueryId) (book Book, err error) {
panic("not implemented")
}
func (implement Implement) List(ctx context.Context, query QueryBook) (list ListBook, err error) {
panic("not implemented")
}
func (implement Implement) Create(ctx context.Context, form FormBook) (book Book, err error) {
panic("not implemented")
}
func (implement Implement) Edit(ctx context.Context, form FormBook) (book Book, err error) {
panic("not implemented")
}
func (implement Implement) Delete(ctx context.Context, query QueryId) (err error) {
panic("not implemented")
}
There is also a zzgen.api.go
file generated to provide API router mapping and invoke.
// Code generated by gozz:api github.com/go-zing/gozz. DO NOT EDIT.
package overview01
import (
"context"
)
var _ = context.Context(nil)
type Apis struct {
BookService BookService
}
func (s Apis) Iterate(fn func(interface{}, []map[string]interface{})) {
for _, f := range []func() (interface{}, []map[string]interface{}){
s._BookService,
} {
fn(f())
}
}
func (s Apis) _BookService() (interface{}, []map[string]interface{}) {
t := s.BookService
return &t, []map[string]interface{}{
{
"name": "Get",
"resource": "get|{id}",
"options": map[string]string{
"prefix": "book",
},
"invoke": func(ctx context.Context, dec func(interface{}) error) (interface{}, error) {
var in QueryId
if err := dec(&in); err != nil {
return nil, err
}
return t.Get(ctx, in)
},
},
{
"name": "List",
"resource": "get",
"options": map[string]string{
"prefix": "book",
},
"invoke": func(ctx context.Context, dec func(interface{}) error) (interface{}, error) {
var in QueryBook
if err := dec(&in); err != nil {
return nil, err
}
return t.List(ctx, in)
},
},
{
"name": "Create",
"resource": "post",
"options": map[string]string{
"prefix": "book",
},
"invoke": func(ctx context.Context, dec func(interface{}) error) (interface{}, error) {
var in FormBook
if err := dec(&in); err != nil {
return nil, err
}
return t.Create(ctx, in)
},
},
{
"name": "Edit",
"resource": "put|{id}",
"options": map[string]string{
"prefix": "book",
},
"invoke": func(ctx context.Context, dec func(interface{}) error) (interface{}, error) {
var in FormBook
if err := dec(&in); err != nil {
return nil, err
}
return t.Edit(ctx, in)
},
},
{
"name": "Delete",
"resource": "delete|{id}",
"options": map[string]string{
"prefix": "book",
},
"invoke": func(ctx context.Context, dec func(interface{}) error) (interface{}, error) {
var in QueryId
if err := dec(&in); err != nil {
return nil, err
}
return nil, t.Delete(ctx, in)
},
},
}
}
Another generated file zzgen.doc.go
could be used for getting comments in runtime.
// Code generated by gozz:doc github.com/go-zing/gozz. DO NOT EDIT.
package overview01
var (
ZZ_types_doc = map[interface{}]map[string]string{
(*BookService)(nil): _doc_BookService,
(*QueryId)(nil): _doc_QueryId,
(*QueryBook)(nil): _doc_QueryBook,
(*FormBook)(nil): _doc_FormBook,
(*Book)(nil): _doc_Book,
(*ListBook)(nil): _doc_ListBook,
}
_doc_BookService = map[string]string{
"": "Book manager service",
"Get": "Get book entity by id",
"List": "Get book entities list by query params",
"Create": "Create book entity with form",
"Edit": "Edit book entity from form",
"Delete": "Delete book entity by id",
}
_doc_QueryId = map[string]string{
"": "Uri path id param",
"Id": "Entity Id",
}
_doc_QueryBook = map[string]string{
"": "Query book entities param",
"PageNo": "Query pagination offser no",
"PageSize": "Query pagination size",
}
_doc_FormBook = map[string]string{
"": "Form to edit or create book entity",
"Title": "Title of book entity",
"Type": "Type of book entity",
}
_doc_Book = map[string]string{
"": "Book entity",
"CreatedAt": "Book entity create time",
"CreatedBy": "Book entity create user",
"UpdatedAt": "Book entity last update time",
"UpdatedBy": "Book entity last update user",
}
_doc_ListBook = map[string]string{
"": "Book entities list",
"Total": "Query pagination total count in database",
"List": "Entities list data",
}
)
TIP
There are also templates generated in directory, modify them to do any customized.
.
├── go.mod
├── types.go
├── zzgen.api.go
├── zzgen.api.go.tmpl
├── zzgen.doc.go
└── zzgen.doc.go.tmpl
And then before we do api routes setup, wo could also use zzgen.api.go
and zzgen.doc.go
to generate OpenApi
.
Look at the example below:
# Example-02
This example display a common web project construct, different type of layers, configs and interfaces definition.
Example project (opens new window)
package overview02
import (
"context"
"database/sql"
"fmt"
"net/http"
"time"
"github.com/go-redis/redis/v8"
)
type (
// root config for unmarshal config file
Config struct {
Server ServerConfig `yaml:"server"`
Sql SqlConfig `yaml:"sql"`
Redis RedisConfig `yaml:"redis"`
}
// http server config
ServerConfig struct {
Addr string `yaml:"addr"`
}
// sql config
SqlConfig struct {
Dsn string `yaml:"dsn"`
}
// redis config
RedisConfig struct {
Host string `yaml:"host"`
Port string `yaml:"port"`
}
)
// provide http server from server config
func ProvideHttpServer(config ServerConfig) *http.Server {
return &http.Server{
Addr: config.Addr,
}
}
// interface of sql connection
type SqlConn interface {
QueryContext(ctx context.Context, statement string, args ...interface{}) (rows *sql.Rows, err error)
}
// interface of key value store
type Store interface {
Get(ctx context.Context, key string) (value []byte, err error)
Set(ctx context.Context, key string, value []byte, exp time.Duration) (err error)
}
// provide sql connection from sql config
func ProvideSql(config SqlConfig) (*sql.DB, error) {
return sql.Open("mysql", config.Dsn)
}
// provide kv store from redis config
func ProvideRedisStore(config RedisConfig) (*redis.Client, error) {
rdb := redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%s:%s", config.Host, config.Port),
})
return rdb, nil
}
type RedisStore struct {
redis.Cmdable
}
func (s RedisStore) Set(ctx context.Context, key string, value []byte, exp time.Duration) (err error) {
return s.Cmdable.Set(ctx, key, value, exp).Err()
}
func (s RedisStore) Get(ctx context.Context, key string) (value []byte, err error) {
return s.Cmdable.Get(ctx, key).Bytes()
}
// biz service handler
type ServiceHandler interface {
GetInt(ctx context.Context) (int, error)
GetString(ctx context.Context) (string, error)
}
// implement of server handler
type ServerHandlerImpl struct {
Sql SqlConn
Store Store
}
func (impl *ServerHandlerImpl) GetInt(ctx context.Context) (int, error) {
panic("not implemented")
}
func (impl *ServerHandlerImpl) GetString(ctx context.Context) (string, error) {
panic("not implemented")
}
// the entry of application
type Application interface {
Run()
}
// web application implement
type application struct {
Server *http.Server
Handler ServiceHandler
}
func (application application) Run() {
panic("not implemented")
}
Add some annotations to describe what were used to wired and how they should be bind.
package overview02
import (
"context"
"database/sql"
"fmt"
"net/http"
"time"
"github.com/go-redis/redis/v8"
)
//go:generate gozz run -p "wire" ./
//go:generate sh -c "go run ./cmd/stgragh | dot -Tpng -o structure.png"
//go:generate sh -c "go run ./cmd/stgragh | dot -Tsvg -o structure.svg"
type (
// root config for unmarshal config file
// +zz:wire:field=*
Config struct {
Server ServerConfig `yaml:"server"`
Sql SqlConfig `yaml:"sql"`
Redis RedisConfig `yaml:"redis"`
}
// http server config
ServerConfig struct {
Addr string `yaml:"addr"`
}
// sql config
SqlConfig struct {
Dsn string `yaml:"dsn"`
}
// redis config
RedisConfig struct {
Host string `yaml:"host"`
Port string `yaml:"port"`
}
)
// provide http server from server config
// +zz:wire
func ProvideHttpServer(config ServerConfig) *http.Server {
return &http.Server{
Addr: config.Addr,
}
}
// interface of sql connection
type SqlConn interface {
QueryContext(ctx context.Context, statement string, args ...interface{}) (rows *sql.Rows, err error)
}
// interface of key value store
type Store interface {
Get(ctx context.Context, key string) (value []byte, err error)
Set(ctx context.Context, key string, value []byte, exp time.Duration) (err error)
}
// provide sql connection from sql config
// +zz:wire:bind=SqlConn
func ProvideSql(config SqlConfig) (*sql.DB, error) {
return sql.Open("mysql", config.Dsn)
}
// provide kv store from redis config
// +zz:wire:bind=redis.Cmdable
func ProvideRedisStore(config RedisConfig) (*redis.Client, error) {
rdb := redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%s:%s", config.Host, config.Port),
})
return rdb, nil
}
// +zz:wire:bind=Store
type RedisStore struct {
redis.Cmdable
}
func (s RedisStore) Set(ctx context.Context, key string, value []byte, exp time.Duration) (err error) {
return s.Cmdable.Set(ctx, key, value, exp).Err()
}
func (s RedisStore) Get(ctx context.Context, key string) (value []byte, err error) {
return s.Cmdable.Get(ctx, key).Bytes()
}
// biz service handler
type ServiceHandler interface {
GetInt(ctx context.Context) (int, error)
GetString(ctx context.Context) (string, error)
}
// implement of server handler
// +zz:wire:bind=ServiceHandler:aop
type ServerHandlerImpl struct {
Sql SqlConn
Store Store
}
func (impl *ServerHandlerImpl) GetInt(ctx context.Context) (int, error) {
panic("not implemented")
}
func (impl *ServerHandlerImpl) GetString(ctx context.Context) (string, error) {
panic("not implemented")
}
// the entry of application
// +zz:wire:inject=./:param=*Config
type Application interface {
Run()
}
// web application implement
// +zz:wire:bind=Application
type application struct {
Server *http.Server
Handler ServiceHandler
}
func (application application) Run() {
panic("not implemented")
}
The complete injector constructor wire_gen.go
were generated.
// Code generated by Wire. DO NOT EDIT.
//go:generate go run github.com/google/wire/cmd/wire
//+build !wireinject
package overview02
// Injectors from wire_zinject.go:
// github.com/go-zing/gozz-doc-examples/overview02.Application
func Initialize_Application(config *Config) (Application, func(), error) {
serverConfig := config.Server
server := ProvideHttpServer(serverConfig)
sqlConfig := config.Sql
db, err := ProvideSql(sqlConfig)
if err != nil {
return nil, nil, err
}
redisConfig := config.Redis
client, err := ProvideRedisStore(redisConfig)
if err != nil {
return nil, nil, err
}
redisStore := &RedisStore{
Cmdable: client,
}
serverHandlerImpl := &ServerHandlerImpl{
Sql: db,
Store: redisStore,
}
overview02_impl_aop_ServiceHandler := &_impl_aop_ServiceHandler{
_aop_ServiceHandler: serverHandlerImpl,
}
overview02Application := &application{
Server: server,
Handler: overview02_impl_aop_ServiceHandler,
}
return overview02Application, func() {
}, nil
}
And wire_zzaop.go
generates static type-safe AOP proxy for interface ServiceHandler
.
// Code generated by gozz:wire github.com/go-zing/gozz. DO NOT EDIT.
package overview02
import (
"context"
)
type _aop_interceptor interface {
Intercept(v interface{}, name string, params, results []interface{}) (func(), bool)
}
// ServiceHandler
type (
_aop_ServiceHandler ServiceHandler
_impl_aop_ServiceHandler struct{ _aop_ServiceHandler }
)
func (i _impl_aop_ServiceHandler) GetInt(p0 context.Context) (r0 int, r1 error) {
if t, x := i._aop_ServiceHandler.(_aop_interceptor); x {
if up, ok := t.Intercept(i._aop_ServiceHandler, "GetInt",
[]interface{}{&p0},
[]interface{}{&r0, &r1},
); up != nil {
defer up()
} else if !ok {
return
}
}
return i._aop_ServiceHandler.GetInt(p0)
}
func (i _impl_aop_ServiceHandler) GetString(p0 context.Context) (r0 string, r1 error) {
if t, x := i._aop_ServiceHandler.(_aop_interceptor); x {
if up, ok := t.Intercept(i._aop_ServiceHandler, "GetString",
[]interface{}{&p0},
[]interface{}{&r0, &r1},
); up != nil {
defer up()
} else if !ok {
return
}
}
return i._aop_ServiceHandler.GetString(p0)
}
Use gozz-kit (opens new window) tool ztree
to do runtime analysis for initialized object,
we could generate a structure graph: