/lifecycle-go

🚴🏻‍♂️ A complete application runtime framework for your Go apps

Primary LanguageGoApache License 2.0Apache-2.0

🚴🏻‍♂️ Lifecycle – an application runtime framework for Go apps

GoDoc

This package provides a start, block-until-signal, and stop(timeout) app runtime for multiple services in your Go apps.

Lifecycle provides ability to act on each step of the lifecycle events for every service it runs.

Append(…) and AppendService(…) calls are additive. Appended services get started in ascending and stopped in descending order.

The last service can either run a blocking task (e.g. a http server), or a non-blocking task (e.g. a bunch of cancellable worker goroutines).

Run(…) expects an implementation of lifecycle.Printer interface. It is used to log start and stop errors. Tested to work with at least *logrus.Logger.

A SIGINT, SIGTERM, or cancellation of context provided to RunContext(…) triggers a stop.

Installation

go get -u https://github.com/Gurpartap/lifecycle-go
import "github.com/Gurpartap/lifecycle-go"

Usage

See _example/cmd/web and _example/cmd/cron-jobs for detailed usage examples.

package main

import (
	"context"
	"database/sql"
	"net/http"
	"time"

	"github.com/Gurpartap/lifecycle-go"
	"github.com/sirupsen/logrus"
)

func main() {
	logger := logrus.StandardLogger()

	lc := &lifecycle.Lifecycle{}

	var db *sql.DB
	// db = providers.DB(lc, logger, driverName, dataSourceName)
	//
	// or
	//
	// dbProvider := providers.NewDBProvider(logger, driverName, dataSourceName)
	// // dbProvider implements lifecycle.Service
	// lc.AppendService(dbProvider)
	//
	// or
	lc.Append(&lifecycle.Hook{
		OnStart: func(_ context.Context) error {
			var err error
			db, err = sql.Open("postgres", "postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable")
			if err != nil {
				return err
			}
			return nil
		},
		OnStop: func(ctx context.Context) error {
			// select on <-ctx.Done() to implement stop timeout
			logger.Infoln("stopping db…")
			// http server (below) is shut down before attempting to stop db
			return db.Close()
		},
	})

	r := http.NewServeMux()
	r.HandleFunc("/ping", func(rw http.ResponseWriter, req *http.Request) {
		rw.WriteHeader(http.StatusOK)
		rw.Write([]byte("pong"))
	})

	server := http.Server{
		Addr:    ":3000",
		Handler: r,
	}

	lc.Append(&lifecycle.Hook{
		// only the last service may block inside its OnStart hook
		OnStart: func(ctx context.Context) error {
			// <-ctx.Done emits on stop request
			logger.Infoln("running server…")
			if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
				return err
			}
			return nil
		},
		OnStop: func(ctx context.Context) error {
			logger.Infoln("shutting down server…")
			// <-ctx.Done emits on stop timeout
			return server.Shutdown(ctx)
		},
	})

	// logger implements lifecycle.Printer
	// 15 seconds of stop timeout
	lc.Run(logger, 15*time.Second)
}

Credits

This package takes a considerable amount of inspiration from the amazing uber-go/fx package. If you're also looking for dependency injection along with an application framework similar to Lifecycle, use fx.

Lifecycle aims for compile-time type safety while also being open to use of any dependency injection method.

Lifecycle depends on github.com/pkg/errors and go.uber.org/multierr for error handling.

About

Copyright 2020 Gurpartap Singh

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.