constructor

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Oct 20, 2019 License: Apache-2.0 Imports: 4 Imported by: 0

README

constructor - Smart factories for Go types.

codecov Build Status GoDoc

Quick Start

package main

import (
    "context"
    "fmt"
    "database/sql"
    "database/sql/driver"

	_ "github.com/lib/pq"

    "github.com/stackopsd/config"
    "github.com/stackopsd/constructor"
)

// Define the constructor options as a struct with standard Go types.
type Configuration struct {
    Username string
    Password string
    Host string
    Database string
}

// Define the constructor using a "generic" pattern.
type Constructor struct {}

// Settings() returns the configuration struct with any default values set.
func (c *Constructor) Settings() *Configuration {
    return &Configuration{
        Username: "defaultuser", // Optionally set default values.
        Password: "defaultpwd",
        Host: "localhost",
        Database: "myDB",
    }
}

// New always takes a context and the output of Settings(). It always returns
// the type being constructed and an error.
func (c *Constructor) New(ctx context.Context, conf *Configuration) (driver.Conn, error) {
    connStr := fmt.Sprintf("postgres://%s:%s@%s/%s", conf.Username, conf.Password, conf.Host, conf.Database)
    return sql.Open("postgres", connStr)
}

func main() {

    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    // Create an instance of the constructor and a pointer to the constructed type.
    var cons Constructor
    db := new(driver.Conn)

    // Create a loader from a source of configuration data.
    loader := config.NewFileLoader("/path/to/some/config.json_or_yaml")

    if err := constructor.New(ctx, loader, &cons, db); err != nil {
        panic(err)
    }

    // Use the constructed asset.
    _, _ = db.Query("SELECT 1")
}

The configuration file loaded would end up looking something like:

# An example YAML file that would work with the above.
username: "myUser"
password: "${DB_PWD}" # ENV interpolation is done automatically
host: "localhost"
database: "myDB"

Configuration Structs And Loading

All handling of the configuration structs and the binding of configuration values to the struct is managed by the config project. The README there goes into detail for how configuration works.

The Constructor Protocol

The constructor protocol would have been defined as a GO interface if Go supported generics. Until then, the interface exists only as documentation and reflection is used to validate and interact with implementations of the protocol.

A Constructor implementation is roughly equivalent to the factory pattern. Each instance must expose two methods: Settings() and New(). The Settings() method must return a non-nil pointer to a struct that represents all of the configuration or input parameters required in order to construct an instance of the arbitrary type that the constructor intends to produce. Any values set in the returned struct will be treated as default values should a Loader be unable to find a user provided configuration for that field.

The output of Settings() must be the same type accepted as the second parameter of the New() method with the first parameter being a context.Context. The New() method must also return two values with the first being the type of the constructed value and the second being an error. For example, a pseudo-code interface might be:

type Constructor inteface{
    Settings() *C
    New(ctx context.Context, conf *C) (*T, error)
}

An example implementation of this might look like:

type MyConstructable struct {}

type MyConfig struct{}
type MyConstructor struct {}
func(*MyConstructor) Settings() *MyConfig {
    return &MyConfig{}
}
func(*MyConstructor) New(ctx context.Context, conf *MyConfig) (*MyConstructable, error) {
    return &MyConstructable{}, nil
}

This overall design is chosen maximize the decoupling of the configuration loading system from the constructors. Generally, all implementations of the constructor protocol are valid Go structs that in no way reference a specific system for loading. Each constructor can be used in code without importing the config project. Should you choose to migrate away from config and constructor then all of your core code remains valid and does not need to change.

License

Copyright 2019 Kevin Conway

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func New

func New(ctx context.Context, src config.Loader, cons interface{}, dest interface{}) error

New is the primary entry point for using a constructor with a config.Loader. The given context and config.Loader are used for all lookups of configuration data. The given constructor must be a valid implementation of the Constructor protocol. The destination parameter must be assignable to the output of the constructor such as the output of new(T).

r := new(Result)
err := New(context.Background(), source, &Constructor{}, r)

If the resulting error is nil then the destination value, r in this case, now points to the output of the Constructor.New method. The method returns an error any time the given constructor does not satisfy the contract, any time the configuration loading fails, or if the Constructor.New returns an error.

func Verify

func Verify(v interface{}) error

Verify checks if a given value implements the Constructor contract.

Types

type Constructor

type Constructor interface{}

Constructor is an empty interface meant only as a placeholder for the constructor protocol documentation.

The constructor protocol would have been defined here as an interface if Go supported generics. Until then, the interface exists only as documentation and reflection is used to validate and interact with implementations of the protocol.

A Constructor implementation is roughly equivalent to the Factory pattern. Each instance must expose two methods: Settings() and New(). The Settings() method must return a non-nil pointer to a struct that represents all of the configuration or input parameters required in order to construct an instance of the arbitrary type that the constructor intends to produce. Any values set in the returned struct will be treated as default values should a Loader be unable to find a user provided configuration for that field.

The output of Settings() must be the same type accepted as the second parameter of the New() method with the first parameter being a context.Context. The New() method must also return two values with the first being the type of the constructed value and the second being an error. For example, a pseudo-code interface might be:

type Constructor inteface{
	Settings() *C
	New(ctx context.Context, conf *C) (*T, error)
}

An example implementation of this might look like:

type MyConstructable struct {}

type MyConfig struct{}
type MyConstructor struct {}
func(*MyConstructor) Settings() *MyConfig {
	return &MyConfig{}
}
func(*MyConstructor) New(ctx context.Context, conf *MyConfig) (*MyConstructable, error) {
	return &MyConstructable{}, nil
}

This overall design is chosen maximize the decoupling of the configuration loading system from the constructors. Generally, all implementations of the constructor protocol are valid Go structs that in no way reference a specific system for loading. Each constructor can be used in code without importing the config project.

Jump to

Keyboard shortcuts

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