Covers different areas of programming and the best practices/components explained.
- Basic Types
- Concurrency
- Encoding
- File System
- GRPC
- Init Function
- Logging
- Net
- OS
- Slices
- Structs
- Testing
- Time
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // alias for uint8
rune // alias for int32
// represents a Unicode code point
float32 float64
complex64 complex128
someNum := x
someFloat := float32(x)
A way to communicate between different go routines
.
someChan := make(chan bool)
Send to a channel:
go func() {
someChan <- true
}
Read from a channel:
msg := <- someChan
fmt.Print(msg)
A pattern that blocks a programming from ending by using for{}
Allows multiple channels to be read-from using select{}
.
Each case reads from a channel and handles the message.
for {
select {
case criticalMsg := <-criticalAlert:
log.Printf("Critical alert received!")
if err := criticalMsg.Ack(false); err != nil {
log.Fatalf("Failed to acknowledge alert msg")
}
case rateLimitMsg := <-rateLimitAlert:
log.Printf("Rate limit alert received!")
if err := rateLimitMsg.Ack(false); err != nil {
log.Fatalf("Failed to acknowledge alert msg")
}
}
}
Encoding and Decoding JSON to structs in go is quite straight forward.
Marshalling struct
-> json
.
import (
"encoding/json"
)
type Image struct {
ImageId string
UserId string
ImagePath string
}
image := &Image{
ImageId: "some-image",
UserId: "some-user",
ImagePath: "some-path",
}
msg, err := json.Marshal(image)
if err != nil {
log.Fatalf("Failed to marshal image to json.")
}
File system can be handled by different packages. For now I'm using the os
package.
import "os"
file, err := os.Create("file.txt")
if err != nil {
...
}
defer file.Close()
First function that is run in a program, runs before main()
.
package main
import(...)
func init() {
...do stuff...
}
func main() {
... do stuff after init...
}
Assumes:
- a proto file exists
- Use the protoc compiler to generate the golang files.
protoc -I ./ some-file.proto --go_out=plugins=grpc:.
- Import the generated grpc files and use them to create a GRPC Server as follows:
package gatewayserver
import (
proto "github.com/simplebank/orders_service/grpc"
"golang.org/x/net/context"
)
// Server is the implementation struct for the proto file describing the endpoints
// callable from the api_gateway service.
type Server struct{}
// CreateOrder handles a request from the api_gateway to place an order on the
// market.
func (s *Server) CreateOrder(context.Context, *proto.OrderRequest) (*proto.OrderResponse, error) {
return &proto.OrderResponse{Status: "Order Placed"}, nil
}
// GetAllOrders returns a list of all orders for a particular user.
func (s *Server) GetAllOrders(context.Context, *proto.OrderStatusAllRequest) (*proto.OrderStatusAllResponse, error) {
orderStatusResponse := &proto.OrderStatusResponse{
OrderId: "1",
UserId: "1",
Symbol: "BTC",
Amount: 134,
Status: "Pending",
}
allResponses := []*proto.OrderStatusResponse{orderStatusResponse}
return &proto.OrderStatusAllResponse{Orders: allResponses}, nil
}
Getting env variables.
import "os"
os.Getenv("SOME_ENV")
Currently using logrus.
The below code example shows the current practice on setting up a custom log format with logrus to match a projects logging filter (logstash).
package main
import (
"fmt"
log "github.com/sirupsen/logrus"
"os"
"strings"
)
// Global logger used for logging in this service.
var logger *log.Logger
// Wraps the log.TextFormatter struct but uses a custom Format function according
// to the projects logstash filter.
type logStashFormatter struct {
log.TextFormatter
}
// Format uses a custom implementation to match the logstash filter.
// Custom format: `time - name_of_service - log_level - message`.
// TODO (ccdle12): pull service name from environment.
func (l *logStashFormatter) Format(entry *log.Entry) ([]byte, error) {
return []byte(
fmt.Sprintf("%s - fees_service - %s - %s",
entry.Time.Format(l.TimestampFormat),
strings.ToUpper(entry.Level.String()),
entry.Message),
), nil
}
// Entry point, initializes the global logger variable with the custom formatter.
func init() {
logger = &log.Logger{
Out: os.Stdout,
Level: log.DebugLevel,
Formatter: &logStashFormatter{log.TextFormatter{
TimestampFormat: "2006-01-02 15:04:05",
FullTimestamp: true},
},
}
}
Creates a TCP server, creates a TCP server that implements the Listener interface.
import "net"
listener, err := net.Listen("tcp", "0.0.0.0:5000")
if err != nil {
log.Fatalf("failed")
}
type SomeStruct struct {
*fields*
}
Methods for structs are identified by:
type Something struct {...}
func (s *Something) someMethod() float32 {}
someSlice := []string{"Hello", "World"}
someSlice = append(someSlice, "!")
In golang test files should have the following format:
something_test.go
To run tests in test folder:
$ go test
Example test file:
- Imports the testing package:
"testing"
- Test functions start with prefix
TestSomething(t *testing.T)
- Test functions declared in the parameter of the function
(t *testing.T)
- Test assertions
if something != expected { t.Errorf(some_message)}
. Errorf will cause a failed test.
package main
import (
"testing"
)
func TestCalcPrice(t *testing.T) {
something := &Something{Quantity: 5, Price: 6}
expected := float32(29.400002)
if something.CalcPrice() != expected {
t.Errorf("expected: %v received: %v", expected, something.CalcPrice())
}
}
Alpine containers do not have gcc.
When running tests we need to run as :
go test -v -vet=off ./folder-path/...
import "time"
time.Sleep(5 * time.Second)