Skip to content

Commit

Permalink
added statsD and new facebook injection module
Browse files Browse the repository at this point in the history
  • Loading branch information
nicholasjackson committed Dec 9, 2015
1 parent 0f88662 commit 2a74d7d
Show file tree
Hide file tree
Showing 16 changed files with 208 additions and 613 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,6 @@ To generate HTML documentation from the api-blueprint run:
rake docs
```
[http://htmlpreview.github.io/?https://github.com/nicholasjackson/go-microservice-template/blob/master/api-blueprint/microservice-template.html](http://htmlpreview.github.io/?https://github.com/nicholasjackson/go-microservice-template/blob/master/api-blueprint/microservice-template.html)

# StatsD
If you have chosen to include the StatsD client and if you are building microservices then metrics exposed from your service will possibly save your ass one day so you really should. For testing purposes compose will include a standard Graphite / Carbon stack, you can access Graphite from http://[DOCKER_IP]:8080
30 changes: 21 additions & 9 deletions generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ var scanner = bufio.NewScanner(os.Stdin)
type templateData struct {
ServiceName string
Namespace string
StatsD bool
}

func requestName(scanner *bufio.Scanner) string {
Expand All @@ -40,14 +41,25 @@ func requestNamespace(scanner *bufio.Scanner) string {
return line
}

func includeStatsD(scanner *bufio.Scanner) bool {
fmt.Printf("Include StatsD? (y|n)\n")
scanner.Scan()
line := scanner.Text()
fmt.Println("")

return line == "y"
}

func main() {
printHeader()

nameSpace := requestNamespace(scanner)
serviceName := requestName(scanner)
statsD := includeStatsD(scanner)

if confirm(serviceName, nameSpace, scanner) {
generateTemplate(serviceName, nameSpace)
data := templateData{ServiceName: serviceName, Namespace: nameSpace, StatsD: statsD}
generateTemplate(data)
} else {
fmt.Println("")
fmt.Println("Fine I won't")
Expand Down Expand Up @@ -84,35 +96,35 @@ func confirm(serviceName, nameSpace string, scanner *bufio.Scanner) bool {
return line == "y"
}

func generateTemplate(serviceName string, nameSpace string) {
func generateTemplate(data templateData) {
fmt.Println("output path: ", os.Getenv("GOPATH"))
destination := destinationFolder(serviceName, nameSpace)
destination := destinationFolder(data.ServiceName, data.Namespace)
os.MkdirAll(destination, os.ModePerm)

copyNonGitFiles(destination, serviceName, nameSpace)
copyNonGitFiles(destination, data)
//renameInFiles(serviceName, nameSpace)
}

func copyNonGitFiles(destination string, serviceName string, nameSpace string) {
func copyNonGitFiles(destination string, data templateData) {
err := filepath.Walk("./template_files", func(path string, f os.FileInfo, err error) error {
_ = processNonGitFile(path, destination, serviceName, nameSpace)
_ = processNonGitFile(path, destination, data)
return nil
})
if err != nil {
panic(err)
}
}

func processNonGitFile(path string, destination string, serviceName string, nameSpace string) error {
newFile := replaceDefaultNameInPath(path, serviceName)
func processNonGitFile(path string, destination string, data templateData) error {
newFile := replaceDefaultNameInPath(path, data.ServiceName)
newFile = replaceTemplateExtInPath(newFile)
destinationFile := destination + "/" + newFile

switch {
case gitRegex.MatchString(path):
fmt.Println("Skipping git path: ", path)
case templateRegex.MatchString(path):
err := saveAndProcessTemplate(path, destinationFile, templateData{serviceName, nameSpace})
err := saveAndProcessTemplate(path, destinationFile, data)
if err != nil {
fmt.Println("Unable to process template:", err)
return err
Expand Down
1 change: 1 addition & 0 deletions template_files/_build/Gemfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
source 'https://rubygems.org'

gem "rake"
gem "cucumber", "~> 1.3.10"
gem "cucumber-rest-api"
gem 'cucumber-mailcatcher', '~> 0.12'
Expand Down
4 changes: 3 additions & 1 deletion template_files/_build/Rakefile.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ task :run do
puts `docker-compose -f ./dockercompose/#{DOCKER_IMAGE_NAME}/docker-compose.yml up -d`
sleep 2
setConsulVariables get_docker_ip_address, 9500
#self.wait_until_server_running ENV['WEB_SERVER_URI']

sh "docker-compose -f ./dockercompose/#{DOCKER_IMAGE_NAME}/docker-compose.yml logs"
rescue SystemExit, Interrupt
sh "docker-compose -f ./dockercompose/#{DOCKER_IMAGE_NAME}/docker-compose.yml stop"
Expand All @@ -73,6 +73,8 @@ task :run do
end
end

task :build_and_run => [:build_server, :run]

task :docs do
container = get_container

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
environment:
- "CONSUL=consul:8500"
links:
- consul:consul
- consul:consul{{if .StatsD}}
- statsd:statsd{{end}}
consul:
image: progrium/consul
ports:
Expand All @@ -14,3 +15,11 @@ consul:
- "9600:53/udp"
hostname: node1
command: "-server -bootstrap -ui-dir /ui"
{{if .StatsD}}statsd:
image: hopsoft/graphite-statsd
ports:
- "8080:80"
- "2003:2003"
- "8125:8125/udp"
- "8126:8126"
{{end}}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"Mykey": "{{key "my-key"}}"
"StatsDServerIP": {{key "StatsDServerIP"}}
}
15 changes: 11 additions & 4 deletions template_files/global/global.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,31 @@ package global

import (
"encoding/json"
"fmt"
"os"
)

type ConfigStruct struct {
Mykey string
RootFolder string
StatsDServerIP string
RootFolder string
}

var Config ConfigStruct

func LoadConfig(config string, rootfolder string) {
func LoadConfig(config string, rootfolder string) error {
fmt.Println("Loading Config: ", config)

file, err := os.Open(config)
if err != nil {
panic("Unable to open config")
return fmt.Errorf("Unable to open config")
}

decoder := json.NewDecoder(file)
Config = ConfigStruct{}
err = decoder.Decode(&Config)
Config.RootFolder = rootfolder

fmt.Println(Config)

return nil
}
18 changes: 0 additions & 18 deletions template_files/handlers/health.go

This file was deleted.

54 changes: 54 additions & 0 deletions template_files/handlers/health.go.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package handlers

import (
"encoding/json"
"net/http"

{{if .StatsD}}"github.com/nicholasjackson/docker-testing/logging"{{end}}
)

// This is not particularlly a real world example it mearly shows how a builder or a factory could be injected
// into the HealthHandler
type HealthResponseBuilder struct {
statusMessage string
}

func (b *HealthResponseBuilder) SetStatusMessage(message string) *HealthResponseBuilder {
b.statusMessage = message
return b
}

func (b *HealthResponseBuilder) Build() HealthResponse {
var hr HealthResponse
hr.StatusMessage = b.statusMessage
return hr
}

type HealthDependencies struct {
// if not specified will create singleton
SingletonBuilder *HealthResponseBuilder `inject:""`
{{if .StatsD}}
// statsD interface must use a name type as injection cannot infer ducktypes
Stats logging.StatsD `inject:"statsd"`
{{end}}
// if not specified in the graph will automatically create private instance
PrivateBuilder *HealthResponseBuilder `inject:"private"`
}

type HealthResponse struct {
StatusMessage string `json:"status_message"`
}

var HealthHandlerDependencies *HealthDependencies = &HealthDependencies{}

{{if .StatsD}}const HEALTH_HANDLER_CALLED = "{{.ServiceName}}.health_handler"{{end}}

func HealthHandler(rw http.ResponseWriter, r *http.Request) {
// all HealthHandlerDependencies are automatically created by injection process
{{if .StatsD}}HealthHandlerDependencies.Stats.Increment(HEALTH_HANDLER_CALLED){{end}}

response := HealthHandlerDependencies.SingletonBuilder.SetStatusMessage("OK").Build()

encoder := json.NewEncoder(rw)
encoder.Encode(&response)
}
68 changes: 68 additions & 0 deletions template_files/handlers/health_test.go.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package handlers

import (
"fmt"
"net/http"
"net/http/httptest"
"testing"

"github.com/facebookgo/inject"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)

{{if .StatsD}}
type MockStatsD struct {
mock.Mock
}

func (m *MockStatsD) Increment(label string) {
_ = m.Mock.Called(label)
}

var statsDMock *MockStatsD
{{end}}

func TestSetup(t *testing.T) {
// create an injection graph containing the mocked elements we wish to replace

var g inject.Graph

{{if .StatsD}}statsDMock = &MockStatsD{}{{end}}

err := g.Provide(
&inject.Object{Value: HealthHandlerDependencies},
{{if .StatsD}}&inject.Object{Value: statsDMock, Name: "statsd"},{{end}}
)

if err != nil {
fmt.Println(err)
}

if err := g.Populate(); err != nil {
fmt.Println(err)
}

statsDMock.Mock.On("Increment", mock.Anything).Return()
}

// Simple test to show how we can use the ResponseRecorder to test our HTTP handlers
func TestHealthHandler(t *testing.T) {
var responseRecorder httptest.ResponseRecorder
var request http.Request

HealthHandler(&responseRecorder, &request)

assert.Equal(t, 200, responseRecorder.Code)
}

{{if .StatsD}}
func TestHealthHandlerSetStats(t *testing.T) {
var responseRecorder httptest.ResponseRecorder
var request http.Request

HealthHandler(&responseRecorder, &request)

statsDMock.Mock.AssertCalled(t, "Increment", HEALTH_HANDLER_CALLED)
}
{{end}}
Loading

0 comments on commit 2a74d7d

Please sign in to comment.