Nacelle

Go service framework
/

Dependency injection

The github.com/go-nacelle/service package provides service container and dependency injection.


A service container is a collection of objects which are constructed separately from their consumers. This pattern allows for a greater separation of concerns, where consumers care only about a particular concrete or interface type, but do not care about their configuration, construction, or initialization. This separation also allows multiple consumers for the same service which does not need to be initialized multiple times (e.g. a database connection or an in-memory cache layer). Service injection is performed on processes during application startup automatically.

A concrete service can be registered to the service container with a unique name by which it can later be retrieved. The Set method fails when a service name is reused. There is also an analogous MustSet method that panics on error. The logger (under the name logger), the health tracker (under the name health), and the service container itself (under the name services) are available in all applications using the nacelle bootstrapper.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
type Process struct {
	Services *nacelle.ServiceContainer `service:"services"`
}

func (p *Process) Init(ctx context.Context) error {
	example := &Example{}
	if err := p.Services.Set("example", example); err != nil {
		return err
	}

	// ...
}

A service can be retrieved from the service container by the name with which it is registered. An alternative way to consume dependent services is to inject them into a struct decorated with tagged fields.

1
2
3
4
5
6
7
8
type Consumer struct {
    Service *SomeExample `service:"example"`
}

consumer := &Consumer{}
if err := services.Inject(consumer); err != nil {
    // handle error
}

The Inject method fails when a consumer asks for an unregistered service or for a service with the wrong target type. Services can be tagged as optional (e.g. service:"example" optional:"true") which will silence the later class of errors. Tagged fields must be exported.

You can see an additional example of service injection in the example repository, specifically the worker spec definition. In this project, the Conn and PubSubConn services are created by application-defined initializers here and here.