service

package module
v1.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 1, 2020 License: MIT Imports: 4 Imported by: 2

README

Nacelle Service Container GoDoc CircleCI Coverage Status

Service container and dependency injection for nacelle.


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 initializers and processes during application startup automatically.

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.

Registration

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.

func Init(services nacelle.ServiceContainer) error {
    example := &Example{}
    if err := services.Set("example", example); err != nil {
        return err
    }

    // ...
}

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.

Retrieval

A service can be retrieved from the service container by the name with which it is registered. However, this returns a bare interface object and requires the consumer of the service to do a type-check and cast.

Instead, the recommended way to consume dependent services is to inject them into a struct decorated with tagged fields. This does the proper type conversion for you.

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.

Post Injection Hook

If additional behavior is necessary after services are available to a consumer struct (e.g. running injection on the elements of a dynamic slice or map or cross-linking dependencies), the method PostInject can be implemented. This method, if it is defined, is invoked immediately after successful injection.

func (c *Consumer) PostInject() error {
    return c.Service.PrepFor("consumer")
}
Anonymous Structs

Injection also works on structs containing composite fields. The following example successfully assigns the registered value to the field Child.Base.Service.

type Base struct {
    Service *SomeExample `service:"example"`
}

type Child struct {
    *Base
}

child := &Child{}
if err := container.Inject(child); err != nil {
    // handle error
}
Recursive Injection

It should be noted that injection does not work recursively. The procedure does not look into the values of non-anonymous fields. If this behavior is needed, it can be performed during a post-injection hook. The following example demonstrates this behavior.

type RecursiveInjectionConsumer struct {
    Services service.ServiceContainer `service:"services"`
    FieldA   *A
    FieldB   *B
    FieldC   *C
}

func (c *RecursiveInjectionConsumer) PostInject() error {
    fields := []interface{}{
        c.FieldA,
        c.FieldB,
        c.FieldC,
    }

    for _, field := range fields {
        if err := c.Services.Inject(field); err != nil {
            return err
        }
    }

    return nil
}

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type PostInject

type PostInject interface {
	PostInject() error
}

PostInject is a marker interface for injectable objects which should perform some action after injection of services.

type ServiceContainer

type ServiceContainer interface {
	// Get retrieves the service registered to the given key. It is an
	// error for a service not to be registered to this key.
	Get(key string) (interface{}, error)

	// MustGet calls Get and panics on error.
	MustGet(service string) interface{}

	// Set registers a service with the given key. It is an error for
	// a service to already be registered to this key.
	Set(key string, service interface{}) error

	// MustSet calls Set and panics on error.
	MustSet(service string, value interface{})

	// Inject will attempt to populate the given type with values from
	// the service container based on the value's struct tags. An error
	// may occur if a service has not been registered, a service has a
	// different type than expected, or struct tags are malformed.
	Inject(obj interface{}) error
}

ServiceContainer is a wrapper around services indexed by a unique name. Services can be retrieved by name, or injected into a struct by reading tagged fields.

func NewServiceContainer

func NewServiceContainer() ServiceContainer

NewServiceContainer creates an empty service container.

func Overlay added in v1.0.1

func Overlay(base ServiceContainer, services map[string]interface{}) ServiceContainer

Overlay wraps the given service container with an immutable map of services. Calling Get or MustGet on the resulting service container will return a service from the overlay map, then will fall back to the wrapped service container. Similarly, Inject will favor services from the overlay map.

This allows a user to re-assign services in the container for a specific specialized code path. This can be used, for example, to inject a logger with context for the current request or task to a short-lived handler.

Calling Set or MustSet will modify the wrapped container directly.

type ServiceGetter added in v1.0.1

type ServiceGetter interface {
	// Get retrieves the service registered to the given key. It is an
	// error for a service not to be registered to this key.
	Get(key string) (interface{}, error)
}

ServiceGetter is a subset of a ServiceContainer that only supports the retrieval of a registered service by name.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL