core

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Feb 22, 2021 License: MIT Imports: 28 Imported by: 2

README

Package core is a service container that elegantly bootstrap and coordinate twelve-factor apps in Go.

Build Go Reference codecov Go Report Card Sourcegraph

Background

The twelve-factor methodology has proven its worth over the years. Since its invention many fields in technology have changed, many among them are shining and exciting. In the age of Kubernetes, service mesh and serverless architectures, the twelve-factor methodology has not faded away, but rather has happened to be a good fit for nearly all of those powerful platforms.

Scaffolding a twelve-factor go app may not be a difficult task for experienced engineers, but certainly presents some challenges to juniors. For those who are capable of setting things up, there are still many decisions left to make, and choices to be agreed upon within the team.

Package core was created to bootstrap and coordinate such services.

Overview

Whatever the app is, the bootstrapping phase are roughly composed by:

  • Read configuration from out of the binary. Namely, flags, environmental variables, and/or configuration files.

  • Initialize dependencies. Databases, message queues, service discoveries, etc.

  • Define how to run the app. HTTP, RPC, command-lines, cron jobs, or more often mixed.

Package core abstracts those repeated steps, keeping them concise, portable yet explicit. Let's see the following snippet:

package main

import (
  "context"
  "net/http"

  "github.com/DoNewsCode/core"
  "github.com/DoNewsCode/core/observability"
  "github.com/DoNewsCode/core/otgorm"
  "github.com/gorilla/mux"
)

func main() {
  // Phase One: create a core from a configuration file
  c := core.New(core.WithYamlFile("config.yaml"))

  // Phase two: bind dependencies
  c.Provide(otgorm.Provide)

  // Phase three: define service
  c.AddModule(core.HttpFunc(func(router *mux.Router) {
    router.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
      writer.Write([]byte("hello world"))
    })
  }))

  // Phase Four: run!
  c.Serve(context.Background())
}

In a few lines, an http service is bootstrapped in the style outlined above. It is simple, explicit and to some extent, declarative.

The service demonstrated above uses an inline handler function to highlight the point. Normally, for real projects, we will use modules instead. The "module" in package core's glossary is not necessarily a go module (though it can be). It is simply a group of services.

You may note that the http service doesn't really consume the dependency. That's true.

Let's rewrite the http service to consume the above dependencies.

package main

import (
  "context"
  "net/http"

  "github.com/DoNewsCode/core"
  "github.com/DoNewsCode/core/otgorm"
  "github.com/DoNewsCode/core/srvhttp"
  "github.com/gorilla/mux"
  "gorm.io/gorm"
)

type User struct {
  Id   string
  Name string
}

type Repository struct {
  DB *gorm.DB
}

func (r Repository) Find(id string) (*User, error) {
  var user User
  if err := r.DB.First(&user, id).Error; err != nil {
    return nil, err
  }
  return &user, nil
}

type Handler struct {
  R Repository
}

func (h Handler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
  encoder := srvhttp.NewResponseEncoder(writer)
  encoder.Encode(h.R.Find(request.URL.Query().Get("id")))
}

type Module struct {
  H Handler
}

func New(db *gorm.DB) Module {
  return Module{Handler{Repository{db}}}
}

func (m Module) ProvideHttp(router *mux.Router) {
  router.Handle("/", m.H)
}

func main() {
  // Phase One: create a core from a configuration file
  c := core.New(core.WithYamlFile("config.yaml"))

  // Phase two: bind dependencies
  c.Provide(otgorm.Provide)

  // Phase three: define service
  c.AddModuleFunc(New)

  // Phase four: run!
  c.Serve(context.Background())
}

Phase three has been replaced by the c.AddModuleFunc(New). AddModuleFunc populates the arguments to New from dependency containers and add the returned module instance to the internal module registry.

Now we have a fully workable project, with layers of handler, repository and entity. Had this been a DDD workshop, we would be expanding the example even further.

That being said, let's redirect our attention to other goodies package core has offered:

  • Package core is natively support multiplexing modules. You could start you project as a monolith with multiple modules, and gradually migrate them into microservices.

  • Package core doesn't lock in transport or framework. For instance, you can use go kit to construct your service, and leveraging grpc, ampq, thrift, etc. Non network services like CLI and Cron are also supported.

  • Sub packages provide support around service coordination, including but not limited to distributed tracing, metrics exporting, error handling, event-dispatching and leader election.

Documentation

GoDoc is the primary source of documentation. Definitely check out the sub packages too.

github.com/DoNewsCode/skeleton is working demo featuring package core with go-kit and gin.

Design Principles

  • No package global state.
  • Promote dependency injection.
  • Testable code.
  • Minimalist interface design. Easy to decorate and replace.
  • Tries to work with the go ecosystem rather than reinventing the wheel.
  • End to end Context passing.

Non-Goals

  • Tries to be a Laravel or Ruby on Rails.
  • Tries to care about service details.

Suggested service framework

  • Gin (if http only)
  • Go Kit (if multiple transport)
  • Go Zero

Documentation

Overview

Package core is a service container that elegantly bootstrap and coordinate twelve-factor apps in Go.

Checkout the README.md for an overview of this package.

Example (Minimal)
package main

import (
	"context"
	"fmt"
	"io/ioutil"
	"net/http"

	"github.com/DoNewsCode/core"
	"github.com/gorilla/mux"
)

func main() {

	// Spin up a real server
	c := core.Default(core.WithInline("log.level", "none"))
	c.AddModule(core.HttpFunc(func(router *mux.Router) {
		router.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
			writer.Write([]byte("hello world"))
		})
	}))
	ctx, cancel := context.WithCancel(context.Background())
	go c.Serve(ctx)

	// Let's try if the server works.
	resp, _ := http.Get("http://localhost:8080/")
	bytes, _ := ioutil.ReadAll(resp.Body)
	cancel()

	fmt.Println(string(bytes))
}
Output:

hello world

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewServeModule

func NewServeModule(in serveIn) serveModule

func ProvideAppName

func ProvideAppName(conf contract.ConfigAccessor) contract.AppName

ProvideAppName is the default AppNameProvider for package Core.

func ProvideConfig

func ProvideConfig(configStack []config.ProviderSet, configWatcher contract.ConfigWatcher) contract.ConfigAccessor

ProvideConfig is the default ConfigProvider for package Core.

func ProvideEnv

func ProvideEnv(conf contract.ConfigAccessor) contract.Env

ProvideEnv is the default EnvProvider for package Core.

func ProvideEventDispatcher

func ProvideEventDispatcher(conf contract.ConfigAccessor) contract.Dispatcher

ProvideEventDispatcher is the default EventDispatcherProvider for package Core.

func ProvideLogger

func ProvideLogger(conf contract.ConfigAccessor, appName contract.AppName, env contract.Env) log.Logger

ProvideLogger is the default LoggerProvider for package Core.

func WithYamlFile

func WithYamlFile(path string) (CoreOption, CoreOption)

WithYamlFile is a two-in-one coreOption. It uses the configuration file as the source of configuration, and watches the change of that file for hot reloading.

Types

type AppNameProvider

type AppNameProvider func(conf contract.ConfigAccessor) contract.AppName

AppNameProvider provides the contract.AppName to the core.

type C

type C struct {
	AppName contract.AppName
	Env     contract.Env
	contract.ConfigAccessor
	logging.LevelLogger
	contract.Container
	contract.Dispatcher
	// contains filtered or unexported fields
}

C stands for the core of the application. It contains service definitions and dependencies. C is mean to be used in the boostrap phase of the application. Do not pass C into services and use it as a service locator.

func Default

func Default(opts ...CoreOption) *C

Default creates a core.C under its default state. Core dependencies are already provided, and the config module and serve module are bundled.

func New

func New(opts ...CoreOption) *C

New creates a new bare-bones C.

func (*C) AddModule

func (c *C) AddModule(modules ...interface{})

AddModule adds one or more module(s) to the core. If any of the variadic arguments is an error, it would panic. This makes it easy to consume constructors directly, so instead of writing:

component, err := components.New()
if err != nil {
  panic(err)
}
c.AddModule(component)

You can write:

c.AddModule(component.New())

A Module is a group of functionality. It must provide some runnable stuff: http handlers, grpc handlers, cron jobs, one-time command, etc.

Example
package main

import (
	"fmt"

	"github.com/DoNewsCode/core"
)

func main() {
	type Foo struct{}
	c := core.New()
	c.AddModule(Foo{})
	fmt.Printf("%T\n", c.Modules()...)
}
Output:

core_test.Foo

func (*C) AddModuleFunc

func (c *C) AddModuleFunc(constructor interface{})

AddModuleFunc add the module after Invoking its' constructor. Clean up functions and errors are handled automatically.

Example
package main

import (
	"fmt"

	"github.com/DoNewsCode/core"
)

func main() {
	type Foo struct{}
	c := core.New()
	c.AddModuleFunc(func() (Foo, func(), error) {
		return Foo{}, func() {}, nil
	})
	fmt.Printf("%T\n", c.Modules()...)
}
Output:

core_test.Foo

func (*C) Invoke

func (c *C) Invoke(function interface{}) error

Invoke runs the given function after instantiating its dependencies. Any arguments that the function has are treated as its dependencies. The dependencies are instantiated in an unspecified order along with any dependencies that they might have. The function may return an error to indicate failure. The error will be returned to the caller as-is.

It internally calls uber's dig library. Consult dig's documentation for details. (https://pkg.go.dev/go.uber.org/dig)

func (*C) Provide

func (c *C) Provide(constructor interface{})

Provide adds a dependencies provider to the core. Note the dependency provider must be a function in the form of:

func(foo Foo) Bar

where foo is the upstream dependency and Bar is the provided type. The order for providers doesn't matter. They are only executed lazily when the Invoke is called.

This method internally calls uber's dig library. Consult dig's documentation for details. (https://pkg.go.dev/go.uber.org/dig)

The difference is, core.Provide has been made to accommodate the convention from google/wire (https://github.com/google/wire). All "func()" returned by constructor are treated as clean up functions. It also respect the core's unique "di.Module" annotation.

Example
package main

import (
	"fmt"

	"github.com/DoNewsCode/core"
)

func main() {
	type Foo struct {
		Value string
	}
	type Bar struct {
		foo Foo
	}
	c := core.New()
	c.Provide(func() (foo Foo, cleanup func(), err error) {
		return Foo{
			Value: "test",
		}, func() {}, nil
	})
	c.Provide(func(foo Foo) Bar {
		return Bar{foo: foo}
	})
	c.Invoke(func(bar Bar) {
		fmt.Println(bar.foo.Value)
	})
}
Output:

test

func (*C) ProvideEssentials

func (c *C) ProvideEssentials()

ProvideEssentials adds the default core dependencies to the core.

func (*C) Serve

func (c *C) Serve(ctx context.Context) error

Serve runs the serve command bundled in the core. For larger projects, consider use full-featured ServeModule instead of calling serve directly.

type ConfParser

type ConfParser interface {
	Unmarshal([]byte) (map[string]interface{}, error)
	Marshal(map[string]interface{}) ([]byte, error)
}

ConfParser models a parser for configuration. For example, yaml.Parser.

type ConfProvider

type ConfProvider interface {
	ReadBytes() ([]byte, error)
	Read() (map[string]interface{}, error)
}

ConfProvider models a configuration provider. For example, file.Provider.

type ConfigProvider

type ConfigProvider func(configStack []config.ProviderSet, configWatcher contract.ConfigWatcher) contract.ConfigAccessor

ConfigProvider provides contract.ConfigAccessor to the core.

type CoreOption

type CoreOption func(*coreValues)

CoreOption is the option to modify core attribute.

func SetAppNameProvider

func SetAppNameProvider(provider AppNameProvider) CoreOption

SetAppNameProvider is a CoreOption to replaces the default AppNameProvider.

func SetConfigProvider

func SetConfigProvider(provider ConfigProvider) CoreOption

SetConfigProvider is a CoreOption to replaces the default ConfigProvider.

func SetDiProvider

func SetDiProvider(provider DiProvider) CoreOption

SetDiProvider is a CoreOption to replaces the default DiContainer.

func SetEnvProvider

func SetEnvProvider(provider EnvProvider) CoreOption

SetEnvProvider is a CoreOption to replaces the default EnvProvider.

func SetEventDispatcherProvider

func SetEventDispatcherProvider(provider EventDispatcherProvider) CoreOption

SetEventDispatcherProvider is a CoreOption to replaces the default EventDispatcherProvider.

func SetLoggerProvider

func SetLoggerProvider(provider LoggerProvider) CoreOption

SetLoggerProvider is a CoreOption to replaces the default LoggerProvider.

func WithConfigStack

func WithConfigStack(provider ConfProvider, parser ConfParser) CoreOption

WithConfigStack is a CoreOption that defines a configuration layer. See package config for details.

func WithConfigWatcher

func WithConfigWatcher(watcher contract.ConfigWatcher) CoreOption

WithConfigWatcher is a CoreOption that adds a config watcher to the core (for hot reloading configs).

func WithInline

func WithInline(key, entry string) CoreOption

WithInline is a CoreOption that creates a inline config in the configuration stack.

type DiContainer

type DiContainer interface {
	Provide(constructor interface{}) error
	Invoke(function interface{}) error
}

DiContainer is a container roughly modeled after dig.Container

func ProvideDi

func ProvideDi(conf contract.ConfigAccessor) DiContainer

ProvideDi is the default DiProvider for package Core.

type DiProvider

type DiProvider func(conf contract.ConfigAccessor) DiContainer

DiProvider provides the DiContainer to the core.

type EnvProvider

type EnvProvider func(conf contract.ConfigAccessor) contract.Env

EnvProvider provides the contract.Env to the core.

type EventDispatcherProvider

type EventDispatcherProvider func(conf contract.ConfigAccessor) contract.Dispatcher

EventDispatcherProvider provides contract.Dispatcher to the core.

type HttpFunc

type HttpFunc func(router *mux.Router)

HttpFunc converts a function to a module provides http.

func (HttpFunc) ProvideHttp

func (h HttpFunc) ProvideHttp(router *mux.Router)

ProvideHttp implements container.HttpProvider

type LoggerProvider

type LoggerProvider func(conf contract.ConfigAccessor, appName contract.AppName, env contract.Env) log.Logger

EnvProvider provides the log.Logger to the core.

Directories

Path Synopsis
Package clihttp adds opentracing support to http client.
Package clihttp adds opentracing support to http client.
Package config provides supports to a fully customizable layered configuration stack.
Package config provides supports to a fully customizable layered configuration stack.
Package container includes the Container type, witch contains a collection of modules.
Package container includes the Container type, witch contains a collection of modules.
Package contract defines a set of common interfaces for all packages in this repository.
Package contract defines a set of common interfaces for all packages in this repository.
Package di is a thin wrapper around dig.
Package di is a thin wrapper around dig.
Package events provides a simple and effective implementation of event system.
Package events provides a simple and effective implementation of event system.
Package ginmw offers a collection of middleware to enforce cross service policies, such as observabilities and context passing.
Package ginmw offers a collection of middleware to enforce cross service policies, such as observabilities and context passing.
Package key provides a way to distribute labels to component.
Package key provides a way to distribute labels to component.
Package kitkafka provides a kafka transport for go kit.
Package kitkafka provides a kafka transport for go kit.
Package kitmw provides a collection fo useful go kit middlewares or options that inter-ops with other libraries for package core.
Package kitmw provides a collection fo useful go kit middlewares or options that inter-ops with other libraries for package core.
Package logging provides a kitlog compatible logger.
Package logging provides a kitlog compatible logger.
Package observability provides a tracer and a histogram to measure all incoming requests to the system.
Package observability provides a tracer and a histogram to measure all incoming requests to the system.
Package otgorm provides gorm with opentracing.
Package otgorm provides gorm with opentracing.
Package otmongo provides mongo client with opentracing.
Package otmongo provides mongo client with opentracing.
Package otredis provides redis client with opentracing.
Package otredis provides redis client with opentracing.
Package ots3 provides a s3 uploader with opentracing capabilities.
Package ots3 provides a s3 uploader with opentracing capabilities.
Package queue provides a persistent queue implementation that interplays with the Dispatcher in the contract package.
Package queue provides a persistent queue implementation that interplays with the Dispatcher in the contract package.
Package srvhttp groups helpers for server side http use cases.
Package srvhttp groups helpers for server side http use cases.
Package text provides utilities to generate textual output.
Package text provides utilities to generate textual output.
Package unierr presents an unification error model between gRPC transport and HTTP transport, and between server and client.
Package unierr presents an unification error model between gRPC transport and HTTP transport, and between server and client.

Jump to

Keyboard shortcuts

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