app

package
v0.5.5 Latest Latest
Warning

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

Go to latest
Published: Nov 11, 2024 License: Apache-2.0 Imports: 12 Imported by: 1

README

Application Container

The ApplicationContainer handles the app life cycle letting you provide dependencies applying the control of inversion principle.

The dependencies have access to the application life cycle at:

  • Start: Once that all dependencies are registered, the app is executed by .Run() method, here is when the app is set to start status and all dependencies OnStart hooks are executed.
  • Stop: This is the last app status, when a signal termination is received the dependencies OnStop hooks are executed.

Please check the example application.

Provider

The provider structs are where the instances to provide are created and wired. Each provider have two different fields annotated with Go tags in order to define its functionality. The tags could be:

torpedo.di:"provide"

The field annotated with this tag is marked as provider to be bound in other provider which requires its instance. Your provider should implement the method Provide() where all fields tagged as provide MUST be initialized.

torpedo.di:"bind"

The field annotated with bind tag will have injected automatically the provided instance of its data type.

When you need to provide more than one instance of the same data type the provide tag could contain an attribute name.

type LoggerProvider struct {
	app.BaseProvider

	// instance to be provided
	defaultInstance *slog.Logger `torpedo.di:"provide"`
	instance        *slog.Logger `torpedo.di:"provide,name=LOGGER"`

}

The providers must implement the interface IProvider but for simplicity can extend the base struct named as app.BaseProvider which has implemented all the required methods as no operation mode. So, the developer only needs to overwrite at least the Provide() method.

Provide Singleton vs Builder function

The previous LoggerProvider example illustrates how to create a Singleton instance of slog.Logger. So, each Provider that contains a bind of *slog.Logger will have injected the same logger instance, which for a logger could be ok, but what happens if we would like to inject different instances per bound dependency. Here is when the Builder function is useful. This function will be called in order to build a new instance of the required object. The previous example can be modified as:

type LoggerProvider struct {
	app.BaseProvider

	// singletonInstance is a pointer to a singleton instance of slog.Logger
	singletonInstance *slog.Logger `torpedo.di:"provide"`
	
	// builderInstance Instance builder function that returns a pointer to a new instance of slog.Logger 
	builderInstance func(c app.IContainer) *slog.Logger `torpedo.di:"provide,name=LOGGER"`

}

// Provide this method sets the declared providers.
func (p *LoggerProvider) Provide(c app.IContainer) error {
	
    p.singletonInstance = slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}))

    p.builderInstance = func(c app.IContainer) *slog.Logger {
	    return slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))	
    }
	
    return nil
}

For instance a good example could be a HTTP Server:

package dependency

import (
	"github.com/darksubmarine/torpedo-lib-go/app"
	"github.com/darksubmarine/torpedo-lib-go/conf"
	"log/slog"

	"fmt"
	"io"
	"net/http"
)

type HttpServerProvider struct {
	app.BaseProvider
	cfg conf.Map

	// instance to be provided
	mux *http.ServeMux `torpedo.di:"provide,name=HTTPSERVER"`
}

func NewHttpServerProvider(config conf.Map) *HttpServerProvider {
	return &HttpServerProvider{cfg: config}
}

func (p *HttpServerProvider) Provide(c app.IContainer) error {
	p.mux = http.NewServeMux()
	p.mux.HandleFunc("/ping", func(writer http.ResponseWriter, request *http.Request) {
		io.WriteString(writer, "pong!")
	})

	return nil
}

func (p *HttpServerProvider) OnStart() func() error {
	return func() error {
		go func() {
			port := p.cfg.FetchIntP("port")
			if err := http.ListenAndServe(fmt.Sprintf(":%d", port), p.mux); err != nil {
				panic(fmt.Sprintf("error starting HTTP server at %d with error %s", port, err))
			}
		}()

		return nil
	}
}

Once that we have the HTTP server provider we could create new provider to add an endpoint:

package dependency

import (
	"github.com/darksubmarine/torpedo-lib-go/app"
	"io"

	"net/http"
)

type HelloProvider struct {
	app.BaseProvider
	
	mux    *http.ServeMux `torpedo.di:"bind,name=HTTPSERVER"`
}

func NewHelloProvider() *HelloProvider { return new(HelloProvider) }

func (p *HelloProvider) Provide(c app.IContainer) error {
	p.mux.HandleFunc("/hello", p.sayHello)

	// in this case we won't be providing anything...
	// only using the Provide method to inject a hello controller
	// as part of the mux server
	return nil
}

func (p *HelloProvider) sayHello(w http.ResponseWriter, r *http.Request) {
	io.WriteString(w, "Hello, HTTP!\n")
}

Running the application container

The main application must be initialized with an instance of IApp like:

package main

import (
	"github.com/darksubmarine/torpedo-lib-go/app"
	"github.com/darksubmarine/torpedo-lib-go/conf"
	"log/slog"
	"os"
)

func main() {

	// 1. App configuration
	config := conf.Map{}
	config.Add(3000, "port")

	// 2. Depdencies
	opts := app.ContainerOpts{Log: app.ContainerLogsOpts{W: os.Stdout, L: slog.LevelInfo}}

	application := app.NewApplicationContainer(opts)
	application.WithProvider(dependency.NewHttpServerProvider(config))
	application.WithProvider(dependency.NewHelloProvider())
	
	// 3. Run your application!
	application.Run()
}

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrDependencyNotProvided dependency has not been provided
	ErrDependencyNotProvided = errors.New("dependency has not been provided")

	// ErrNilDependency the provided dependency cannot be nil
	ErrNilDependency = errors.New("the provided dependency cannot be nil")

	// ErrDependencyAlreadyProvided dependency already provided
	ErrDependencyAlreadyProvided = errors.New("dependency already provided")
)

Functions

This section is empty.

Types

type ApplicationContainer

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

ApplicationContainer implementation of the IApp interface. Handles the app life cycle and the main dependency container.

func NewApplicationContainer added in v0.3.7

func NewApplicationContainer(opts ContainerOpts) *ApplicationContainer

NewApplicationContainer app container constructor

func NewContainer

func NewContainer(opts ContainerOpts) *ApplicationContainer

NewContainer app container constructor

func (*ApplicationContainer) Invoke

func (c *ApplicationContainer) Invoke(name string) (interface{}, error)

Invoke fetch a named dependency from the container or error if not exists.

func (*ApplicationContainer) InvokeByType

func (c *ApplicationContainer) InvokeByType(obj interface{}) (interface{}, error)

InvokeByType fetch an object registered by type or error if not exists.

func (*ApplicationContainer) InvokeByTypeP

func (c *ApplicationContainer) InvokeByTypeP(obj interface{}) interface{}

InvokeByTypeP fetch an object registered by type or panic if not exists.

func (*ApplicationContainer) InvokeP

func (c *ApplicationContainer) InvokeP(name string) interface{}

InvokeP fetch a named dependency from the container and panic if not exists.

func (*ApplicationContainer) Logger

func (c *ApplicationContainer) Logger() *slog.Logger

Logger returns the container logger useful to log something into your providers

func (*ApplicationContainer) Register

func (c *ApplicationContainer) Register(name string, obj interface{}) error

Register exposed method to register object by name without linked hooks

func (*ApplicationContainer) Run

func (c *ApplicationContainer) Run()

Run app method to starts the application life cycle

func (*ApplicationContainer) WithProvider

func (c *ApplicationContainer) WithProvider(provider IProvider) *ApplicationContainer

WithProvider provides dependencies

type BaseProvider

type BaseProvider struct{}

BaseProvider no operation provider to create your own provider

func (*BaseProvider) OnStart

func (p *BaseProvider) OnStart() func() error

OnStart returns a hook function to be executed at start step

func (*BaseProvider) OnStop

func (p *BaseProvider) OnStop() func() error

OnStop returns a hook function to be executed at stop step

type ContainerLogsOpts

type ContainerLogsOpts struct {
	W io.Writer
	L slog.Leveler
}

ContainerLogsOpts struct to configure the container log

type ContainerOpts

type ContainerOpts struct {
	Log ContainerLogsOpts
}

ContainerOpts application container options

type DependencyGraph added in v0.5.0

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

DependencyGraph graph to validate injected dependencies

func NewDependencyGraph added in v0.5.0

func NewDependencyGraph() *DependencyGraph

NewDependencyGraph constructor of DependencyGraph

func (*DependencyGraph) AddEdge added in v0.5.0

func (g *DependencyGraph) AddEdge(v, w string) error

AddEdge adds edge from v to w

func (*DependencyGraph) AddVertex added in v0.5.0

func (g *DependencyGraph) AddVertex(v string)

AddVertex adds a vertex to the graph

func (*DependencyGraph) IsCyclic added in v0.5.0

func (g *DependencyGraph) IsCyclic() bool

IsCyclic validates if the constructed graph has at least one cycle built on top of DFS topological sort.

func (*DependencyGraph) TopologicalSort added in v0.5.0

func (g *DependencyGraph) TopologicalSort() ([]string, error)

TopologicalSort by default calls a DFS based algorithm

func (*DependencyGraph) TopologicalSortDFS added in v0.5.0

func (g *DependencyGraph) TopologicalSortDFS() ([]string, error)

TopologicalSortDFS based on Depth-first search

func (*DependencyGraph) TopologicalSortKahn added in v0.5.0

func (g *DependencyGraph) TopologicalSortKahn() ([]string, error)

TopologicalSortKahn based on Kahn's algorithm

IMPORTANT:

This implementation removes edges from original graph, so, once that has been executed the original
graph will be modified.

TODO: clone graph before execution.

type IApp

type IApp interface {
	IContainer
	Run()
	// contains filtered or unexported methods
}

IApp application interface

type IContainer

type IContainer interface {
	IContainerMonitor
	Register(name string, obj interface{}) error
	Invoke(name string) (interface{}, error)
	InvokeP(name string) interface{}
	InvokeByType(obj interface{}) (interface{}, error)
	InvokeByTypeP(obj interface{}) interface{}
}

IContainer exposed interface that defines the container methods

type IContainerMonitor

type IContainerMonitor interface {
	Logger() *slog.Logger
}

IContainerMonitor interface that defines the container monitoring capabilities

type IProvider

type IProvider interface {
	Provide(c IContainer) error
	OnStart() func() error
	OnStop() func() error
}

IProvider dependency provider interface that cover the app life-cycle events

type RegisteredProvider added in v0.5.0

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

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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