vara

package module
v0.0.1-beta Latest Latest
Warning

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

Go to latest
Published: Nov 14, 2024 License: MIT Imports: 14 Imported by: 0

README

Vara GoDoc Build Status

Vara is a powerful dependency injection framework for building modular, maintainable and testable Go applications.

Features

  • 🚀 Zero-configuration dependency injection
  • 🔒 Type-safe dependency management
  • 📦 Modular architecture with clear boundaries
  • 🌐 Built-in HTTP routing and middleware
  • ⚡ Lifecycle management for application components
  • 🛡️ Flexible guard system for request authorization

Core Concepts

Vara is built around four fundamental concepts:

  • Modules: Organizational units that encapsulate related functionality
  • Dependencies: Services and components managed through dependency injection
  • Controllers: HTTP request handlers with built-in support for guards, filters, interceptors and middlewares
  • Lifecycle Events: Hooks for managing application and component lifecycles

See the documentation to get started and learn more.

Installation

Install Vara in your application with the following command.

go get github.com/huboh/vara@latest

Documentation

Quick example

Here's a minimal example to get you started. View the full example here

const (
    port = "5000"
    host = "localhost"
)

func main() {
    app, err := vara.New(&app.Module{})
    if err != nil {
        log.Fatal("failed to create app:", err)
    }

    err := app.Listen(host, port)
    if err != nil {
        log.Fatal("failed to start server:", err)
    }
}

Authors

License

Vara is released under the MIT License.

Contributing

Contributions are welcomed!

Documentation

Overview

Package vara is a powerful dependency injection framework for Go that enables building modular, maintainable and testable applications.

Overview

Vara simplifies the creation of highly maintainable Go applications through a modular architecture powered by dependency injection. It provides a robust foundation for building large-scale applications where components are loosely coupled, easily testable and highly reusable.

Core Concepts

Vara is built around four fundamental concepts:

  1. Modules - Organizational units that encapsulate related functionality
  2. Dependencies - Services and components managed through dependency injection
  3. Controllers - Http request handlers with built-in support for guards, filters, interceptors and middlewares
  4. Lifecycle Events - Hooks for managing application and component lifecycles

Key Features

  • Zero-configuration dependency injection
  • Type-safe dependency management
  • Modular architecture with clear boundaries
  • Built-in HTTP routing and middleware
  • Lifecycle management for application components
  • Flexible guard system for request authorization

Getting Started

To create a new Vara application:

func main() {
	app, err := vara.New(&app.Module{})
	if err != nil {
		log.Fatal("failed to create app:", err)
	}

	err := app.Listen("localhost", "8080")
	if err != nil {
		log.Fatal("failed to start server:", err)
	}
}

Dependency Injection

Vara supports two types of dependency injection:

  1. Constructor-based Injection
  2. Direct Injection

Constructor-based Injection:

Constructors are the building blocks of dependency injection in Vara. They are plain Go functions that: Accepts zero or more dependencies as parameters and return one or more values of any type and may optionally return an error as the last return value.

  • Preferred for complex dependencies.
  • Allows initialization logic and error handling.
  • Supports dependency chains.

Example:

func NewUserService(cache *cache.Service, database *database.Service) (*UserService, error) {
	return &UserService{
		cache: 	  cache,
		database: database,
	}, nil
}

Any arguments that the constructor has are treated as its dependencies. The dependencies are instantiated in an unspecified order along with any dependencies that they might have, creating a dependency graph at runtime.

Important notes about constructors:

  • Constructors are lazy - they are only called when their return type is required somewhere in the application
  • If a constructor is added for side effects (like registering handlers), its return type must be referenced somewhere in the module for the constructor to be called
  • Dependencies are instantiated in an unspecified order along with their own dependencies

Direct Injection:

If a dependency itself does not require any other dependencies, you can opt to inject it directly without a constructor.

  • Suitable for simple dependencies
  • No constructor needed
  • Faster initialization

Module System

Modules are the building blocks of a Vara application. Each module must implement the Module interface:

type UserModule struct{}

func (m *UserModule) Config() *vara.ModuleConfig {
	return &vara.ModuleConfig{
		IsGlobal:         false,
		Imports:          []vara.Module{&config.Module{}},
		ExportsCtor:      []vara.ProviderConstructor{NewAuthService},
		ProviderConstructors:   []vara.ProviderConstructor{NewAuthService},
		ControllerConstructors: []vara.ControllerConstructor{},
	}
}

Module Configuration Options:

  • [IsGlobal]: Makes this module's exports available to all other modules
  • [Imports]: Other modules required by this module
  • [ExportsCtor]: Subset of providers that will be available to other modules
  • [ProviderConstructors]: Internal services used within the module
  • [ControllerConstructors]: HTTP controllers

Lifecycle Management

Vara provides hooks for managing component lifecycles:

func NewUserService(d *database.Service, lc *vara.Lifecycle) *UserService {
	svc := &UserService{}

	lc.Append(vara.LifecycleHook{
		OnStart: func(ctx context.Context) error {
			// code to execute before the application starts accepting connections
			return nil
		},
		OnStop: func(ctx context.Context) error {
			// code to execute during application shutdown, before the application stops accepting new requests
			return nil
		},
	})

	return svc
}

Lifecycle Events:

  • OnStart: Called before the application starts accepting connections
  • OnStop: Called during graceful shutdown

Controllers

Controllers handles request routing and processing. They provide a structured way to define endpoints and their associated handlers.

A controller must implement the Controller interface:

type UserController struct {
	service *UserService
}

func (c *UserController) Config() *vara.ControllerConfig {
	return &vara.ControllerConfig{
		Pattern: 	"/api/v1",
		GuardConstructors: []vara.GuardConstructor{newAuthGuard},
		RouteConfigs: []*vara.RouteConfig{
			{
				Pattern: "/users",
				Method:  http.MethodGet,
				Handler: http.HandlerFunc(c.listUsers),
				GuardConstructors: []vara.GuardConstructor{newRateLimitGuard},
			},
		},
	}
}

Request Guards

Guards are used to control access to controllers or individual routes, providing an additional layer of security by enforcing runtime or compile-time rules through the incoming request or controller/route metadata before handlers are executed.

A guard must implement the Guard interface:

type AuthGuard struct {
    auth *AuthService
}

func newAuthGuard(a *AuthService) *AuthGuard {
    return &AuthGuard{
        auth: a,
    }
}

func (g *AuthGuard) Allow(gCtx vara.GuardContext) (bool, error) {
    validated, err := g.auth.Validate(gCtx.Http.R.Header.Get("Authorization"))
    if err != nil {
        return false, err
    }

    return validated, nil
}

Guard Scopes:

  • Route-level: Applied to specific routes only
  • Controller-level: Applied to all routes in a controller

Complete application structure:

api/
├── main.go           	// Application entry point
├── modules/
│   ├── app/
│   │   └── module.go     // Root module
│   ├── auth/
│   │   ├── module.go     // Auth module
│   │   ├── service.go    // Auth service
│   │   └── controller.go // Auth controller
│   └── user/
│       ├── module.go     // User module
│       ├── service.go    // User service
│       └── repository.go // User repository
└── go.mod

Best Practices

1. Module Organization:

  • Keep modules focused on a single responsibility
  • Use clear naming conventions
  • Group related functionality together

2. Dependency Management:

  • Prefer constructor injection for complex dependencies
  • Validate dependencies in constructors
  • Use interfaces for better testability

3. Error Handling:

  • Return meaningful errors from constructors
  • Implement proper cleanup in OnStop handlers

4. Testing:

  • Mock dependencies using interfaces
  • Test modules in isolation
  • Use table-driven tests

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func GetToken

func GetToken(v any) string

GetToken generates a unique token for the given value based on its type.

Types

type App

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

App represents the main application

func New

func New(module Module) (*App, error)

New initializes a new instance of App, configuring the root module and dependencies.

func (*App) Listen

func (a *App) Listen(host, port string) error

func (*App) Shutdown

func (a *App) Shutdown(ctx context.Context) error

type Controller

type Controller interface {
	Config() *ControllerConfig
}

Controller is any type that can receive inbound requests and produce responses.

type ControllerConfig

type ControllerConfig struct {
	// Pattern is a root prefix appended to each route path registered
	// within the controller.
	Pattern string

	// Metadata holds arbitrary metadata associated with the controller.
	Metadata any

	// RouteConfigs lists all routes managed by the controller.
	RouteConfigs []*RouteConfig

	// Guards contains guard instances applied globally to all routes in the controller.
	Guards []Guard

	// GuardConstructors provides constructors for creating guard instances that
	// requires dependency injection.
	GuardConstructors []GuardConstructor
}

ControllerConfig defines the configuration for a controller. E.g route patterns, guards and metadata.

type ControllerConstructor

type ControllerConstructor constructor

ControllerConstructor is a function type that creates Controller instances. It may have dependencies as parameters and returns instances of the Controller interface, optionally returning an error on failure.

Any arguments that the constructor has are treated as its dependencies. The dependencies are instantiated in an unspecified order along with any dependencies that they might have.

type Guard

type Guard interface {
	Allow(GuardContext) (bool, error)
}

Guard is an interface that determines whether a request should be handled by a route handler or rejected based on specific criteria or metadata present at runtime.

type GuardConstructor

type GuardConstructor constructor

GuardConstructor is a function that takes any number of dependencies as its parameters and returns an arbitrary number of values that meets the `Guard` interface and may optionally return an error to indicate that it failed to build the value.

Any arguments that the constructor has are treated as its dependencies. The dependencies are instantiated in an unspecified order along with any dependencies that they might have.

type GuardContext

type GuardContext struct {
	// Http contains the request and response information.
	Http GuardContextHttp

	// RouteCfg contains metadata and configuration specific to the route.
	RouteCfg RouteConfig

	// ControllerCfg contains metadata and configuration for the controller.
	ControllerCfg ControllerConfig
}

GuardContext provides the contextual information that a guard needs to make its decision.

It encapsulates the HTTP request and response, along with route and controller metadata for a more informed decision-making process.

type GuardContextHttp

type GuardContextHttp struct {
	R *http.Request
	W http.ResponseWriter
}

GuardContextHttp holds HTTP request and response information for GuardContext.

type HttpServer

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

func (*HttpServer) Listen

func (s *HttpServer) Listen(host string, port string) error

Listen starts the HTTP server on the specified host and port and listens for incoming requests.

Also listens for system signals like SIGINT and SIGTERM to enable graceful shutdown.

func (*HttpServer) RegisterOnShutdown

func (s *HttpServer) RegisterOnShutdown(f func(context.Context) error) error

RegisterOnShutdown registers a function that will be called before shutting own.

func (*HttpServer) Shutdown

func (s *HttpServer) Shutdown(c context.Context) error

Shutdown gracefully shuts down the HTTP server.

type Lifecycle

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

Lifecycle manages the lifecycle hooks registered within the application, enabling actions to be performed on the application's startup and graceful shutdown.

Hooks can be referenced from any provider or controller in the application. The hooks are triggered before the application begins accepting connections and during graceful shutdown to release resources.

Example:

func NewUserService(d *database.Service, lc *vara.Lifecycle) *UserService {
	us := &UserService{
		database: d,
	}

	lc.Append(vara.LifecycleHook{
		OnStart: func(ctx context.Context) error {
			// code to execute before the application starts accepting connections
			return nil
		},
		OnStop: func(ctx context.Context) error {
			// code to execute during application shutdown, before the application stops accepting new requests
			return nil
		},
	})

	return us
}

func (*Lifecycle) Append

func (l *Lifecycle) Append(hooks ...LifecycleHook)

Append registers a new lifecycle hook.

type LifecycleHook

type LifecycleHook struct {
	// OnStop defines a lifecycle hook that is triggered during a graceful shutdown,
	// before the application stops accepting new requests.
	//
	// Use this hook to clean up resources like closing database connections,
	// flushing logs or saving state.
	OnStop LifecycleHookFunc

	// OnStart defines a lifecycle hook that is called before the application
	// begins accepting new connections.
	//
	// Use this to set up necessary resources like database connections or caches.
	OnStart LifecycleHookFunc
}

LifecycleHook represents a lifecycle hook with functions that can be registered to execute specific tasks during the application's lifecycle.

type LifecycleHookFunc

type LifecycleHookFunc func(context.Context) error

LifecycleHookFunc represents a [lifecycleHook] function

type Module

type Module interface {
	// Config returns the module config containing providers, exports
	// and imports required by the module.
	Config() *ModuleConfig
}

Module is an interface representing a self-contained unit of functionality. It exposes providers that can be imported by other modules within the application.

type ModuleConfig

type ModuleConfig struct {
	// IsGlobal indicates whether the module's exported providers are globally
	// available to every other module without needing an explicit import.
	//
	// This is useful for shared utilities or database connections.
	IsGlobal bool

	// Imports specifies other modules that this module depends on.
	// Providers from these modules will be available within this module.
	Imports []Module

	// Exports lists providers from this module that should be accessible to
	// other modules that import this module.
	Exports []Provider

	// ExportConstructors lists constructors for providers that should be accessible
	// in other modules importing this module.
	ExportConstructors []ProviderConstructor

	// Providers lists the providers within the module that are shared across
	// the module's other providers.
	Providers []Provider

	// ProviderConstructors lists constructors for providers that the Vara injector
	// will create and share within this module.
	ProviderConstructors []ProviderConstructor

	// Controllers lists the handlers defined in this module, which handle
	// HTTP requests and define the module's endpoints.
	Controllers []Controller

	// ControllerConstructors lists constructors for controllers in this module that
	// will be instantiated by the Vara injector.
	ControllerConstructors []ControllerConstructor
}

ModuleConfig provides configuration settings for a module's functionality, such as its providers, controllers and imported modules.

type Provider

type Provider interface{}

Provider is a marker interface for types that can be provided as dependencies.

type ProviderConstructor

type ProviderConstructor constructor

ProviderConstructor is a constructor function type for providers. It takes any required dependencies as parameters and returns instances that implement the `Provider` interface. Optionally, the constructor may return an error to signal that it failed to create the provider instance.

Dependencies are resolved and injected into the constructor in an unspecified order. This allows for flexible and lazy-loading dependency injection where each dependency may itself have nested dependencies.

Example:

func NewProvider(dep1 Dependency1, dep2 Dependency2) (Provider, error) {
    // Create and return a provider instance
}

Any dependencies needed by the constructor will be resolved and instantiated by the module's DI scope.

type RouteConfig

type RouteConfig struct {
	Method   string       // The HTTP method (e.g., GET, POST) for the route.
	Pattern  string       // The URL pattern that the route will match.
	Handler  http.Handler // The HTTP handler to process requests on this route.
	Metadata any          // Optional metadata that can be associated with the route.

	Guards            []Guard            // Guards to enforce conditions before route handling.
	GuardConstructors []GuardConstructor // Guard constructors for dynamic guard instantiation.
}

RouteConfig defines the configuration for a route.

Directories

Path Synopsis
examples
rest-api command
pkg
modules/json
Package json provides utilities for working with JSON in HTTP request & responses.
Package json provides utilities for working with JSON in HTTP request & responses.

Jump to

Keyboard shortcuts

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