Skip to content
This repository was archived by the owner on Apr 13, 2024. It is now read-only.

mitz-it/golang-modules

Repository files navigation

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()
}

Configure the API

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)
    }
}

About

Module manager and App Builder for MitzIT microservices

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •  

Languages