MitzIT - Go Modules
MitzIT microservices are built on top of one or multiple modules. Each module is an independent piece and has its own domain. This package allows registering modules to a single entry point and having it all started with one call.
Installation
go get -u github.com/mitz-it/golang-modules
Usage
package main
import (
modules "github.com/mitz-it/golang-modules"
)
func main() {
builder := modules.NewHostBuilder()
host := builder.Build()
host.Run()
}
The HostBuilder
can be used to enable Swagger
and set the API base path e.g: /api
package main
import (
modules "github.com/mitz-it/golang-modules"
ginzap "github.com/gin-contrib/zap"
"github.com/gin-gonic/gin"
"github.com/mitz-it/my-microservice/docs" // import the docs folder content generated by swag
"github.com/mitz-it/my-microservice/logger"
)
func main() {
builder := modules.NewHostBuilder()
builder.ConfigureAPI(func(api *modules.API) {
api.UseSwagger(docs.SwaggerInfo)
api.WithBasePath("/api") // /api is the default base path
api.ConfigureRouter(func(router *gin.Engine) { // override router configurations
router.Use(ginzap.Ginzap(logger.Log.Zap(), time.RFC3339, true))
router.Use(ginzap.RecoveryWithZap(logger.Log.Zap(), true))
})
})
host := builder.Build()
host.Run()
}
The API can also be disabled by invoking the builder.UseAPI(false)
like so:
package main
import (
modules "github.com/mitz-it/golang-modules"
)
func main() {
builder := modules.NewHostBuilder()
builder.UseAPI(false) // any controller for any module will be ignored if this method is called passing false
host := builder.Build()
host.Run()
}
Dependency Injection
The golang-modules
package provides an interface to create standardized dependency injection containers. To implement the IDependencyInjectionContainer
interface the struct must have a Setup()
method. As a best practice, we create a constructor function that returns both the concrete implementation of IDependencyInjectionContainer
and the pointer to the module's *dig.Container
.
package privategroupsmodule
import (
modules "github.com/mitz-it/golang-modules"
"go.uber.org/dig"
)
type DependencyInjectionContainer struct {
container *dig.Container
}
func (di *DependencyInjectionContainer) Setup() {
modules.SetupConfig(di.container, "../config/dev.env", "private_groups")
modules.SetupLogging(di.container)
di.setupCQRS()
di.setupMessaging()
// add configuration to the DI container for this module
}
func NewDependencyInjectionContainer() (modules.IDependencyInjectionContainer, *dig.Container) {
container := dig.New()
di := &DependencyInjectionContainer{
container: container,
}
return di, container
}
Controllers
Controllers are a group of endpoints for the same resource or collection. To define a controller you need to implement the IController
interface by adding the Register(*gin.RouterGroup)
method to the controller struct and,
and create a constructor function that receives a parameter of type *dig.Container
.
package privategroupsmodule
import (
"github.com/gin-gonic/gin"
modules "github.com/mitz-it/golang-modules"
"go.uber.org/dig"
)
type PrivateGroupsController struct {
container *dig.Container
}
func (controller *PrivateGroupsController) Register(group *gin.RouterGroup) {
// /api/private-groups/:id/invite
group.POST("/:id/invite", func(ctx *gin.Context) {
// invite the member
})
// /api/private-groups/:id/members
group.GET("/:id/members", func(ctx *gin.Context) {
// get the members
})
}
func NewPrivateGroupsController(container *dig.Container) modules.IController {
return &PrivateGroupsController{
container: container,
}
}
Workers
Workers can be used to do background processing e.g: listen to a broker for messages. To define a worker you need to implement the IWorker
interface by adding the Run()
method to the worker struct and, create a constructor function that receives a parameter of type *dig.Container
.
package privategroupsmodule
import (
"context"
config "github.com/mitz-it/golang-config"
logging "github.com/mitz-it/golang-logging"
messaging "github.com/mitz-it/golang-messaging"
modules "github.com/mitz-it/golang-modules"
"go.uber.org/dig"
)
type PrivateGroupRequestsWorker struct {
consumer messaging.IConsumer
}
func (worker *PrivateGroupRequestsWorker) Run() {
go worker.consumer.Consume(worker.configureConsumer, worker.onMessageReceived)
}
func (worker *PrivateGroupRequestsWorker) configureConsumer(config *messaging.ConsumerConfiguration) {
// configure the consumer ...
}
func (worker *PrivateGroupRequestsWorker) onMessageReceived(ctx context.Context, message []byte) {
// process received messages ...
}
func NewPrivateGroupRequestsWorker(container *dig.Container) modules.IWorker {
worker := &PrivateGroupRequestsWorker{}
err := container.Invoke(func(config config.Config, logger *logging.Logger) {
connectionString := config.Standard.GetString("amqp_connection_string")
worker.consumer = messaging.NewConsumer(logger, connectionString)
})
if err != nil {
panic(err)
}
return worker
}
Modules
Modules can be composed by zero or more controllers, and zero or more workers which will be sharing the same instance of *dig.Container
.
To create a module, first create the module configuration funcion:
package privategroupsmodule
import modules "github.com/mitz-it/golang-modules"
func PrivateGroupsModule(config *modules.ModuleConfiguration) {
// add the resource/collection path for the module
config.WithName("/private-groups")
// create the DI and *dig.Container instances
di, container := NewDependencyInjectionContainer()
// configure the *dig.Container
di.Setup()
config.WithDIContainer(container)
// register the controller constructor function
config.AddController(NewPrivateGroupsController)
// register the worker constructor function
config.AddWorker(NewPrivateGroupRequestsWorker)
}
⚠ FOR VERSIONS >= 1.13.0, MODULE NAMES ARE OPTIONAL ⚠
When a module is nameless, all endpoints are added to the root *gin.RouterGroup
, which default path is /api
.
Register the PrivateGroupsModule
function using the HostBuilder
instance:
package main
import (
modules "github.com/mitz-it/golang-modules"
"github.com/mitz-it/groups-microservice/docs" // import the docs folder content generated by swag
)
func main() {
builder := modules.NewHostBuilder()
builder.ConfigureAPI(func(api *modules.API) {
api.UseSwagger(docs.SwaggerInfo)
api.WithBasePath("/api") // /api is the default base path
})
// register the module
builder.AddModule(privategroupsmodule.PrivateGroupsModule)
// you can register as many modules as you want, e.g:
builder.AddModule(publicgroupsmodule.PublicGroupsModule)
host := builder.Build()
host.Run()
}
Init Call
You can invoke a function using the *dig.Container
instance from a module before the application starts.
package foomodule
import (
modules "github.com/mitz-it/golang-modules"
os
)
func FooModule(config *modules.ModuleConfiguration) {
config.WithName("/foos")
di, container := NewDependencyInjectionContainer()
di.Setup()
config.WithDIContainer(container)
config.AddController(NewFoosController)
config.AddWorker(NewFooWorker)
config.SetupInitCall(migrateDatabase)
}
func migrateDatabase(container *dig.Container) {
env := os.GetEnv("APPLICATION_ENVIRONMENT")
if env != "dev" || env != "local" {
return
}
err := container.Invoke(func(db DatabaseProvider) {
if connection, err := db.Connect(); err == nil {
connection.Migrate()
} else {
panic(err)
}
})
if err != nil {
panic(err)
}
}