Inversion of Control
Introduction to IoC
The main goal of Inversion of Control and Dependency Injection is to remove dependencies of an application. This makes the system more decoupled and maintainable.

Dependency injection is a concept valid for any programming language. The general concept behind dependency injection is called Inversion of Control. According to this concept, a struct should not configure its dependencies statically but should be configured from the outside.
Dependency Injection design pattern allows us to remove the hard-coded dependencies and make our application loosely coupled, extendable and maintainable.
Dependency Injection
One of the most significant features of Hiboot is Dependency Injection. Hiboot implements the JSR-330 standard.
Dependency Injection provides objects that an object needs. Rather than the dependencies constructing themselves, they are injected by some external means.
Basic Example
package service
import "github.com/hidevopsio/hiboot/pkg/app"
type UserRepository interface {
FindById(id uint64) (*User, error)
}
type UserService struct {
repository UserRepository
}
func init() {
app.Register(newUserService)
}
func newUserService(repository UserRepository) *UserService {
return &UserService{
repository: repository,
}
}
func (s *UserService) GetUser(id uint64) (*User, error) {
return s.repository.FindById(id)
}
Constructor Injection (Recommended)
Constructor Injection is the recommended approach in Hiboot. It has several advantages:
- Testable: Easy to implement unit tests with mocks
- Syntax validation: IDEs can validate types and avoid typos
- Immutability: Dependencies cannot be changed after construction
- Completeness: Ensures all required dependencies are set
Example
package controller
import (
"github.com/hidevopsio/hiboot/pkg/app"
"github.com/hidevopsio/hiboot/pkg/at"
"github.com/hidevopsio/hiboot/pkg/model"
"myapp/service"
)
type userController struct {
at.RestController
at.RequestMapping `value:"/user"`
userService *service.UserService
}
func init() {
app.Register(newUserController)
}
// Dependencies are injected through constructor arguments
func newUserController(userService *service.UserService) *userController {
return &userController{
userService: userService,
}
}
func (c *userController) GetById(id uint64) (model.Response, error) {
user, err := c.userService.GetUser(id)
response := new(model.BaseResponse)
response.SetData(user)
return response, err
}
Field Injection
Field Injection is triggered by the inject:"" struct tag. When this tag is present on a field, Hiboot tries to resolve the object to inject by the type of the field.
Basic Field Injection
type userController struct {
at.RestController
UserService *service.UserService `inject:""`
}
func newUserController() *userController {
return &userController{}
}
func init() {
app.Register(newUserController)
}
Named Injection
If multiple implementations of the same interface are available, disambiguate using the field name or tag value:
type AuthService interface {
Authenticate(username, password string) bool
}
// Two implementations of AuthService
type basicAuthService struct{}
type oauth2AuthService struct{}
type authController struct {
at.RestController
// Inject by field name (matches "basicAuthService")
BasicAuthService AuthService `inject:""`
// Inject by field name (matches "oauth2AuthService")
Oauth2AuthService AuthService `inject:""`
// Inject by explicit name
PrimaryAuth AuthService `inject:"basicAuthService"`
}
Value Injection
Inject configuration values using the value:"" tag:
type MyService struct {
AppName string `value:"${app.name}"`
Port int `value:"${server.port}"`
LogLevel string `value:"${logging.level:info}"` // with default value
}
Default Value Injection
Use the default:"" tag to specify default values:
type Config struct {
Timeout int `default:"30"`
Host string `default:"localhost"`
Enabled bool `default:"true"`
}
Method Injection
Method Injection is used in auto-configuration. Dependencies are injected through method arguments:
package myconfig
import "github.com/hidevopsio/hiboot/pkg/app"
type configuration struct {
app.Configuration
}
func init() {
app.Register(newConfiguration)
}
func newConfiguration() *configuration {
return &configuration{}
}
// Database is injected through method arguments
func (c *configuration) UserRepository(db *Database) UserRepository {
return &mysqlUserRepository{db: db}
}
// UserService depends on UserRepository
func (c *configuration) UserService(repo UserRepository) *UserService {
return &UserService{repository: repo}
}
Registration
All injectable components must be registered using app.Register():
func init() {
// Register constructors
app.Register(newUserService)
app.Register(newUserController)
// Register multiple at once
app.Register(newFoo, newBar, newBaz)
}
Injection Rules
- Pointer types: Dependencies must be pointer types or interfaces
- Registration required: Components must be registered via
app.Register() - No circular dependencies: Avoid circular dependency chains
- Interface resolution: If multiple implementations exist, use naming to disambiguate
Valid
type Foo struct{}
type Bar struct {
foo *Foo // pointer - will be injected
}
Invalid
type Foo struct{}
type Bar struct {
foo Foo // not a pointer - will NOT be injected
}
Testing with Dependency Injection
Constructor injection makes testing easy:
package controller
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
// Mock implementation
type mockUserService struct {
mock.Mock
}
func (m *mockUserService) GetUser(id uint64) (*User, error) {
args := m.Called(id)
return args.Get(0).(*User), args.Error(1)
}
func TestUserController_GetById(t *testing.T) {
// Create mock
mockService := new(mockUserService)
mockService.On("GetUser", uint64(1)).Return(&User{Name: "Test"}, nil)
// Inject mock through constructor
controller := newUserController(mockService)
// Test
response, err := controller.GetById(1)
assert.NoError(t, err)
assert.NotNil(t, response)
mockService.AssertExpectations(t)
}
What’s Next?
- Auto Configuration - Create custom starters
- Web Application - Build REST APIs
- CLI Application - Build CLI tools