Home

Awesome

nserve - server startup/shutdown in an nject world

GoDoc unit tests report card codecov

Install:

go get github.com/muir/nserve

Prior to nject version 0.2.0, this was part of that repo.


This package provides server startup and shutdown wrappers that can be used with libraries and servers. It is based up nject.

Example

On thing you might want to do with nserve is to use a Hook to trigger per-library database migrations using libschema.

First create the hook:

package myhooks

import "github.com/nject/nserve"

var MigrateMyDB = nserve.NewHook("migrate, nserve.Ascending)

In each library, have a create function:

package users

import(
	"github.com/muir/libschema/lspostgres"
	"github.com/muir/nject/nserve"
)

func NewUsersStore(app *nserve.App) *Store {
	...
	app.On(myhooks.MigrateMyDB, func(database *libschema.Database) {
		database.Migrations("MyLibrary",
			lspostgres.Script("create users", `
				CREATE TABLE users (
					id	bigint PRIMARY KEY,
					name	text
				)
			`),
		)
	})
	...
	return &Store{}
}

Then as part of server startup, invoke the migration hook:

package main

import(
	"github.com/muir/libschema"
	"github.com/muir/libschema/lspostgres"
	"github.com/muir/nject"
)

func main() {
	app, err := nserve.CreateApp("myApp", users.NewUserStore, ...)
	schema := libschema.NewSchema(ctx, libschema.Options{})
	sqlDB, err := sql.Open("postgres", "....")
	database, err := lspostgres.New(logger, "main-db", schema, sqlDB)
	myhooks.MigrateMyDB.Using(database)
	err = app.Do(myhooks.MigrateMyDB)

How to structure your application

Libraries become injected dependencies. They can in turn have other libraries as dependencies of them. Since only the depenencies that are required are actaully injected, the easiest thing is to have a master list of all your libraries and provide that to all your apps.

Let's call that master list allLibraries.

app, err := nserve.CreateApp("myApp", allLibrariesSequence, createAppFunction)
err = app.Do(nserve.Start)
err = app.Do(nserve.Stop)

Hooks

Libaries and appliations can register callbacks on a per-hook basis. Two hooks are pre-provided by other hooks can be created.

Hook invocation can be limited by a timeout. If the hook does not complete in that amount of time, the hook will return error and continue processing in the background.

The Start, Stop, and Shutdown hooks are pre-defined:

var Shutdown = NewHook("shutdown", ReverseOrder)
var Stop = NewHook("stop", ReverseOrder).OnError(Shutdown).ContinuePastError(true)
var Start = NewHook("start", ForwardOrder).OnError(Stop)

Hooks can be invoked in registration order (ForwardOrder) or in reverse registration order ReverseOrder.

If there is an OnError modifier for the hook registration, then that hook gets invoked if there is an error returned when the hook is running.

Libraries and applications can register callbacks for hooks by taking an *nserve.App as as an input parameter and then using that to register callbacks:

func CreateMyLibrary(app *nserve.App) *MyLibrary {
	lib := &MyLibrary{ ... }
	app.On(Start, lib.Start)
	return lib
}
func (lib *MyLibrary) Start(app *nserve.App) {
	app.On(Stop, lib.Stop)
}

The callback function can be any nject injection chain. If it ends with a function that can return error, then any such error will be become the error return from app.Do and if there is an OnError handler for that hook, that handler will be invoked.