di

package module
v0.0.9 Latest Latest
Warning

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

Go to latest
Published: Sep 22, 2024 License: MIT Imports: 10 Imported by: 0

README

📦 GoDoc

automatica.team/di is a dead simple dependency injection tool for Go. Supports declarative configuration.

Installation

$ go get -u automatica.team/di

Usage

Declarative configuration is optional, but is very handy and recommended to use:

# example/di.yml

# A version of the current dependency state
version: 0

# Dependencies are described here
di:
  # Order does matter, keep individual dependencies on the top
  x/db:
    # Immediate dependency configuration
    prepare_stmt: true
    # A '$' sign points to look up the env
    path: $DB_PATH # "example.db" is also valid
  # `x/hit` depends on `x/db`, that's why it's defined as the second
  x/hit: {}

# A list of Go imports for external dependency integration
imports:
  # Generation tool will add this import path, thus it will
  # be able to inject the external dependency if it satisfies
  # the `di.Dependency` interface
  - github.com/external/dependency

Use a cmd/di generation tool to automatically inject the dependencies:

// example/main.gen.go

package main

import (
	"automatica.team/di"
	"automatica.team/di/example/db"
	"automatica.team/di/example/hit"
)

// Injections generated automatically via `go:generate`.
func init() {
	di.Inject(db.New())
	di.Inject(hit.New())
}

The first defined dependency that opens the database connection:

// example/db/db.go

package db

import (
	"fmt"

	"automatica.team/di"

	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

var _ = (di.D)(&DB{})

// A shortcut for `di` generation to avoid searching for a proper type.
func New() *DB { return &DB{} }

// DB implements `di.D` that is `Dependency`.
type DB struct {
	*gorm.DB
}

// Name is a unique identifier of your dependency.
func (DB) Name() string {
	// Use the "x/" prefix for the local dependencies.
	return "x/db"
}

// New defines a constructor for dependency
// The `di.C` stands for `Config`.
func (DB) New(c di.C) (di.D, error) {
	// Using built-in configuration helper, `err` will be
	// properly formatted to display the missing required param.
	path, err := c.EnvString("path")
	if err != nil {
		return nil, err
	}

	conf := &gorm.Config{
		// Use `Must` to get zero value for optional params.
		PrepareStmt: di.Must(c.Bool("prepare_stmt")),
	}

	db, err := gorm.Open(sqlite.Open(path), conf)
	if err != nil {
		return nil, fmt.Errorf("x/db: %w", err)
	}

	return &DB{DB: db}, nil
}

Dependency can inject other dependencies declared above it. In this example, x/hit depends on the x/db. Hitter provides some server logic to be used later in the final Server runnable:

// example/hit/hit.go

package hit

import (
	"time"

	"automatica.team/di"
	"automatica.team/di/example/db"
)

var _ = (di.D)(&Hitter{})

func New() *Hitter {
	return &Hitter{}
}

type Hitter struct {
	db *db.DB `di:"x/db"`
}

func (Hitter) Name() string {
	return "x/hit"
}

func (h Hitter) New(c di.C) (di.D, error) {
	// Automatically migrate the `hits` table.
	// `h.db` is already accessible.
	return New(), h.db.AutoMigrate(&Hit{})
}

// Hit represents a database model for server hits.
type Hit struct {
	When time.Time
	IP   string
}

// Specify a table name for GORM.
func (Hit) TableName() string {
	return "hits"
}

// Hit adds a new IP hit to the database.
func (h Hitter) Hit(ip string) error {
	return h.db.Create(&Hit{
		When: time.Now(),
		IP:   ip,
	}).Error
}

You end up by calling a single final runnable in the main function:

// example/main.go

package main

import (
	"net/http"

	"automatica.team/di"
	"automatica.team/di/example/hit"

	"github.com/labstack/echo/v4"
)

//go:generate $(go env GOBIN)/di

func main() {
	// Parse is optional, but it's handy for configuring
	// the dependencies in a single config file.
	if err := di.Parse(); err != nil {
		panic(err)
	}
	// Running a final handler that in some way uses
	// the injected dependencies.
	if err := di.Run[Server](); err != nil {
		panic(err)
	}
}

// Server is a final runnable entity.
type Server struct {
	// Inject any declared dependency by its name.
	hit *hit.Hitter `di:"x/hit"`
}

// Run implements `di.R` that is `Runnable`.
func (s Server) Run() error {
	e := echo.New()
	e.HideBanner = true
	e.GET("/", s.onHit)
	return e.Start(":8080")
}

func (s Server) onHit(c echo.Context) error {
	// Using injected `x/hit` dependency.
	if err := s.hit.Hit(c.RealIP()); err != nil {
		return err
	}
	return c.NoContent(http.StatusOK)
}

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Get added in v0.0.7

func Get[T Dependency](name string) (T, error)

Get returns the dependency by its name. It should be already initialized.

func Inject

func Inject(d Dependency)

Inject adds a dependency to the global scope. Always inject dependencies you are going to use. Always pass the pointer to the dependency.

func Must

func Must[T any](v T, _ error) T

Must is a helper function when working with `di.Config` that returns zero value of the type if the error is not nil.

func Parse

func Parse(path ...string) error

Parse parses the global `di.yml` configuration file. It also sets the `Version` of current dependencies state.

func Run

func Run[R Runnable]() error

Run runs the `di` flow injecting all the dependencies and running the provided runnable instance. Your runnable instance will be injected with dependencies as well.

func Version

func Version() string

Version returns the version of the current dependencies state.

Types

type C

type C = Config

A set of frequently used aliases for the package.

type Config

type Config struct {
	// contains filtered or unexported fields
}

Config represents the configuration of the dependency. It exposes a bunch of useful methods to get the values.

func (Config) Any

func (c Config) Any(key string) (any, error)

Any retrieves the value for the given key. It returns an error if the key is missing.

func (Config) Bool

func (c Config) Bool(key string) (bool, error)

Bool retrieves the value for the given key as a boolean.

func (Config) Duration

func (c Config) Duration(key string) (time.Duration, error)

Duration retrieves the value for the given key as a `time.Duration`.

func (Config) EnvString

func (c Config) EnvString(key string) (string, error)

EnvString retrieves the value for the given key as a string, treating values starting with "$" as environment variables. It returns an error if the environment variable is empty.

func (Config) Float

func (c Config) Float(key string) (float64, error)

Float retrieves the value for the given key as a float64.

func (Config) Int

func (c Config) Int(key string) (int, error)

Int retrieves the value for the given key as an integer.

func (Config) String

func (c Config) String(key string) (string, error)

String retrieves the value for the given key as a string.

type D

type D = Dependency

A set of frequently used aliases for the package.

type Dependency

type Dependency interface {
	// Name returns the name of the dependency.
	// The name is used to identify the dependency in the `di.yml`.
	Name() string
	// New creates a new instance of the dependency using the provided
	// config `di.Config`. Don't worry about filling the new instance with
	// other injectable dependencies, it will be done automatically.
	//
	// NOTE: Always return the pointer to the dependency.
	New(C) (D, error)
}

Dependency represents an injectable dependency.

type Idle added in v0.0.7

type Idle struct{}

Idle is a dummy runnable that does nothing. Use it for initializing your dependencies without running anything.

func (Idle) Run added in v0.0.7

func (Idle) Run() error

Run implements `di.Runnable` interface.

type M

type M = map[string]any

A set of frequently used aliases for the package.

type Optional added in v0.0.3

type Optional[T Dependency] struct {
	// contains filtered or unexported fields
}

Optional is a wrapping type that can be used to inject optional dependencies.

func (Optional[T]) Get added in v0.0.3

func (o Optional[T]) Get() (*T, bool)

Get returns the value of the optional dependency and a boolean flag indicating whether it was injected.

func (Optional[T]) Use added in v0.0.4

func (o Optional[T]) Use(f func(*T))

Use calls the provided function with the value of the optional dependency if it was injected. See With for the usage example.

func (Optional[T]) With added in v0.0.3

func (o Optional[T]) With(f func(*T) error) (err error)

With calls the provided function with the value of the optional dependency if it was injected. This is an error-powered wrapper.

Usage:

var s struct {
	cache di.Optional[cache.Cache] `di:"x/cache"`
}
s.cache.With(func(c *cache.Cache) error {
	return c.Set("key", "value")
})

type R

type R = Runnable

A set of frequently used aliases for the package.

type Runnable

type Runnable interface {
	// Run is the main method of the runnable instance.
	Run() error
}

Runnable represents a final runnable instance, that uses all the dependencies provided in the end. Using `di.Run` the dependencies of the runnable instance will be injected automatically.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL
JackTT - Gopher 🇻🇳