October, 2019
Microservices Infrastructure
This month focus is on microservices infrastracture related to management of multiple processes. It includes:
- dealing with multiple microservices within a test case
- dealing with multiple microservices in production
- dealing with multiple microservices in development
Hivemind
I use hivemind when I need to run a set of microservices at the same time during development.
Good Planning Results in Fast Microservice Tests
It is not a secret that microservices usually have a dependency graph. When time comes to test microservices it can be challenging to setup a proper testing environment.
If a proper effort has been put in how to create microservice at application level, then it is not difficult to host all microservices inside a single “fat” test process. The advantage of this approach is in short development cycle time.
The following example is a content of a test case for private logger microservce written in GO language. The test case starts 2 microservices in the same test process prior to running any tests and ensures that microservices properly shutdown as well.
package logger_test
import (
"log"
"os"
"testing"
"github.com/golang/protobuf/ptypes"
"github.com/nats-io/nats.go"
"gitlab.com/sergei.gnezdov/idcs/backend/services/config"
"gitlab.com/sergei.gnezdov/idcs/backend/services/logger"
"gitlab.com/sergei.gnezdov/idcs/backend/services/servicebase"
)
// loads test configuration from filesystem
// replaces microservice arguments on startup
var appFlags = servicebase.TestEnvService()
// TestMain is an entry point when
// custom setup needs to be done before and after tests.
//
// In this case we start 2 microservices in the same process.
//
// The tests will still run even if this method is commented out.
// If it is commented out, then microservices need to be started
// outside of the test process prior to running the test.
func TestMain(m *testing.M) {
var err error
confSrv := config.NewServer(appFlags.BootConfig)
logSrv := logger.NewServer(appFlags.BootConfig)
subSrv := servicebase.NewSubscriberService(appFlags.BootConfig, confSrv, logSrv)
err = subSrv.Start()
if err != nil {
log.Fatal(err)
}
defer subSrv.Stop()
exitCode := m.Run()
os.Exit(exitCode)
}
func TestLog(t *testing.T) {
var err error
connSrv := servicebase.NewConnectionService(appFlags.BootConfig)
err = connSrv.Run(func(nc *nats.Conn) error {
loggerProxy := logger.NewLoggerClient(nc)
err := loggerProxy.Log(logger.LogRequest{
App: "testApp",
Created: ptypes.TimestampNow(),
Message: "test-line",
})
return err
})
if err != nil {
t.Fatalf("Log call failed: %v", err)
}
}
The result is a test run time as short as 1.5 seconds on an average laptop and one command allows to start test environment and test itself:
go test
PM2 and Swagger
Example ecosystem.yml
configuration file for PM2 and dotnet:
apps:
- name: 'service-name-goes-here'
script: dotnet
args: IO.Swagger.dll
instances: 1
Swagger server URLs:
- https://localhost:5001/swagger/index.html
- API calls match specification path. The following is just an example: https://localhost:5001/api/path/.