Skip to content

micro-framework for deterministic services boot and shutdown

License

Notifications You must be signed in to change notification settings

pinebit/go-boot

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

go-boot

Go

A micro-framework for deterministic and graceful services boot and shutdown.

Check the article on Medium about the framework.

Why?

Most golang applications are made of multiple services, where each service is an entity that can be started and stopped on-demand. Most of the services depend on each other, for example it makes little sense to start HTTP server until a DB connection is established. Therefore, all the services need to be started in the right order and then stopped in the reverse order. This allows application to boot and shutdown gracefully and deterministically, properly acquiring and releasing all the resources, locks, etc.

Furthermore, each application has to respect OS signals (e.g. SIGINT, SIGTERM). And such the signals can be caught even during the application boot process and in that case they should trigger the shutdown flow immediately. The shutdown flow is also non-trivial, because for most execution environments an application is given a little window to shutdown gracefully (usually 5 to 15 seconds) before it gets SIGKILL. All the flows are usually controlled by a context provided by the application, either bound to OS signals, or a timeout.

This architecure pattern became very common for most applications, and this is why go-boot was built as a standalone micro-framework.

Usage

  1. Installation
go get -u github.com/pinebit/go-boot
  1. Make your services conforming the boot.Service interface
package service1

type Service1 interface {
    boot.Service
}
  1. Instantiate all your services

Recommended to use Dependency Injection frameworks, such as wire, fx or any other, for now we simply create all services one by one.

s1 := service1.NewService1(...)
s2 := service2.NewService2(...)
s3 := service3.NewService3(...) 
  1. Start all services respecting the given context
// You can choose between boot.Sequentially, boot.Simultaneously or combine them.
services := boot.Sequentially(s1, s2, s3)
// the ctx can stop the boot flow gracefully
if err := services.Start(ctx); err != nil {
    // report error and proceed with the shutdown 
}

You can combine sequential and parallel boot flows. For example, given services A, B, C and D. Where service A must be started first, then B and C can be started simultaneously and then D can be started only after A, B and C have started:

services := boot.Sequentially(a, boot.Simultaneously(b, c), d)
err := services.Start(ctx)
  1. Shutdown all services respecting a timeout
// The recommended shutdown timeout is five seconds for most systems.
ctx, cancel := context.WithTimeout(context.Background(), 5 * time.Second)
defer cancel()
// Stop() will stop all services in the reverse order.
// The shutdown flow would break if the ctx is done.
if err := services.Stop(ctx); err != nil {
    // One of the services reported error, or ctx is done.
    // Log the error for further investigation.
    // You still have a good chance to release critical resources, locks. 
}

Using Application wrapper

Almost every application is handling OS signals to trigger a shutdown. The framework provides this convenient wrapper, that allows you to build a complete graceful application:

func main() {
    // creating services: s1, s2, s3
    services := boot.Sequentially(s1, s2, s3)
    app := boot.NewApplicationForService(services, 5 * time.Second)
    if err := app.Run(context.Background()); err != nil {
        fmt.Println("application error:", err)
    }
}

Using HttpServer wrapper for the standard http.Server

The framework also provides a convenient wrapper for http.Server that gracefully starts/stops the HTTP server. Together with Application you get the complete minimalistic yet graceful HTTP server:

func main() {
	server := &http.Server{
		Addr: ":8080",
	}
	serverAsService := boot.NewHttpServer(server)
	app := boot.NewApplicationForService(serverAsService, 5*time.Second)
	if err := app.Run(context.Background()); err != nil {
		fmt.Println("server error:", err)
	}
}

License

MIT. See the LICENSE file for details.

About

micro-framework for deterministic services boot and shutdown

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages