Skip to content

Latest commit

 

History

157 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

yama

A compile-time lifecycle orchestration framework: it derives application startup/quiesce/shutdown ordering from a Google Wire dependency graph and generates the orchestration code, rather than building a runtime engine that interprets one.

Build Status Go Report Card Documentation Coverage Status License

Image of Yama

Status

This is a green-field rewrite; it shares only a name and a repository with the signal-watcher released as v0.1.0 and v0.2.0. The public API surface is defined and frozen, and the generator and runtime are complete. The project is preparing its first alpha release.

Setup

Generation is one command. Yama runs Google Wire for you, reads the injector Wire produces, and writes lifecycle_gen.go.

Pin both tools in the application's go.mod. Yama invokes Wire as go tool wire from the target package's own module, so that module supplies it:

tool (
	github.com/google/wire/cmd/wire
	l7e.io/yama/cmd/yama
)

Declaring a lifecycle

Declare the graph with ordinary Wire providers. Then add a lifecycle stub file, behind the yamainject build tag, naming each constructor the application calls and the providers its graph is built from:

//go:build yamainject

package hello

// NewLifecycle orchestrates the graph GraphSet builds, reporting to w.
func NewLifecycle(w io.Writer, opts ...yama.Option) (*Server, yama.Lifecycle, error) {
	panic(wire.Build(GraphSet))
}

Add the generate directive to a committed file that no build tag excludes; go generate does not read a file a build tag hides, so it cannot live in the stub file:

//go:generate go tool yama

Generating

Then generate:

go generate ./...

Yama's flags and package-pattern argument are wire gen's own, so a directive that already names Wire's command carries over by changing the command it names.

Running

The application calls the generated constructor and runs the lifecycle it returns:

app, lc, err := hello.NewLifecycle(os.Stdout)
if err != nil {
	log.Fatal(err)
}

if err := yama.RunUntilSignal(context.Background(), lc); err != nil {
	log.Fatal(err)
}

RunUntilSignal gives its context to Start and to Stop without a change. Every component and every interceptor therefore receives it. Deployment facts on that context, such as a node identifier, reach all of them. A cancellation and a deadline reach them too. A cancellation also stops the application in the same way that a signal does.

Boundary components

Some components belong at the edges of the graph's order rather than inside it. Two options on the generated constructor register them:

app, lc, err := hello.NewLifecycle(os.Stdout,
	yama.WithBeginComponents(telemetry),
	yama.WithEndComponents(readiness),
)

A begin component starts before every graph component, and it quiesces and stops after every graph component. Base services such as telemetry belong here. They outlive everything that uses them.

An end component starts after every graph component, and it quiesces and stops before every graph component. A readiness flip belongs here. It turns on only when the whole application is up, and it turns off first.

A boundary component participates through the capabilities it implements, exactly as a graph component does. Both options are variadic and accumulate across calls.

Files in the package directory

lifecycle_gen.go is the only file generation commits. A run also writes two transient files into the package directory and removes both before it returns:

File Owner Committed
lifecycle_gen.go Yama yes
wire_gen.go Wire no, transient
yama_wireinject.go Yama no, transient

A run does not overwrite a wire_gen.go that it did not create. It moves that file to .yama.wire_gen.go for the run, and puts it back at the end. An application that commits its own Wire output therefore keeps it. A run moves a committed lifecycle_gen.go to .yama.lifecycle_gen.go the same way.

Yama owns the name yama_wireinject.go. A run writes over a file already at that name. Do not keep a file of your own there.

Do not start two runs over one package directory at the same time. Yama does not lock the directory. The second run's cleanup can delete a wire_gen.go that the first run put back.

Recovering from an interrupted run

A run that stops before it completes does not reach its cleanup. It can leave these files behind:

  • .yama.wire_gen.go — your wire_gen.go, if you committed one.
  • .yama.lifecycle_gen.go — your committed lifecycle_gen.go.
  • wire_gen.go — Wire's output from that run, not yours.
  • yama_wireinject.go — Yama's derived injectors.

Generate again. The next run repairs the directory before it reads anything:

go generate ./...

It puts both .yama. files back under their original names first, and discards the output the interrupted run left. You lose nothing. Yama creates a .yama. file only to hold a file of yours.

Example

examples/hello is a working application built this way. It is its own Go module, so it reaches Yama the way an application does.

Guide

docs/guide.md is the behavior reference: the lifecycle model, ordering, errors, interceptors, the context, and the command.

Design

For the design, see:

About

A signal watcher that can be used to shutdown an application

Topics

Resources

Stars

0 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages