scaffolder

package module
v0.0.0-...-29ecac1 Latest Latest
Warning

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

Go to latest
Published: Sep 2, 2019 License: Apache-2.0 Imports: 2 Imported by: 6

README

Scaffolder experimentalGoDoc

Scaffolder framework provide generic building blocks to develop Golang applications.

Why Scaffolder ?

Golang is a simple and expressive language, making it easy to build new applications from scratch without the need to use opinionated third party libraries.

Because of those advantages, the structure of most of the Golang applications has been delegated directly to the developers. While the freedom of defining the application structure is enjoyable, it quickly becomes a nightmare to move from one project to another when every projects are structurally unique.

Worse, because Golang does not support generic, many application tends to re-implements the same minor features over and over again. While it is fun to implements your first database connector, it becomes quite monotonous to do it again just because your new application use YAML for the configuration instead of JSON like the previous one.

How does Scaffolder address those problems ?

While most of the traditional application framework would define every processes through your application, being opinionated and making assumption on your application design. Scaffolder focus on defining a referential component which can later be used or composed to build larger components, similar to how a meter can be used to build a wood plank which itself can be used to build a house or a boat.

The advantage with such architecture is that, although most of the components have been designed to be used and wired through the Application package, they do perform the same way without it and can freely be imported into any other project.

Documentation

Overview

Package scaffolder is a dependency injection framework dedicated into abstracting the redundant boilerplate needed to build any Golang application and letting you build what you want to build without having to rewrite yet another /health endpoint.

Example
package main

import (
	"context"
	"log"
	"math/rand"
	"net/http"
	"time"

	"github.com/Vorian-Atreides/scaffolder"
	"github.com/Vorian-Atreides/scaffolder/application"
	"github.com/Vorian-Atreides/scaffolder/component/healthcheck"
)

type SomeComponent struct {
	HealthRegistry healthcheck.HealthRegistry
}

func (s *SomeComponent) Start(ctx context.Context) error {
	for {
		i := rand.Intn(4)
		select {
		case <-ctx.Done():
			return nil
		case <-time.After(time.Duration(i) * time.Second):
		}
		a.HealthRegistry.SetStatus("SomeComponent", healthcheck.Status(i))
	}
	return nil
}

type Server struct {
	Readiness *healthcheck.HTTPHandler `scaffolder:"readiness"`
	Liveness  *healthcheck.HTTPHandler `scaffolder:"liveness"`
}

func (s *Server) Start(ctx context.Context) error {
	mux := http.DefaultServeMux
	mux.Handle("/ready", s.Readiness)
	mux.Handle("/healthy", s.Liveness)
	server := http.Server{
		Addr:    ":8080",
		Handler: mux,
	}

	server.ListenAndServe()
	return nil
}

func main() {
	app, err := application.New(
		application.WithComponent(healthcheck.NewRegistry()),
		application.WithComponent(&SomeComponent{}),
		application.WithComponent(
			&healthcheck.HTTPHandler{},
			scaffolder.WithName("readiness"),
		),
		application.WithComponent(
			&healthcheck.HTTPHandler{},
			healthcheck.WithMergingStrategy(
				healthcheck.EveryService(healthcheck.Healthy, healthcheck.NotReady),
			),
			scaffolder.WithName("liveness"),
		),
		application.WithComponent(&Server{}),
	)
	if err != nil {
		log.Fatal(err)
	}

	if err := app.Run(context.Background()); err != nil {
		log.Fatal(err)
	}
}
Output:

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrInvalidTarget is returned if the target given in the Init function is not a pointer.
	ErrInvalidTarget = errors.New("the target must be a pointer")
	// ErrInvalidOption is returned if the given Option does not respect the prototype:
	// func(pointer) error.
	ErrInvalidOption = errors.New("the option does not respect the mandatory prototype")
)
View Source
var (
	// ErrInvalidComponent means that the given component was neither a pointer not an interface.
	ErrInvalidComponent = errors.New("component is neither a pointer nor an interface")
)

Functions

func Configure

func Configure(target Component, cfg Configuration) error

Configure apply the Options returned by the Configuration.

type Config struct {
	FirstName string `json:"first_name"`
	Age       int    `json:"age"`
}

func (c *Config) Options() []scaffolder.Option {
	return []scaffolder.Option{
		WithAge(c.Age),
	}
}

func Init

func Init(target Component, opts ...Option) error

Init will take care of initializing the given Component by first calling the default method, if the component implements the Defaulter interface.

Afterward, it will iterate through the list of options and apply them one after another.

type Form struct {
	Age       int
	FirstName string
}

func (f *Form) Default() {
	f.FirstName = "FirstName"
}

func WithAge(age int) scaffolder.Option {
	return func(f *Form) error {
		f.Age = age
		return nil
	}
}

Types

type Component

type Component interface{}

Component let you define your application into small, independent, reusable element. Think of a component as a service or aggregate of structure and logic that you want to share with the rest of your code base or with other application.

Not everything is intended to be a component, but anything which would benefits from the scaffolder dependency injection, configuration or application life cycle should be thought as a component.

type Configuration

type Configuration interface {
	Options() []Option
}

Configuration define a generic interface to turn configuration structure into usable component in the Scaffolder framework.

type Container

type Container interface {
	Name() string
	Component() Component
}

Container extends the Component interface with meta data.

type Defaulter

type Defaulter interface {
	Default()
}

Defaulter optional interface which can be used to attach default values to a component.

type Inventory

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

Inventory define a registry of component where any component can resolve its dependencies. You can think of it as a bag of tools where the hammer will automatically goes next to the nails.

Currently the assignment algorithm follow this priority:

  1. The component name match the field tag.
  2. The component name and type match the field name and type (case sensitive).
  3. The component type match the field type.
  4. Component implements the field interface.

A valid assignable field must be a public and can be either a pointer, an interface or a slice. At the moment the slice is an experiments to build higher level components over the whole set of components.

func New

func New() *Inventory

New build a new Inventory.

func (*Inventory) Add

func (i *Inventory) Add(component Component, opts ...Option) *Inventory

Add a component to the inventory, it will take care of calling Init with the given options. You could use the WithName Option if you intend to assign the component to matching structure tags.

Any error while initializing a component will be returned in the Compile method.

func (*Inventory) Compile

func (i *Inventory) Compile() error

Compile will attempt to link the components together.

type Option

type Option interface{}

Option are generic functor used to configure a component, it is intended to be used to set one field at a time.

  func(f *Form) error {
  	  f.Age = age
	  return nil
  }

the function should always respect the prototype: func(pointer) error

func WithName

func WithName(name string) Option

WithName attach a name to a component, it is useful to give a name to a component if you intend to manually configure the dependency injection with the structure tags.

Directories

Path Synopsis
Package application define a simple application life cycle which should be robust enough for most of the application or be extended by your own custom implementation if it does not.
Package application define a simple application life cycle which should be robust enough for most of the application or be extended by your own custom implementation if it does not.
component
logger
Package logger define a Logger component which can be used to wrap the logger in your application.
Package logger define a Logger component which can be used to wrap the logger in your application.
examples
app

Jump to

Keyboard shortcuts

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