wirejacket

package module
v1.1.5 Latest Latest
Warning

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

Go to latest
Published: Nov 16, 2021 License: Apache-2.0 Imports: 6 Imported by: 1

README

Wire-Jacket: IoC Container of google/wire for cloud-native

GoDoc Github release Build Status Coverage Status Go Report Card

Jacket of google/wire: advanced DI approach wrapping google/wire for cloud.

A jacket is an outer sheath that groups wires and protects the core from external issues.

wire-jacket wraps google/wire and provides advanced DI(Dependency Injection) experience in cloud-native.

Installation

Install Wire-Jacket by running:

go get github.com/bang9211/wire-jacket

and ensuring that $GOPATH/bin is added to your $PATH.

Example

Wire-Jacket example of ossicones. In this example, ossicones is simple blockchain package. It consisted of only 3 components: Config, Database, Blockchain.

Define simple two Interface, Implement.

type Database interface {
    Connect() bool
    Close() error   //necessary for Wire Jacket
}

type MySQL struct {
    cfg viperjacket.Config
}

func NewMySQL(cfg viperjacket.Config) Database {
    return &MySQL{cfg : cfg}
}

func (m *MySQL) Connect() error {
    address := m.cfg.GetString("address", "localhost:3306")
    ...
    return nil
}

func (m *MySQL) Close() error {
    return nil
}
type Blockchain interface {
    Init() error
    Close() error   //necessary for Wire Jacket
}

type Ossicones struct {
    db *Database
}

func NewOssicones(db Database) Blockchain {
    return &Ossicones{db : db}
}

func (o *Ossicones) Init() error {
    err := o.db.Connect()
    ...
    return nil
}

func (o *Ossicones) Close() error {
    return nil
}

Suppose there is MongoDB that implements Database Interface like MySQL. And Wire-Jacket has ViperJacket by default.

Then, there are 3 Interfaces and 4 Implements.

  • Database Interface - MySQL, MongoDB Implement
  • Blockchain Interface - Ossicones Implement
  • (Default) Config Interface - ViperJacket Implement

Database depends on viperjacket.Config. Blockchain depends on Database.

The pair of interface and implement called module in Wire-Jacket.

Wire-Jacket helps to replace implement of interface easy way. And close modules gracefully. So the modules are closable, have to implment Close().

1. Create wire.go with injectors.
package wire

func InjectMySQL(cfg viperjacket.Config) (Database, error) {
	wire.Build(NewMySQL)
	return nil, nil
}

func InjectMongoDB(cfg viperjacket.Config) (Databsae, error) {
    wire.Build(NewMongoDB)
    return nil, nil
}

func InjectOssicones(db Database) (Blockchain, error) {
	wire.Build(NewOssicones)
	return nil, nil
}

// key will use in app.conf
var Injectors map[string]interface{} = {"mysql" : InjectMySQL}
var EagerInjectors map[string]interface{} = {"ossicones" : InjectOssicones}
2. Generate wire_gen.go using wire.
wire wire.go

wire is compile-time DI. It can verify validity of DI in compile-time.

3. Create app.conf(default way)
# Specify module to use.
modules=mysql ossicones

# And you can add any config to use.
db_host=localhost
db_port=3306

Choose modules to use mysql, ossicones.

Database binds to MySQL, Blockchain binds to Ossicones.

4. Create wirejacket, Set injectors, Call DoWire().
wj := wirejacket.New().
    SetInjectors(wire.Injectors).
    SetEagerInjectors(wire.EagerInjectors)

// inject eager injectors.
wj.DoWire()

Except for eager injectors, injectors are loaded lazy by default.

Or If you call wj.GetModule() to get the module you need, all the dependencies of the module will be injected automatically. you don't need to call DoWire() in this case. It is not necessary to call DoWire().

Assume that there is mongodb like mysql as the implementation of Database.

If you want to change implement of Database to mongodb, Just modify modules in config and restart app.

modules=mongodb ossicones

Features

  • google/wire based IoC Container
  • Environment-variable based setting for cloud
  • Lazy Loading, Eager Loading

Why Wire-Jacket needs?

google/wire works statically because it performs DI at compile-time. This approach is great for pre-debugging of DI.

It is difficult to debug problems that occur at runtime like dependency injection in a cloud environment. So It can be usefully used in a cloud environment.

Wire-Jacket wraps google/wire in order to use google/wire appropriately for the cloud environment.

1. IoC (Inversion of Control) Container

google/wire just provides injecting and binding features, do not have an IoC container. IoC Container makes it easy to version up and replace modules. You can also make a Plan B and keep it. For example, DB Skip mode that does not use DB or emergency processing mode that does not actually connect with other nodes can be applied by replacing module and restarting.

And it helps not to use singleton as a global variable. Automatically maintains and recycles one singleton object.

2. Binding based environment-variable for cloud.

The Twelve-Factors recommends use environment variables for configuration. Because it's good in container, cloud environments. However, it is not efficient to express all configs as environment variables.

So, we adopted viper, which integrates most config formats with environment variables in go. By using viper, you can use various config file formats without worrying about conversion to environment variables even if it is not in env format like json, yaml, toml, etc.

In Wire-Jacket, modules to use set in config file. For example, if you use the MySQL DB implementation in your app and want to replace the implementation with MongoDB, you don't need to change the code.

just change MySQL to MongoDB in config and restart the app.

3. Modulization, Loose-Coupling.

Wirejacket uses the simple approach of injecting implement into the interface. This approach simplifies and allows the team leader or designer to define the interface required for the project and effectively divide the work. the each implementation can be integrated easily and replaced as a plug-in using the config file.

Features of Wire-Jacket [ko] : https://syntaxsugar.tistory.com/entry/koWire-Jacket-IoC-Container-of-googlewire-for-cloud-native

Documentation

Overview

Package wirejacket is a IoC Container of google/wire for cloud-native

Example

Default use case to use New(). Wire-Jacket defaultly uses 'app.conf' for setting modules to activate. Or you can use the flag '--config {file_name}'.

package main

import (
	"fmt"
	"log"

	wirejacket "github.com/bang9211/wire-jacket"
)

// ==============================================
// Database Interface - MockupDB Implement example
// ==============================================
type Database interface {
	Connect() error

	Close() error
}

type MockupDB struct{}

func NewMockupDB() Database {
	return &MockupDB{}
}

func (mdb *MockupDB) Connect() error {
	return nil
}

func (mdb *MockupDB) Close() error {
	return nil
}

// =========================================================
// Blockchain Interface - MockupBlockchain Implement example
// =========================================================
type Block interface {
	GetData() string
}

type MockupBlock struct {
	data string
}

func (mb *MockupBlock) GetData() string {
	return mb.data
}

type Blockchain interface {
	Init() error

	AddBlock(data string) error

	GetBlocks() []Block

	Close() error
}

var genesisBlockData = "Genesis Block Data"

type MockupBlockchain struct {
	db     Database
	blocks []Block
}

func NewMockupBlockchain(db Database) Blockchain {
	return &MockupBlockchain{db: db, blocks: []Block{}}
}

func (mbc *MockupBlockchain) Init() error {
	mbc.db.Connect()
	mbc.AddBlock(genesisBlockData)
	return nil
}

func (mbc *MockupBlockchain) AddBlock(data string) error {
	mbc.blocks = append(mbc.blocks, &MockupBlock{data: data})
	return nil
}

func (mbc *MockupBlockchain) GetBlocks() []Block {
	return mbc.blocks
}

func (mbc *MockupBlockchain) Close() error {
	return nil
}

// InjectMockupDB injects dependencies and inits of Database.
func InjectMockupDB() (Database, error) {
	database := NewMockupDB()
	return database, nil
}

// InjectMockupBlockchain injects dependencies and inits of Blockchain.
func InjectMockupBlockchain(db Database) (Blockchain, error) {
	blockchain := NewMockupBlockchain(db)
	return blockchain, nil
}

var Injectors = map[string]interface{}{
	"mockup_database": InjectMockupDB,
}

var EagerInjectors = map[string]interface{}{
	"mockup_blockchain": InjectMockupBlockchain,
}

func main() {
	// Create wirejacket and set injectors.
	wj := wirejacket.New().
		SetEagerInjectors(EagerInjectors).
		SetInjectors(Injectors)
	defer wj.Close()

	// Inject eager injectors.
	if err := wj.DoWire(); err != nil {
		// Error occured in this example, because there is no app.conf and modules.
		log.Print(err)
	}

	// You can set modules using SetActivatingModules() directly without app.conf.
	wj.SetActivatingModules([]string{"mockup_blockchain", "mockup_database"})

	// Get module and use.
	blockchain := wj.GetModule("mockup_blockchain").(Blockchain)
	blockchain.AddBlock("test")
	fmt.Println(blockchain.GetBlocks())
}
Output:

Example (Second)

Second use case to use NewWithServiceName().

package main

import (
	"fmt"

	wirejacket "github.com/bang9211/wire-jacket"
)

// ==============================================
// Database Interface - MockupDB Implement example
// ==============================================
type Database interface {
	Connect() error

	Close() error
}

type MockupDB struct{}

func NewMockupDB() Database {
	return &MockupDB{}
}

func (mdb *MockupDB) Connect() error {
	return nil
}

func (mdb *MockupDB) Close() error {
	return nil
}

// =========================================================
// Blockchain Interface - MockupBlockchain Implement example
// =========================================================
type Block interface {
	GetData() string
}

type MockupBlock struct {
	data string
}

func (mb *MockupBlock) GetData() string {
	return mb.data
}

type Blockchain interface {
	Init() error

	AddBlock(data string) error

	GetBlocks() []Block

	Close() error
}

var genesisBlockData = "Genesis Block Data"

type MockupBlockchain struct {
	db     Database
	blocks []Block
}

func NewMockupBlockchain(db Database) Blockchain {
	return &MockupBlockchain{db: db, blocks: []Block{}}
}

func (mbc *MockupBlockchain) Init() error {
	mbc.db.Connect()
	mbc.AddBlock(genesisBlockData)
	return nil
}

func (mbc *MockupBlockchain) AddBlock(data string) error {
	mbc.blocks = append(mbc.blocks, &MockupBlock{data: data})
	return nil
}

func (mbc *MockupBlockchain) GetBlocks() []Block {
	return mbc.blocks
}

func (mbc *MockupBlockchain) Close() error {
	return nil
}

// InjectMockupDB injects dependencies and inits of Database.
func InjectMockupDB() (Database, error) {
	database := NewMockupDB()
	return database, nil
}

// InjectMockupBlockchain injects dependencies and inits of Blockchain.
func InjectMockupBlockchain(db Database) (Blockchain, error) {
	blockchain := NewMockupBlockchain(db)
	return blockchain, nil
}

func main() {
	// Create wirejacket with serviceName.
	wj := wirejacket.NewWithServiceName("example_service")
	defer wj.Close()

	// You can also add injector directly, instead of SetInjectors().
	wj.AddInjector("mockup_database", InjectMockupDB)
	wj.AddEagerInjector("mockup_blockchain", InjectMockupBlockchain)

	// Check value of modules in app.conf.
	// Or You can set modules using SetActivatingModules() directly.
	wj.SetActivatingModules([]string{"mockup_blockchain", "mockup_database"})

	// You can also get module without DoWire().
	// the dependencies of the module are injected automatically.
	blockchain := wj.GetModule("mockup_blockchain").(Blockchain)
	blockchain.AddBlock("test")
	fmt.Println(blockchain.GetBlocks())
}
Output:

Index

Examples

Constants

View Source
const DefaultConfigName = "viperjacket"
View Source
const DefaultModulesKey = "modules"

Variables

This section is empty.

Functions

func GetConfig added in v1.1.2

func GetConfig() viperjacket.Config

GetConfig returns config object.

Types

type Module

type Module interface {
	// Close closes module gracefully.
	Close() error
}

All the module should have Close().

type WireJacket

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

WireJacket structure. the jacket of the wires(injectors).

func New

func New() *WireJacket

New creates empty WireJacket. If you want to use more than one WireJacket on the same system, Use NewWithServiceName with unique serviceName instead of New(). By default, WireJacket reads list of module name to activate in 'modules' value of viperjacket.

But Wire-Jacket considered The Twelve Factors. Config can be overrided by envrionment variable.(see viperjacket.go) So, when using more than one WireJacket on the same system, each WireJacket should have a unique service name to avoid conflicting value of 'modules'.

If serviceName no exists, WireJacket reads value of 'modules'. By default, WireJacket reads app.conf. Or you can specify file with '--config' flag.(see viperjacket.go)

modules example in app.conf without serviceName

ossicones_modules=mysql ossiconesblockchain defaultexplorerserver defaultrestapiserver

SetActivatingModules can specify activating modules without viperjacket. But this way is needed re-compile for changing module. The list of activating modules is used as key of injectors to call.

func NewWithServiceName added in v1.1.0

func NewWithServiceName(serviceName string) *WireJacket

NewWithServiceName creates empty WireJacket. Make sure the serviceName is unique in same system. By default, WireJacket reads list of module name to activate in 'modules' value of viperjacket.

But Wire-Jacket considered The Twelve Factors. Config can be overrided by envrionment variable.(see viperjacket.go) So, when using more than one WireJacket on the same system, each WireJacket should have a unique service name to avoid conflicting value of 'modules'.

If serviceName exists, WireJacket reads value of '{serviceName}_modules' in viperjacket. the spaces of serviceName will be changed to '_'. serviceName will be used in config, we don't recommend it to contain space.

By default, WireJacket reads app.conf. Or you can specify file with '--config' flag.(see viperjacket.go)

modules example in app.conf with serviceName(ossicones)

ossicones_modules=mysql ossiconesblockchain defaultexplorerserver defaultrestapiserver

SetActivatingModules can specify activating modules without viperjacket. But this way is needed re-compile for changing module. The list of activating modules is used as key of injectors to call.

func (*WireJacket) AddEagerInjector

func (wj *WireJacket) AddEagerInjector(moduleName string, injector interface{})

AddInjector adds injector function to the eager injection list.

func (*WireJacket) AddInjector

func (wj *WireJacket) AddInjector(moduleName string, injector interface{})

AddInjector adds injector function to the lazy injection list.

func (*WireJacket) Close

func (wj *WireJacket) Close() error

Close closes all the modules gracefully

func (*WireJacket) DoWire

func (wj *WireJacket) DoWire() error

DoWire does wiring of wires(injectors). It calls eagerInjectors as finding(if no exists, loading) and injecting dependencies.

func (*WireJacket) GetModule

func (wj *WireJacket) GetModule(moduleName string) interface{}

GetModule finds module using moduleName and returns module if exists. If no exists, it tries to create module using injector and returns.

func (*WireJacket) GetModuleByType

func (wj *WireJacket) GetModuleByType(interfaceType interface{}) interface{}

GetModuleByType finds module using interfaceType(pointer of interface) and returns module if exists.

Example (process_name=ossicones) :

config := wj.GetModuleByType((*viperjacket.Config)(nil))

If no exists, it tries to create module using injector and returns. This may return undesirable results if there are other implementations that may use the same interface. Use only if you are sure that there are no overlapping interfaces.

func (*WireJacket) SetActivatingModules

func (wj *WireJacket) SetActivatingModules(moduleNames []string)

SetActivatingModules sets list of module's name. module's name is used as key of injector maps. It overwrites list of modules to activate.

func (*WireJacket) SetEagerInjectors

func (wj *WireJacket) SetEagerInjectors(injectors map[string]interface{}) *WireJacket

SetEagerInjectors sets injectors to inject eagerly. WireJacket maps module_name to injector as a key-value pairs. WireJacket tries to find module_name. if serviceName no exists, value of 'modules' in viperjacket. if serviceName exists, value of '{serviceName}_modules' in viperjacket.

modules example of app.conf (serviceName=ossicones) :

ossicones_modules=mysql ossiconesblockchain defaultexplorerserver defaultrestapiserver

definition in wire.go

func InjectMySQL(config viperjacket.Config) (database.Database, error) { ... } func InjectOssiconesBlockchain(config viperjacket.Config, database.Database) (blockchain.Blockchain, error) { ... } func InjectDefaultExplorerServer(config viperjacket.Config, blockchain blockchain.Blockchain) (explorerserver.ExplorerServer, error) { ...} func InjectDefaultRESTAPIServer(config viperjacket.Config, blockchain blockchain.Blockchain) (restapiserver.RESTAPIServer, error) { ...}

injectors can be like this.

var injectors = map[string]interface{}{
		"mysql": 				InjectMySQL,
		"ossiconesblockchain": 	InjectOssiconesBlockchain,
}

eagerInjectors can be like this.

var eagerInjectors = map[string]interface{}{
		"defaultexplorerserver": 	InjectDefaultExplorerServer,
		"defaultrestapiserver": 	InjectDefaultRESTAPIServer,
}

injectors will be injected eagerly.

func (*WireJacket) SetInjectors

func (wj *WireJacket) SetInjectors(injectors map[string]interface{}) *WireJacket

SetInjectors sets injectors to inject lazily. WireJacket maps module_name to injector as a key-value pairs. WireJacket tries to find module_name. if serviceName no exists, value of 'modules' in viperjacket. if serviceName exists, value of '{serviceName}_modules' in viperjacket.

modules example of app.conf (serviceName=ossicones) :

ossicones_modules=mysql ossiconesblockchain defaultexplorerserver defaultrestapiserver

definition in wire.go

func InjectMySQL(config viperjacket.Config) (database.Database, error) { ... } func InjectOssiconesBlockchain(config viperjacket.Config, database.Database) (blockchain.Blockchain, error) { ... } func InjectDefaultExplorerServer(config viperjacket.Config, blockchain blockchain.Blockchain) (explorerserver.ExplorerServer, error) { ...} func InjectDefaultRESTAPIServer(config viperjacket.Config, blockchain blockchain.Blockchain) (restapiserver.RESTAPIServer, error) { ...}

injectors can be like this.

var injectors = map[string]interface{}{
		"mysql": 				InjectMySQL,
		"ossiconesblockchain": 	InjectOssiconesBlockchain,
}

eagerInjectors can be like this.

var eagerInjectors = map[string]interface{}{
		"defaultexplorerserver": 	InjectDefaultExplorerServer,
		"defaultrestapiserver": 	InjectDefaultRESTAPIServer,
}

injectors will be injected lazily.

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

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