Glacier is a Go language, support dependency injection, modular application development framework, it uses go-ioc The dependency injection container core solves the problems of dependency transfer and modularization for Go application development.
characteristic
- dependency injection: Manage object dependencies through dependency injection, support creation of singleton and prototype objects
- Modular: Through the Provider feature, it is easy to realize the modularization of the application
- Built-in web development support: Glacier has built-in support for web application development and provides a feature-rich API to simplify web development
use
Create a new project and install the Glacier development framework using the following command
go get github.com/mylxsw/glacier
In order to simplify the application creation process, we can generally create applications through starter templates
import "github.com/mylxsw/glacier/starter/app" ... // 方法一:快捷启动应用 app.MustStart("1.0", 3, func(app *app.App) error { // 这里完成应用的初始化 // ... return nil }) // 方法二: 分步骤启动应用 ins := app.Create("1.0", 3) // 应用初始化 // ... app.MustRun(ins)
Example:
app.MustStart("1.0", 3, func(ins *app.App) error { ins.AddStringFlag("listen", ":8080", "http listen address") ins.Provider(web.Provider( listener.FlagContext("listen"), web.SetRouteHandlerOption(func(cc infra.Resolver, router web.Router, mw web.RequestMiddleware) { router.Get("/", func(ctx web.Context) web.Response { return ctx.JSON(web.M{}) }) }), )) return nil })
Core idea
dependency injection
The Glacier framework takes full advantage of the go-ioc The provided dependency injection capability provides powerful dependency injection features for applications.
When using the dependency injection feature, you must first understand the role of the following two interfaces
infra.Binder
This interface is used for the binding of object creation instance method, simply put togo-ioc
How to create a container registration objectinfra.Resolver
This interface is used to instantiate objects and get object instances
Whether it is Binder
still Resolver
there will be a interface{}
Type parameter, its type is a function that conforms to certain rules, followed by Binder
and Resolver
section will be explained in detail.
Binder
infra.Binder
It is an object definition interface, which is used to bind the creation method of the instance to the dependency injection container, and provides the following common methods
Prototype(initialize interface{}) error
Prototype binding, each access to the bound instance will be based oninitialize
function to create a new instanceSingleton(initialize interface{}) error
Singleton binding, the bound instance is the same every time you access it, and only the initial instance will be created when you access it for the first timeBindValue(key string, value interface{}) error
Bind a concrete value tokey
Prototype
and Singleton
method parameters initialize interface{}
The following two forms are supported
-
Form 1:
func(依赖参数列表...) (绑定类型定义, error)
// 这里使用单例方法定义了数据库连接对象的创建方法 binder.Singleton(func(conf *Config) (*sql.DB, error) { return sql.Open("mysql", conf.MySQLURI) }) binder.Singleton(func(c infra.FlagContext) *Config { ... return &Config{ Listen: c.String("listen"), MySQLURI: c.String("mysql_uri"), APIToken: c.String("api_token"), ... } })
-
Form 2:
func(注入参数列表...) 绑定类型定义
binder.Singleton(func() UserRepo { return &userRepoImpl{} }) binder.Singleton(func(db *sql.DB) UserRepo { // 这里我们创建的 userRepoImpl 对象,依赖 sql.DB 对象,只需要在函数 // 参数中,将依赖列举出来,容器会自动完成这些对象的创建 return &userRepoImpl{db: db} })
Resolver
infra.Resolver
It is an object instantiation interface, which obtains instances through dependency injection, and provides the following common methods
Resolve(callback interface{}) error
Execute the callback function and automatically provide the required parameters for the callback functionCall(callback interface{}) ([]interface{}, error)
Execute the callback function, automatically provide the required parameters for the callback function, support the return value, and the return parameter isCall
The first array parameter ofAutoWire(object interface{}) error
Automatically perform dependency injection on the structure object, and object must be a pointer to the structure object.Auto-injected fields (both public and private are supported) need to be addedautowire
tag, supports the following two- autowire: “@” is injected according to the type of the field
- autowire: “custom key” is injected according to a custom key (look for a binding named key)
Get(key interface{}) (interface{}, error)
Find the corresponding object instance directly through the key
// Resolve resolver.Resolve(func(db *sql.DB) {...}) err := resolver.Resolve(func(db *sql.DB) error {...}) // Call resolver.Call(func(userRepo UserRepo) {...}) // Call 带有返回值 // 这里的 err 是依赖注入过程中的错误,比如依赖对象创建失败 // results 是一个类型为 []interface{} 的数组,数组中按次序包含了 callback 函数的返回值,以下面的代码为例,其中 // results[0] - string // results[1] - error results, err := resolver.Call(func(userRepo UserRepo) (string, error) {...}) // 由于每个返回值都是 interface{} 类型,因此在使用时需要执行类型断言,将其转换为具体的类型再使用 returnValue := results[0].(string) returnErr := results[1].(error) // AutoWire // 假设我们有一个 UserRepo,创建该结构体时需要数据库的连接实例 type UserRepo struct { db *sql.DB `autowire:"@"` } userRepo := UserRepo{} resolver.AutoWire(&userRepo) // 现在 userRepo 中的 db 参数已经自动被设置为了数据库连接对象,可以继续执行后续的操作了
Provider
In the Glacier application development framework, Provider is the core of application modularization. Each independent functional module completes instance initialization through Provider, and each Provider needs to implement infra.Provider
interface.In each functional module, we usually create a file called provider.go, and create a provider implementation in this file
type Provider struct{} func (Provider) Register(binder infra.Binder) { ... // 这里可以使用 binder 向 IOC 容器注册当前模块中的实例创建方法 }
Provider An interface has only one method that must be implemented Register(binder infra.Binder)
this method is used to register the object of the current module into the IOC container to support dependency injection.
For example, we implement a database-based user management module repo
the module contains two methods
package repo type UserRepo struct { db *sql.DB } func (repo *UserRepo) Login(username, password string) (*User, error) {...} func (repo *UserRepo) GetUser(username string) (*User, error) {...}
In order for this module to work properly, we need to create the UserRepo
when, provide db
parameter, in Glacier, we can implement it like this
package repo type Provider struct {} func (Provider) Register(binder infra.Binder) { binder.Singleton(func(db *sql.DB) *UserRepo { return &UserRepo {db: db} }) }
When our application is created, use ins.Provider
method to register the module
ins := app.Default("1.0") ... ins.MustSingleton(func() (*sql.DB, error) { return sql.Open("mysql", "user:pwd@tcp(ip:3306)/dbname") }) // 在这里加载模块的 Provider ins.Provider(repo.Provider{}) ... app.MustRun(ins)
ProviderBoot
When we use Provider, only one interface method needs to be implemented by default Register(binder infra.Binder)
That is, this method is used to register the instance creation method of the module in the IOC container of the Glacier framework.
In Glaicer, there is also a ProviderBoot
interface, which contains a Boot(resolver Resolver)
method, the module that implements this method can execute some business logic of the module during the startup process of the Glacier framework. This method is executed after all modules are loaded (all modules’ Register
methods have been executed), therefore, all objects in the system are available.
Boot(resolver Resolver)
The method is suitable for performing some one-time tasks that must be completed during the application startup process, and the tasks should be completed as soon as possible to avoid affecting the application startup.
type Provider struct{} func (Provider) Register(binder infra.Binder) { binder.MustSingleton(func(conf *configs.Config) *grpc.Server { return ... }) } func (Provider) Boot(resolver infra.Resolver) { resolver.MustResolve(func(serv *grpc.Server) { protocol.RegisterMessageServer(serv, NewEventService()) protocol.RegisterHeartbeatServer(serv, NewHeartbeatService()) }) }
DaemonProvider
Module Provider’s Boot
The method is blocking execution and is usually used to perform some initialization tasks that need to be performed when the application starts. In an application, all Provider’s Boot
Methods are executed serially.
and DaemonProvider
The interface provides the module with the ability to execute asynchronously, and the module’s Daemon(ctx context.Context, resolver infra.Resolver)
The method is executed asynchronously, where we can perform operations such as creating a web server.
func (Provider) Daemon(_ context.Context, app infra.Resolver) { app.MustResolve(func( serv *grpc.Server, conf *configs.Config, gf graceful.Graceful, ) { listener, err := net.Listen("tcp", conf.GRPCListen) ... gf.AddShutdownHandler(serv.GracefulStop) ... if err := serv.Serve(listener); err != nil { log.Errorf("GRPC Server has been stopped: %v", err) } }) }
ProviderAggregate
The ProviderAggregate interface provides an application with the ability to aggregate Providers from other modules. Aggregate() []Provider
In the method, we can define multiple other modules that our current module depends on. During the startup process of the Glacier framework, the dependent modules defined here will be loaded first, and then our current module will be loaded.
we can pass ProviderAggregate
to create our own module, Aggregates() []infra.Provider
The dependent submodule is returned in the method, and the framework will initialize the submodule first, and then initialize the current module.
// 创建自定义模块,初始化了 Glacier 框架内置的 Web 框架 type Provider struct{} func (Provider) Aggregates() []infra.Provider { return []infra.Provider{ // 加载了 web 模块,为应用提供 web 开发支持 web.Provider( listener.FlagContext("listen"), // 从命令行参数 listen 获取监听端口 web.SetRouteHandlerOption(s.routes), // 设置路由规则 web.SetExceptionHandlerOption(func(ctx web.Context, err interface{}) web.Response { log.Errorf("error: %v, call stack: %s", err, debug.Stack()) return nil }), // Web 异常处理 ), } } func (Provider) routes(cc infra.Resolver, router web.Router, mw web.RequestMiddleware) { router.Controllers( "/api", // 这里添加控制器 controller.NewWelcomeController(cc), controller.NewUserController(cc), ) } func (Provider) Register(app infra.Binder) {}
service
In the Glacier framework, Service represents a background module, and Service will continue to run in the framework life cycle.To implement a Service, you need to implement infra.Service
interface, which contains only one method
Start() error
Used to start the Service
Apart from Start
method, the following control methods are also supported, but they are all optional
Init(resolver Resolver) error
Used for Service initialization, injecting dependencies, etc.Stop()
Trigger the shutdown of the ServiceReload()
Trigger a reload of the Service
The following is an example
type DemoService struct { resolver infra.Resolver stopped chan interface{} } // Init 可选方法,用于在 Service 启动之前初始化一些参数 func (s *DemoService) Init(resolver infra.Resolver) error { s.resolver = resolver s.stopped = make(chan interface{}) return nil } // Start 用于 Service 的启动 func (s *DemoService) Start() error { for { select { case <-s.stopped: return nil default: ... // 业务代码 } } } // Stop 和 Reload 都是可选方法 func (s *DemoService) Stop() { s.stopped <- struct{}{} } func (s *DemoService) Reload() { ... }
When our application is created, use app.Service
Method to register Service
ins := app.Create("1.0") ... ins.Service(&service.DemoService{}) ... app.MustRun(ins)
ModuleLoadPolicy
Provider and service Supports on-demand loading, to use this feature, just let the Provider and service accomplish ShouldLoad(…) bool method.ShouldLoad
method for controlling Provider and service Whether to load, support the following forms
func (Provider) ShouldLoad(...依赖) bool
func (Provider) ShouldLoad(...依赖) (bool, error)
example
type Provider struct{} func (Provider) Register(binder infra.Binder) {...} // 只有当 config.AuthType == ldap 的时候才会加载当前 Provider func (Provider) ShouldLoad(config *config.Config) bool { return str.InIgnoreCase(config.AuthType, []string{"ldap"}) }
Notice:
ShouldLoad
When the method is executed,Provider
not finishedRegister
method execution, therefore, inShouldLoad
In the parameter list of the method, only the object instance that is injected globally when the application is created can be used.
ins := app.Create("1.0") ... ins.Singleton(func(c infra.FlagContext) *config.Config { return ... }) ... app.MustRun(ins)
priority
accomplish infra.Priority
The **Provider ** and **Service ** of the interface will follow the Priority()
The return value of the method is loaded sequentially. The larger the value, the lower the loading order. The default priority is 1000
.
type Provider struct {} func (Provider) Register(binder infra.Binder) {...} func (Provider) Priority() int { return 10 }
web framework
Glacier is an application framework. In order to facilitate Web development, it also has a built-in flexible Web application development framework.
Usage
Glaicer Web is a built in Glacier framework DaemonProvider, no different from other modules.we pass web.Provider(builder infra.ListenerBuilder, options ...Option) infra.DaemonProvider
method to create a web module.
parameter builder
Used to create a listener for web services (used to tell the web framework how to listen to the port). In Glaicer, there are several ways to create a listener:
listener.Default(listenAddr string) infra.ListenerBuilder
The builder uses a fixed listenAddr to create the listenerlistener.FlagContext(flagName string) infra.ListenerBuilder
The builder obtains the address to listen to according to the command line option flagName to create a listenerlistener.Exist(listener net.Listener) infra.ListenerBuilder
This builder uses the listener that should exist to create
parameter options
Used to configure the behavior of web services, including the following common configurations
web.SetRouteHandlerOption(h RouteHandler) Option
Set the route registration function, register the API routing rules in this functionweb.SetExceptionHandlerOption(h ExceptionHandler) Option
Set request exception handlerweb.SetIgnoreLastSlashOption(ignore bool) Option
Set routing rules to ignore the last/
is not ignored by defaultweb.SetMuxRouteHandlerOption(h MuxRouteHandler) Option
Sets the underlying gorilla Mux object for direct control of the underlying Gorilla frameworkweb.SetHttpWriteTimeoutOption(t time.Duration) Option
Set HTTP write timeoutweb.SetHttpReadTimeoutOption(t time.Duration) Option
Set HTTP read timeoutweb.SetHttpIdleTimeoutOption(t time.Duration) Option
Set HTTP idle timeoutweb.SetMultipartFormMaxMemoryOption(max int64)
Sets the maximum memory that form parsing can useweb.SetTempFileOption(tempDir, tempFilePattern string) Option
Set temporary file storage rulesweb.SetInitHandlerOption(h InitHandler) Option
In the initialization phase, the web application object has not been created, and the web configuration can be updated hereweb.SetListenerHandlerOption(h ListenerHandler) Option
In the service initialization phase, the web service object has been created, and the web configuration cannot be updated at this time
The easiest way to use the Web module is to create a Provider directly,
// Password 该结构体时 /complex 接口的返回值定义 type Password struct { Password string `json:"password"` } // Glacier 框架初始化 ins := app.Default("1.0") ... // 添加命令行参数 listen,指定默认监听端口 :8080 ins.AddStringFlag("listen", ":8080", "http listen address") ... ins.Provider(web.Provider( // 使用命令行 flag 的 listener builder listener.FlagContext("listen"), // 设置路由规则 web.SetRouteHandlerOption(func(resolver infra.Resolver, r web.Router, mw web.RequestMiddleware) { ... r.Get("/simple", func(ctx web.Context, gen *password.Generator) web.Response { ... return ctx.JSON(web.M{"password": pass}) }) r.Get("/complex", func(ctx web.Context, gen *password.Generator) Password {...}) }), )) app.MustRun(ins)
A better way is to use modularity and write an independent Provider
type Provider struct{} // Aggregates 实现 infra.ProviderAggregate 接口 func (Provider) Aggregates() []infra.Provider { return []infra.Provider{ web.Provider( confListenerBuilder{}, web.SetRouteHandlerOption(routes), web.SetMuxRouteHandlerOption(muxRoutes), web.SetExceptionHandlerOption(exceptionHandler), ), } } // Register 实现 infra.Provider 接口 func (Provider) Register(binder infra.Binder) {} // exceptionHandler 异常处理器 func exceptionHandler(ctx web.Context, err interface{}) web.Response { return ctx.JSONWithCode(web.M{"error": fmt.Sprintf("%v", err)}, http.StatusInternalServerError) } // routes 注册路由规则 func routes(resolver infra.Resolver, router web.Router, mw web.RequestMiddleware) { mws := make([]web.HandlerDecorator, 0) // 添加 web 中间件 mws = append(mws, mw.AccessLog(log.Module("api")), mw.CORS("*"), ) // 注册控制器,所有的控制器 API 都以 `/api` 作为接口前缀 router.WithMiddleware(mws...).Controllers( "/api", controller.NewServerController(resolver), controller.NewClientController(resolver), ) } func muxRoutes(resolver infra.Resolver, router *mux.Router) { resolver.MustResolve(func() { // 添加 prometheus metrics 支持 router.PathPrefix("/metrics").Handler(promhttp.Handler()) // 添加健康检查接口支持 router.PathPrefix("/health").Handler(HealthCheck{}) }) } // 创建自定义的 listener 构建器,从配置对象中读取 listen 地址 type confListenerBuilder struct{} func (l confListenerBuilder) Build(resolver infra.Resolver) (net.Listener, error) { return listener.Default(resolver.MustGet((*config.Server)(nil)).(*config.Server).HTTPListen).Build(resolver) }
controller
The controller must implement web.Controller
interface, which has only one method
Register(router Router)
Routing rules for registering the current controller
type UserController struct {...} // NewUserController 控制器创建方法,返回 web.Controller 接口 func NewUserController() web.Controller { return &UserController{...} } // Register 注册当前控制器关联的路由规则 func (ctl UserController) Register(router web.Router) { router.Group("/users/", func(router web.Router) { router.Get("/", u.Users).Name("users:all") router.Post("/", u.Add) router.Post("/{id}/", u.Update) router.Get("/{id}/", u.User).Name("users:one") router.Delete("/{id}/", u.Delete).Name("users:delete") }) router.Group("/users-helper/", func(router web.Router) { router.Get("/names/", u.UserNames) }) } // 读取 JSON 请求参数,直接返回实例,会以 json 的形式返回给客户端 func (ctl UserController) Add(ctx web.Context, userRepo repository.UserRepo) (*repository.User, error) { var userForm *UserForm if err := ctx.Unmarshal(&userForm); err != nil { return nil, web.WrapJSONError(fmt.Errorf("invalid request: %v", err), http.StatusUnprocessableEntity) } ctx.Validate(userForm, true) ... return ... } // 直接返回错误,如果 error 不为空,则返回错误给客户端 func (ctl UserController) Delete(ctx web.Context, userRepo repository.UserRepo) error { userID := ctx.PathVar("id") ... return userRepo.DeleteID(userID) } // 返回 web.Response,可以使用多种格式返回,如 ctx.Nil, ctx.API, ctx.JSON, ctx.JSONWithCode, ctx.JSONError, ctx.YAML, ctx.Raw, ctx.HTML, ctx.HTMLWithCode, ctx.Error 等 func (u UserController) Users(ctx web.Context, userRepo repository.UserRepo, roleRepo repository.RoleRepo) web.Response { page := ctx.IntInput("page", 1) perPage := ctx.IntInput("per_page", 10) ... return ctx.JSON(web.M{ "users": users, "next": next, "search": web.M{ "name": name, "phone": phone, "email": email, }, }) }
use web.Router
example of Controllers
method to register the controller.
// routes 注册路由规则 func routes(resolver infra.Resolver, router web.Router, mw web.RequestMiddleware) { mws := make([]web.HandlerDecorator, 0) // 添加 web 中间件 mws = append(mws, mw.AccessLog(log.Module("api")), mw.CORS("*"), ) // 注册控制器,所有的控制器 API 都以 `/api` 作为接口前缀 router.WithMiddleware(mws...).Controllers( "/api", controller.NewUserController(), ) }
event management
The Glacier framework provides a simple event management module, which can be used to publish and monitor the events in the running of the application, and perform corresponding business processing.
pass event.Provider(handler func(resolver infra.Resolver, listener Listener), options ...Option) infra.Provider
to initialize the event manager.
ins.Provider(event.Provider( func(cc infra.Resolver, listener event.Listener) { listener.Listen(func(event CronEvent) { log.Debug("a new cron task executed") // 执行监听到定时任务执行事件后要触发的操作 }) }, // 设置事件管理器选项 event.SetStoreOption(func(cc infra.Resolver) event.Store { // 设置使用默认的内存事件存储 return event.NewMemoryEventStore(true, 100) }), ))
When publishing events, use the dependency injection capability of the Glacier framework to obtain event.Publisher
interface implementation
ins.Async(func(publisher event.Publisher) { for i := 0; i < 10; i++ { publisher.Publish(CronEvent{GoroutineID: uint64(i)}) } })
local memory as event storage backend
Glacier has a built-in memory-based event storage backend, which means that all event listeners are executed synchronously.
// 设置事件管理器选项 event.SetStoreOption(func(cc infra.Resolver) event.Store { // 设置使用默认的内存事件存储 return event.NewMemoryEventStore(true, 100) })
Redis as an event storage backend
When using memory as the event storage backend, when the application exits abnormally, events may be lost. You can use this Redis-based event storage backend redis-event-store To get persistence support for events.
timed task
Glacier provides built-in support for scheduled tasks, use scheduler.Provider
to fulfill.
type Provider struct{} func (Provider) Register(binder infra.Binder) {...} func (Provider) Aggregates() []infra.Provider { return []infra.Provider{ // 加载 scheduler 定时任务模块 scheduler.Provider( func(resolver infra.Resolver, creator scheduler.JobCreator) { // 添加一个名为 test-job 的任务,每隔 10s 执行一次 _ = cr.Add("test-job", "@every 10s", TestJob) // 添加一个名称为 test-timeout-job 的任务,每隔 5s 执行一次 // 通过 AddAndRunOnServerReady 添加的任务会在服务启动时先执行一次 _ = creator.AddAndRunOnServerReady( "test-timeout-job", "@every 5s", // 使用 scheduler.WithoutOverlap 包装的函数,当前一次调度还没有执行完毕,本次调度的时间已到,本次调度将会被取消 scheduler.WithoutOverlap(TestTimeoutJob).SkipCallback(func() { ... // 当前一个任务还没有执行完毕时,当前任务会被跳过,跳过时会触发该函数的执行 }), ) }, ), } }
scheduler.Provider
Support distributed locks, through SetLockManagerOption
The option can specify the implementation of distributed locks to satisfy the logic that the task will only be triggered once in a group of servers.
scheduler.Provider( func(resolver infra.Resolver, creator scheduler.JobCreator) {...}, // 设置分布式锁 scheduler.SetLockManagerOption(func(resolver infra.Resolver) scheduler.LockManagerBuilder { // get redis instance redisClient := resolver.MustGet(&redis.Client{}).(*redis.Client) return func(name string) scheduler.LockManager { // create redis lock return redisLock.New(redisClient, name, 10*time.Minute) } }), )
Note: The Glacier framework does not have a built-in implementation of distributed locks, in mylxsw/distribute-locks Implemented a simple Redis-based distributed lock implementation, which can be used for reference.
log
In Glacier, the default is to use asteria As a log framework, asteria is a powerful and flexible structured log framework that supports multiple log output formats and output methods, and supports adding context information to log information.
The easiest way is through log.SetDefaultLogger(logger infra.Logger)
method to set the default log handler for the Glacier framework,
// import "github.com/mylxsw/glacier/log" // 默认设置,使用 asteria 日志框架 // import asteria "github.com/mylxsw/asteria/log" log.SetDefaultLogger(asteria.Module("glacier")) // 使用标准库中的日志包,Glacier 对标准库日志包进行了简单封装 log.SetDefaultLogger(log.StdLogger())
Of course, if you use the application created by the starter template project, you can also use WithLogger(logger infra.Logger)
method to set the log handler.
ins := app.Default("1.0") ... // 设置使用标准库日志包,不输出 DEBUG 日志 ins.WithLogger(log.StdLogger(log.DEBUG)) ...
In addition to the default asteria
The logging library and Glacier’s own StdLogger
In addition, you can also use other third-party log packages, only need simple encapsulation, to achieve infra.Logger
interface.
type Logger interface { Debug(v ...interface{}) Debugf(format string, v ...interface{}) Info(v ...interface{}) Infof(format string, v ...interface{}) Error(v ...interface{}) Errorf(format string, v ...interface{}) Warning(v ...interface{}) Warningf(format string, v ...interface{}) // Critical 关键性错误,遇到该日志输出时,应用直接退出 Critical(v ...interface{}) // Criticalf 关键性错误,遇到该日志输出时,应用直接退出 Criticalf(format string, v ...interface{}) }
Eloquent ORM
Eloquent ORM is a database ORM framework developed for Go. Its design is inspired by the famous PHP development framework Laravel and supports MySQL and other databases.
The project address is mylxsw/eloquent can be used with Glacier framework.
smooth exit
Glacier supports smooth exit, when we press the keyboard’s Ctrl+C
When (receiving signals such as SIGINT, SIGTERM, Interrupt, etc.), Glacier will receive a shutdown signal, and then trigger the shutdown behavior of the application.By default, our application will exit immediately, we can enable the smooth support option on the application created by the starter template WithShutdownTimeoutFlagSupport(timeout time.Duration)
to set the default smooth exit time
ins := app.Create("1.0") ins.WithShutdownTimeoutFlagSupport(5 * time.Second) ... // Provider 中获取 `gf.Graceful` 实例,注册关闭时的处理函数 resolver.MustResolve(func(gf graceful.Graceful) { gf.AddShutdownHandler(func() { ... }) })
Third-party framework integration
sample project
- Example Example of use
- WebDAV Server A WebDAV server that supports LDAP as a user database
- Adanos Alert A powerful open source alarm platform, through the event aggregation mechanism, provides support for alarm methods such as DingTalk, email, HTTP, JIRA, and voice calls for the monitoring system
- Health Check Provide health check alarm support for application services
- Sync Cross-server file synchronization service
- Tech Share A web application for internal technology sharing management of small and medium teams
- Universal Exporter A general-purpose Prometheus dimension tool that currently supports querying and generating Metric data from the database
- Graphviz Server A web service that encapsulates the interface call to Graphviz and realizes the generation of Graphviz graphics through Web API
- MySQL Guard Used for MySQL long transaction detection kill and deadlock alarm
- Password Server A simple web server that generates random passwords
#Glacier #Framework #Homepage #Documentation #Downloads #Development #Framework #Supporting #Dependency #Injection #News Fast Delivery