Categorygithub.com/mitz-it/golang-modules
modulepackage
1.16.0
Repository: https://github.com/mitz-it/golang-modules.git
Documentation: pkg.go.dev

# README

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

:warning: FOR VERSIONS >= 1.13.0, MODULE NAMES ARE OPTIONAL :warning:

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

# Functions

No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author

# Structs

No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author

# Interfaces

No description provided by the author
No description provided by the author
No description provided by the author

# Type aliases

No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author