application

package module
v0.0.5 Latest Latest
Warning

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

Go to latest
Published: Jun 8, 2022 License: BSD-3-Clause Imports: 19 Imported by: 6

README

Modulus Framework

Modulus is a framework for the web development. It allows a developer to create a modular monolithic application.

A modular monolith is an approach where you build and deploy a single application, but you build application in a way that breaks up the code into independent modules for each of the features needed in your application.

Our way to create a modular monolith is having a core with the base interfaces and a system of adding as many configurable parts of code as necessary to it. Also, interaction between these parts should be organized via this core (framework).

Module structure

In fact, there is no strong module structure. You are free to implement a module in a way you want. Only one restriction to be a module is having a structure that implements ServiceProvider interface. It will be an entrypoint to your module. We propose to call it ModuleConfig, but actually it is not necessary.

ModuleConfig's ProvidedServices method should return all constructors of structures that you want to mark as visible for other parts of the application. All these constructors will be added to the Dependency Injection Container (DIC). We are using the Uber's container https://github.com/uber-go/dig as DIC in our project. For example:

//internal/my_module/service/registration.go
type Registration struct {
    logger application.Logger
}

func NewRegistration(logger application.Logger) *Registration {
    return &Registration{logger: logger}
}

//internal/my_module/service/config.go
type ModuleConfig struct {
}

func (s *ModuleConfig) ProvidedServices() []interface{} {
	return []interface{}{		
		service.NewRegistration,
	}
}

By the way, all dependencies in the constructor will be resolved automatically, if their constructors are returned from any ProvidedServices method in any module.

Getting dependencies inside config

Sometimes it is necessary to get some dependencies in module's entrypoint. In this case your ModuleConfig should implement ContainerHolder interface. For example:

type ModuleConfig struct {
	container *dig.Container
}

func (s *ModuleConfig) SetContainer(container *dig.Container) {
	s.container = container
}

func (s *ModuleConfig) ModuleRoutes() []application.RouteInfo {
	var moduleActions *ModuleActions
	err := s.container.Invoke(func(dep *ModuleActions) {
		moduleActions = dep
	})
	if err != nil {
		panic("cannot instantiate module dependencies" + err.Error())
	}
	return moduleActions.Routes()
}

Parameters injecting

All entrypoint of modules are configurations for modules, and can hold some values. We propose to set values from env variables. Also, it is nice to have description of these variables with default values in the .env.dist file in the root of a module.

Let your ModuleConfig implement the ConfigInitializer interface and fill all values there: For example:

type ModuleConfig struct {
	apiUrl          string
}

func (s *ModuleConfig) InitConfig(config application.Config) error {	
	if s.apiUrl == "" {
		s.apiUrl = config.GetEnv("MODULE_NAME_API_URL")
	}

	return nil
}

We propose to prefix your env variables with a module name to prevent names intersection of modules variables.

Routes description

If your module processes some http routes it is necessary to implement the HttpRoutesInitializer interface to return all supported routes. For example:

//internal/my_module/actions.go
type ModuleActions struct {
    routes *application.Routes
}

func NewModuleActions(
    registerAction *action2.RegisterAction,
) *ModuleActions {
    routes := application.NewRoutes()
    routes.Post(
        "/users",
        registerAction.Handle,
    )
    
    return &ModuleActions{
        routes: routes,
    }
}

func (a *ModuleActions) Routes() []application.RouteInfo {
    return a.routes.GetRoutesInfo()
}

//internal/my_module/config.go

func (s *ModuleConfig) ModuleRoutes() []application.RouteInfo {
	var moduleActions *ModuleActions
	err := s.container.Invoke(func(dep *ModuleActions) {
		moduleActions = dep
	})
	if err != nil {
		panic("cannot instantiate module dependencies" + err.Error())
	}
	return moduleActions.Routes()
}

Application lifecycle events

Any application has own lifecycle, divided to 5 steps: #Gather all dependencies from modules #Config initialization #Routes initialization #Running the application #Closing the application

Configuration module reactions on the first 3 steps has been described previously in the document. If you want to start for example a server in your application, and, for example, release some resources in the end of the application running, than implement interfaces StartApplicationListener and CloseApplicationListener For example:


func (s *ServiceProvider) OnStart() error {
	var router *Router
	err := s.container.Invoke(func(dep *Router) error {
		router = dep
		return nil
	})
	if err != nil {
		return err
	}

	return router.Run()
}

func (s *ServiceProvider) OnClose() error {
    var db *Db
    err := s.container.Invoke(func(dep *Db) error {
        router = dep
        return nil
    })
    if err != nil {
        return err
    }
	db.Close()
	return nil
}

Documentation

Index

Constants

View Source
const (
	TestEnv = "test"
	DevEnv  = "dev"
	ProdEnv = "prod"
)

Variables

This section is empty.

Functions

This section is empty.

Types

type Action

type Action interface {
	NewRequestObject() any
	Handle(ctx context.Context, request any) (any, error)
}

type ActionError added in v0.0.3

type ActionError struct {
	Ctx              context.Context
	Identifier       ErrorIdentifier
	Err              error
	ValidationErrors []ValidationError
}

func (*ActionError) Error added in v0.0.3

func (e *ActionError) Error() string

type ActionResponse added in v0.0.3

type ActionResponse struct {
	StatusCode      int
	Response        any
	Error           *ActionError
	IsLoggingErrors bool
}

func NewServerErrorResponse added in v0.0.4

func NewServerErrorResponse(ctx context.Context, identifier ErrorIdentifier, err error) ActionResponse

func NewSuccessCreationResponse added in v0.0.3

func NewSuccessCreationResponse(response any) ActionResponse

func NewSuccessResponse added in v0.0.3

func NewSuccessResponse(response any) ActionResponse

func NewUnprocessableEntityResponse added in v0.0.4

func NewUnprocessableEntityResponse(ctx context.Context, err error) ActionResponse

func NewValidationErrorResponse added in v0.0.4

func NewValidationErrorResponse(ctx context.Context, errors []ValidationError) ActionResponse

type ActionRunner

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

func NewActionRunner

func NewActionRunner(logger Logger, jsonWriter JsonResponseWriter, router Router) *ActionRunner

func (*ActionRunner) Run

func (j *ActionRunner) Run(
	w http.ResponseWriter,
	r *http.Request,
	action func(ctx context.Context, request any) ActionResponse,
	request any,
)

type Application

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

func New

func New(moduleConfigs []interface{}) *Application

func (*Application) Container

func (a *Application) Container() *dig.Container

func (*Application) Run

func (a *Application) Run() error

type CloseApplicationListener

type CloseApplicationListener interface {
	// OnClose may close some resources of a module, for example a db connection
	OnClose() error
}

CloseApplicationListener if service provider implements this method it will be called after stopping the application

type CommonError added in v0.0.4

type CommonError struct {
	Identifier ErrorIdentifier
	Err        string
}

func NewCommonError added in v0.0.4

func NewCommonError(identifier ErrorIdentifier, err string) *CommonError

func (*CommonError) Error added in v0.0.4

func (e *CommonError) Error() string

type Config

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

func NewConfig

func NewConfig() *Config

func (*Config) AppEnv

func (c *Config) AppEnv() string

func (*Config) AppEnvIsProd

func (c *Config) AppEnvIsProd() bool

func (*Config) GetEnv

func (c *Config) GetEnv(key string) string

func (*Config) GetEnvAsBool

func (c *Config) GetEnvAsBool(name string) bool

func (*Config) GetEnvAsInt

func (c *Config) GetEnvAsInt(name string) int

func (*Config) GetEnvAsSlice

func (c *Config) GetEnvAsSlice(name string, sep string) []string

func (*Config) ProvidedServices

func (c *Config) ProvidedServices() []interface{}

type ConfigInitializer

type ConfigInitializer interface {
	// InitConfig is called for each module to initialize module's variables
	InitConfig(config Config) error
}

ConfigInitializer if service provider implements this method it will be called after providing dependencies of the module

type ContainerHolder

type ContainerHolder interface {
	// SetContainer receives the dependency injection container from application
	SetContainer(*dig.Container)
}

ContainerHolder allows module config to have a link to the dependency injection container

type DefaultJsonResponseWriter added in v0.0.3

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

func (*DefaultJsonResponseWriter) Error added in v0.0.3

func (*DefaultJsonResponseWriter) Success added in v0.0.3

type DefaultLogger

type DefaultLogger struct {
}

func (*DefaultLogger) Debug

func (d *DefaultLogger) Debug(ctx context.Context, s string, i ...interface{})

func (*DefaultLogger) Error

func (d *DefaultLogger) Error(ctx context.Context, s string, i ...interface{})

func (*DefaultLogger) Info

func (d *DefaultLogger) Info(ctx context.Context, s string, i ...interface{})

func (*DefaultLogger) Panic

func (d *DefaultLogger) Panic(ctx context.Context, s string, i ...interface{})

func (*DefaultLogger) Warn

func (d *DefaultLogger) Warn(ctx context.Context, s string, i ...interface{})

type DefaultValidator

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

func (*DefaultValidator) ValidateStruct added in v0.0.5

func (v *DefaultValidator) ValidateStruct(obj any) []ValidationError

func (*DefaultValidator) ValidateVar added in v0.0.5

func (v *DefaultValidator) ValidateVar(variable any, rule string) *ValidationError

type ErrorIdentifier added in v0.0.4

type ErrorIdentifier string
const InvalidRequest ErrorIdentifier = "InvalidRequest"
const UnknownError ErrorIdentifier = "UnknownError"
const UnprocessableEntity ErrorIdentifier = "UnprocessableEntity"
const WrongRequestDecoding ErrorIdentifier = "WrongRequestDecoding"

type HttpRoutesInitializer

type HttpRoutesInitializer interface {
	// HttpRoutesInitializer Returns a set of http routes processed by the module
	ModuleRoutes() []RouteInfo
}

HttpRoutesInitializer if service provider implements this method it will be called after initializing the configuration and its result will be added to the http routes listened by the application router

type JsonResponseWriter

type JsonResponseWriter interface {
	Success(w http.ResponseWriter, r *http.Request, response ActionResponse)
	Error(w http.ResponseWriter, r *http.Request, response ActionResponse)
}

func NewJsonResponseWriter added in v0.0.3

func NewJsonResponseWriter(logger Logger, config *Config) JsonResponseWriter

type Logger

type Logger interface {
	Debug(ctx context.Context, s string, i ...interface{})
	Info(ctx context.Context, s string, i ...interface{})
	Warn(ctx context.Context, s string, i ...interface{})
	Error(ctx context.Context, s string, i ...interface{})
	Panic(ctx context.Context, s string, i ...interface{})
}

func NewDefaultLogger

func NewDefaultLogger() Logger

type RouteInfo

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

func NewRouteInfo

func NewRouteInfo(method string, path string, handler http.HandlerFunc) *RouteInfo

func (RouteInfo) Handler

func (r RouteInfo) Handler() http.HandlerFunc

func (RouteInfo) Method

func (r RouteInfo) Method() string

func (RouteInfo) Path

func (r RouteInfo) Path() string

type Router

type Router interface {
	AddRoutes(routes []RouteInfo)
	Run() error
	RouteParams(r *http.Request) url.Values
}

type Routes

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

func NewRoutes

func NewRoutes() *Routes

func (*Routes) AddFromRoutes

func (r *Routes) AddFromRoutes(routes *Routes)

func (*Routes) Delete

func (r *Routes) Delete(path string, handler http.HandlerFunc)

func (*Routes) Get

func (r *Routes) Get(path string, handler http.HandlerFunc)

func (*Routes) GetRoutesInfo

func (r *Routes) GetRoutesInfo() []RouteInfo

func (*Routes) Options

func (r *Routes) Options(path string, handler http.HandlerFunc)

func (*Routes) Post

func (r *Routes) Post(path string, handler http.HandlerFunc)

func (*Routes) Put

func (r *Routes) Put(path string, handler http.HandlerFunc)

type ServiceProvider

type ServiceProvider interface {
	// ProvidedServices returns a list of constructors that presents all services of the module.
	// All of them will be placed in the dependency injection container
	ProvidedServices() []interface{}
}

ServiceProvider describes all services of a module in the dependency injection container it is the first step of the application running

type StartApplicationListener

type StartApplicationListener interface {
	// OnStart Starts module's application such as a web-server
	OnStart() error
}

StartApplicationListener if service provider implements this method it will be called after initializing the routes

type StructValidator added in v0.0.5

type StructValidator interface {
	ValidateStruct(obj any) []ValidationError
}

func NewDefaultValidator

func NewDefaultValidator(logger Logger) StructValidator

type ValidatableStruct added in v0.0.5

type ValidatableStruct interface {
	Validate(ctx context.Context) []ValidationError
}

type ValidationError added in v0.0.3

type ValidationError struct {
	Field      string
	Identifier ErrorIdentifier
	Err        string
}

func NewValidationError added in v0.0.3

func NewValidationError(field string, err string, identifier ErrorIdentifier) *ValidationError

func (ValidationError) Error added in v0.0.3

func (e ValidationError) Error() string

type VarValidator added in v0.0.5

type VarValidator interface {
	ValidateVar(variable any, rule string) *ValidationError
}

Jump to

Keyboard shortcuts

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