hero

Gozz

Golang Annotation Analyzer and Template-Programing Kits

Get Started →

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:

# Just Click Me to Start Explore →