Workshop sobre Go:

Do básico ao avançado em oito horas

Quem sou eu?

Ricardo Longa

Graduado em Sistemas de Informação

Pós-graduado em Engenharia de Software

Professor de cursos técnicos, graduação e pós-graduação

Effective Software Craftsman at Neoway Business Solutions

Palestrante desde 2013

(SOA, Big Data, Arquitetura, Android, Java, Testes e Go)

Artigo publicado na Devmedia sobre Design Patterns

Coordenador da trilha Go do TDC São Paulo (2016) / Florianópolis (2018)

Um dos fundadores e organizadores da GopherCon Brasil

Quem são vocês?

  • Nome?

  • Naturalidade?

  • Profissão atual e onde trabalhou?

  • Linguagens que já trabalhou?

  • Como conheceu Go?

  • Algum hobby ou curiosidade?

Cronograma + Bônus

Bônus (part I):

  • Boas práticas na estruturação de pacotes

  • Makefile para a orquestração de tarefas

Cronograma:

  • Sintaxe básica

  • Arrays

  • Slices

  • Maps

  • Structs

  • Interfaces

  • JSON

  • Web Server

  • Testes unitários

  • Testes de Rest APIs

  • Mocks

  • Goroutines

  • Channels

  • WaitGroup

Bônus (part II):

  • Cobertura de pacotes

  • Containerização (c/ Docker)

Não vamos ver...

Fora do escopo:

  • O que é um servidor web?

  • O que é REST ou RESTful?

  • O que é JSON?

  • O que é orientação a objetos?

  • O que é polimorfismo/herança?

  • O que é interface?

  • O que é Docker?

Dúvidas?

Um pouco de história...

Em 2007 a Google deu início ao Go, como um projeto interno, desenvolvido por Rob Pike, Ken Thompson e Robert Griesemer.

Em novembro de 2009 a Google abriu o código fonte da linguagem.

Go permite a escrita de programas procedurais, orientados a objetos ou funcionais.

Uma linguagem de tipagem forte e estática, com inferência de tipos e duck typing*.

* - Se faz "quack" e anda como um pato, então é um pato.

A concorrência em Go é um dos maiores diferenciais da linguagem, veremos em detalhes.

Instalando o Go

Instruções oficiais e dicas para validação

IDE

Lista de IDE's:

Estruturação de pacotes

Resultado de 3 anos de algumas variações/experimentos.

Lembra muito DDD

(Domain Driven Design)

User Interface

Application

Domain

Infrastructure

cmd/server

internal/server/http

domain

internal/storage/mongo

Entrypoint

Entrypoint

Application (controllers)

User controller

Entrypoint

Domain (business rules)

Application (controllers)

Entity/Service/Repo Interface

Service implementation

User controller

Entrypoint

Domain (business rules)

Application (controllers)

Infrastructure (repository)

Entity/Service/Repo Interface

Service implementation

User repository

User controller

Entrypoint

Domain (business rules)

Application (controllers)

Infrastructure (repository)

Entity/Service/Repo Interface

Service implementation

User repository

User controller

Avoid imports

Entrypoint

Domain (business rules)

Application (controllers)

Infrastructure (repository)

Entity/Service/Repo Interface

Service implementation

User repository

User controller

Avoid imports

Vendor folder

Entrypoint

Domain (business rules)

Application (controllers)

Infrastructure (repository)

Entity/Service/Repo Interface

Service implementation

User repository

User controller

Avoid imports

Vendor folder

Orchestrator file

Fontes do workshop

Exercício 01

2. Clone em: $HOME/go/src/github.com/<your_user>

3. Abra o diretório do projeto no VS Code.

Nosso 1º Hello Go...

Crie o diretório:

/$HOME/go/src/hellogo

Crie o arquivo:

/$HOME/go/src/hellogo/hello.go

Cole este conteúdo:

Vá para $HOME/src e execute:

go run hellogo/hello.go

Orquestração de tarefas

Criamos um arquivo na raiz chamado Makefile, sem extensão.

"Composto por regras (rules). Cada regra contém 3 elementos. O alvo, os pré-requisitos e as instruções."

https://blog.pantuza.com/tutoriais/como-funciona-o-makefile

.DEFAULT_GOAL := all

NAME = pessoas

clean: ## Remove all generated files
    -@rm -f $(NAME); \

goget: clean ## Install external vendor packages
    govendor sync -insecure +external

build: goget ## Build static executable
    CGO_ENABLED=0 go build -v -a -installsuffix cgo -o $(NAME) ./cmd/server
all: build ## Do tests, build and generate image

"Sua estrutura de regras define um pipeline (sequência) de procedimentos para a produção de um binário final."

"O programa make interpreta o conteúdo do Makefile e executa as regras lá definidas."

https://pt.wikibooks.org/wiki/Programar_em_C/Makefiles

Sintaxe básica

Não precisamos finalizar nosso código com ponto e vírgula.

func main() {
     fmt.Println("Hello!")
}

Parênteses são opcionais nos if's.

func main() {
     if crazy {     
          fmt.Println("Hello!")
     }
}

Mas todo if deve abrir e fechar chaves.

func main() {
     if crazy {     
          fmt.Println("Hello!")
     }
}

Variáveis somente no escopo do if.

func main() {
     if err := human("developer"); err != nil {     
          fmt.Printf("Crazy: %q", err)
     }
     
     fmt.Printf("undefined err: %q", err)
}

A expressão lógica em um if deve ser sempre verdadeiro ou falso.

Definimos variáveis assim, a tipagem a direita:

func main() {
     var roberta Pessoa
}

Go possui inferência de tipos:

func main() {
     roberta := Pessoa{}
}

Go possui apenas uma estrutura de repetição:

func main() {
     for i, v := range valores {
          // ...
     }
}

Range sobre uma coleção devolve o índice e a cópia do valor:

func main() {
     for i, v := range valores {
          // ...
     }
}

Loop infinito e break:

for {
     break
}

Existe também continue:

for _, rule := range rules {
     operation, _ := rule.IsMatched(uri)
     if operation == "" {
          continue
     }
     // ... more code ...
}

Go também permite nomearmos blocos for:

teste:
for {
     break teste
}

O retorno de uma função ou método a direita:

func soma(a int, b int) int {
     return a + b
}

Argumentos do mesmo tipo abreviados:

func soma(a, b int) int {
     return a + b
}

Erro de compilação com variáveis não utilizadas.

func soma(a, b int) int {
     var x int
     return a + b
}

Blank identifier, ignorando um valor...

func main() {
     for _, v := range valores {
          // ...
     }
}

Múltiplos retornos não nomeados...

func getName(uf string) (string, error) {
    name, exists := estados[uf]
    if exists {
        return name, nil
    }
    
    return "", errors.New("Not found.")
}

Múltiplos retornos nomeados...

func getName(uf string) (name string, err error) {
    var exists bool

    name, exists = estados[uf]
    if exists {
        return name, nil
    }
    
    return "", errors.New("Not found.")
}

Go não possui classes, definimos structs.

type Pessoa struct {
     Nome string
}

Os métodos não possuem um receptor implícito, como self ou this em outras linguagens.

Definimos um método privado para Pessoa assim:

type Pessoa struct {
     Nome string
}

func (me Pessoa) getNome() string {

    return me.Nome
}

Definimos um método público para Pessoa assim:

type Pessoa struct {
     Nome string
}
func (me Pessoa) GetNome() string {
    return me.Nome
}

Argumentos são sempre cópias, exceto slices, maps e channels.

type Pessoa struct {
     Nome string
}
func (me *Pessoa) SetNome(nome string) {
     me.Nome = nome
}

Exercício 02

1. Crie o arquivo /cmd/server/main.go.

2. Crie o arquivo /domain/user.go.

3. Crie a estrutura User com Name (string) e Age (int).

4. Instancie User na função main() e execute:

go run main.go pessoa.go

5. O resultado de                                        deve ser:

Name: Ricardo Longa - Age: 31
fmt.Printf("%s", user)

Arrays

Arrays são coleções do mesmo tipo e de tamanho fixo e invariável.

var a [3]int
numeros := [5]int{1, 2, 3, 4, 5}
nomes := [...]string{"Ricardo", "Longa"}

Primeiro elemento na posição 0.

numeros := [5]int{1, 2, 3, 4, 5}
primeiro := numeros[0]

Último elemento na posição len(array) - 1.

numeros := [5]int{1, 2, 3, 4, 5}
ultimo := numeros[len(numeros) - 1]

Exercício 03

1. Adicione um array em User chamado Phones.

2. Evolua a instanciação na main().

3. O resultado de                                       deve ser:

Phones: [48998791340 4832220978] - Celular: 48998791340 - Residencial: 4832220978
fmt.Printf("%s", user)

Slices

Um abstração em cima de Arrays, onde não definimos o tamanho da coleção.

var a []int
numeros := []int{1, 2, 3, 4, 5}
nomes := []string{"Ricardo", "Longa"}

Podemos utilizar a função builtin: make([]T, len, cap) []T

b := make([]int, 10)

fmt.Println(b, len(b), cap(b))

[0 0 0 0 0 0 0 0 0 0] 10 10

 

b = make([]int, 10, 20)

fmt.Println(b, len(b), cap(b))

[0 0 0 0 0 0 0 0 0 0] 10 20

Adicionamos elementos em um slice com a função builtin: append([]T, ...T) []T

b := []int{1, 2, 3, 4}

fmt.Println(b) // [1 2 3 4]

 

b = append(b, 5, 6, 7)

fmt.Println(b) // [1 2 3 4 5 6 7]

Para remover a 4ª posição, precisamos fatiar o slice.

// b = [1 2 3 4 5 6 7]

result := append(b[:3], b[4:]...)
fmt.Println(result) // [1 2 3 5 6 7]

A grande vantagem: quando utilizados como argumento ou retorno, são passados por referência.

Para iterar utilizamos o operador range.

numeros := []int{1, 2, 3}
for i, v := range numeros {
     numeros[i] = v * 2
}

Podemos omitir a cópia do valor...

numeros := []int{1, 2, 3}
for i := range numeros {
     numeros[i] *= 2
}

Podemos omitir a posição e a cópia do valor...

numeros := []int{1, 2, 3}
for range numeros {
     // ...
}

Exercício 04

1. Altere o tipo do atributo Phones para slice.

2. Corrija a instanciação na main().

3. O resultado de                                       deve ser:

Name: Ricardo Longa - Age: 31
fmt.Printf("%s", user)

4. Percorra os telefones e printe cada um na saída:
Phone [0]: 48988792345
Phone [1]: 4832220978

Maps

Coleção de pares chave-valor sem ordem definida. As chaves devem ser do mesmo tipo e únicas.

Podemos criar um map da forma literal ou com make(map[type]type, len).

map1 := map[int]string{}
map2 := make(map[int]string)
map3 := make(map[int]string, 400)

Sempre que possível, declare o tamanho do map. Performance!

somenteComMake := make(map[int]string, 40)

Verificando se a chave existe no map:

_, encontrado := estados["sc"]
if encontrado {
     // ...
}

Para remover uma chave existente no map, utilize a função builtin delete(map, "key"):

delete(estados, "sc")

Podemos iterar as chaves e os valores com range:

for sigla, estado := range estados {
     // ...
}

Exercício 05

1. Crie um construtor para User.

2. Corrija a instanciação na main().

4. Percorra os parentes e printe cada um na saída:

Grau de parentesco [father]: Luigi
Grau de parentesco [mother]: Rosana
Grau de parentesco [brothers]: [Fernanda Gian]

3. Crie um mapa pra guardar o nome dos parentes pelo grau de parentesco.

5. Verifique se existe a chave "father" e então printe o valor.
Father: Luigi

Structs

Go tem suporte LIMITADO a Orientação a Objetos. Não há suporte a herança mas sim a composição.

Criação de novos tipos:

type Itens []string

func (me *Itens) Categorizar() {
     // ...
}

Criamos um novo tipo baseado em um []string, porém Itens não pode ser passado como []string ou vice-versa. Na prática, são tipos diferentes.

Type conversion - T(x)

func main() {
     []string(Itens{"Pneu", "Freio"})
     Itens([]string{"Pneu", "Freio"})
}

Uma Struct é um conjunto de variáveis que definem um novo tipo.

type Pessoa struct {
     Nome string
     Idade int
}

Podemos instanciar Pessoa destas formas:

pessoa := Pessoa{"Ricardo", 29}
pessoa := Pessoa{Nome: "Ricardo", Idade: 29}

Podemos obter o ponteiro da instância com &:

pessoa := &Pessoa{"Ricardo", 29}
pessoa := &Pessoa{Nome: "Ricardo", Idade: 29}

Podemos definir métodos para Pessoa:

type Pessoa struct {}

func (me *Pessoa) Andar() {}
func (me *Pessoa) Correr() {}

Interfaces

Um contrato de métodos comuns a um ou mais tipos, similar ao Java, por exemplo.

Contudo, não precisamos da palavra implements. Basta que o tipo defina todos os métodos da Interface.

Uma interface:

type Pessoa interface {
     Andar()
     Correr()
     Cumprimentar() string
}

Uma implementação:

type Homem struct {}

func (me *Homem) Andar() {}
func (me *Homem) Correr() {}
func (me *Homem) Cumprimentar() string {
     return "Olá"
}

Duck typing - se faz "quack" e anda como um pato, provavelmente é um pato.

Exercício 06

1. Entre no arquivo fmt/print.go da Standard Library.

2. Observe a interface chamada Stringer.

3. Nossa estrutura User implementa Stringer?

JSON

"Quase qualquer tipo válido em Go pode ser serializado, exceto Channels, Functions e Complex." (Caio Filipini, p. 153)

Serializando em JSON:

type User struct {
     Name string 
     Age int
}

user := User{"Ricardo", 29}

 

json, _ := json.Marshal(user)
fmt.Println(string(json))

O resultado em JSON:

{
     "Name": "Ricardo",
     "Age": 29
}
{
     "Name": "Ricardo",
     "Age": 29
}

E esses atributos?

Struct tags

type User struct {
     Name string `json:"name"` 
     Age int `json:"age"`
}

user := User{"Ricardo", 29}

 

json, err := json.Marshal(user)
fmt.Println(string(json))
{
     "name": "Ricardo",
     "age": 29
}

Resultado

Desserializando um JSON:

j := []byte(`{
     "name":"Ricardo",
     "age":29
}`)

var user User
json.Unmarshal(j, &user)

Desserializando um JSON:

j := []byte(`{
     "name":"Ricardo",
     "age":29,
     "lastname": "Longa"
}`)

var user User
json.Unmarshal(j, &user)

Exercício 07

1. Na função main(), serialize User e printe:

{"Name":"Ricardo Longa","Age":31,"Phones":["48987794530","4832220978"],"Relatives":{"brothers":["Fernanda","Gian"],"father":"Luigi","mother":"Rosana"}}

2. Corrija os atributos para serializarem em minúsculo e omita o atributo Relatives do resultado:

{"name":"Ricardo Longa","age":31,"phones":["48987794530","4832220978"]}

3. Instancie um novo User informando apenas o name. Faça com que os atributos não informados não sejam serializados:

{"name":"Fernanda Rodrigues"}

Web Server

package main

import "github.com/gin-gonic/gin"

func main() {
    r := gin.New()
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong",
        })
    })
    r.Run() // listen and server on 0.0.0.0:8080
}

Exercício 08

1. Construa um web server Rest para simularmos um CRUD de User. Você pode implementar nativo ou utilizar o Gin.

[POST] /v1/users

[GET/PUT/DELETE] /v1/users/:id

Testes unitários

Os arquivos de testes devem ser nomeados com o sufixo _test.go.

/$HOME/go/src/hellogo/main.go
/$HOME/go/src/hellogo/util.go
/$HOME/go/src/hellogo/util_test.go

Arquivos com o sufixo _test.go serão ignorados na fase de build.

As funções de testes devem conter o prefixo Test e receber um ponteiro de testing.T.

func TestExport(t *testing.T) {
     t.Fail()
}

Para rodar os testes utilizamos o comando go test.

$HOME/go/src/hellogo go test

Podemos importar apenas o pacote assert do projeto Testify. Easy assertions!

 

github.com/stretchr/testify/assert
import "github.com/stretchr/testify/assert"

 

func TestSomething(t *testing.T) {
     assert.Equal(t, 123, 123, "they should be equal")
     assert.NotEqual(t, 123, 456, "they should not be equal")
     assert.Nil(t, object)
     if assert.NotNil(t, object) {
          assert.Equal(t, "Something", object.Value)
     }
}

Exercício 09

1. Crie um método de testes para validar o retorno do método String() de User.

Testes de Rest API's

Podemos facilmente testar APIs Rest utilizando os pacotes net/http e net/http/httptest.

Criamos um http.NewRequest(), um httptest.NewRecorder() e passamos para ServeHTTP(res, req) do Gin.

func TestWithRecorder(t *testing.T) {
    router := gin.New()
    router.POST("/pessoas", postPessoa)

    response := httptest.NewRecorder()
    endpoint := "/pessoas"
    body := []byte(`{"nome": "Ricardo Longa"}`)

    req, _ := http.NewRequest("POST", endpoint, bytes.NewReader(body))

    router.ServeHTTP(response, req)
    assert.Equal(t, http.StatusCreated, response.Code)
}

Você também pode subir um servidor com o pacote net/http/httptest, realizando uma requisição HTTP, de fato.

func TestWithHttpServer(t *testing.T) {
    router := gin.New()
    router.POST("/pessoas", postPessoa)

    server := httptest.NewServer(router)
    defer server.Close()

    URL, _ := url.Parse(server.URL)
    endpoint := fmt.Sprintf("%s/pessoas", URL)
    body := []byte(`{"nome": "Ricardo Longa"}`)

    req, _ := http.NewRequest("POST", endpoint, bytes.NewReader(body))
    res, _ := http.DefaultClient.Do(req)
    assert.Equal(t, http.StatusCreated, res.StatusCode)
}

Exercício 10

1. Crie um método de testes para validar o retorno do método String() de User.

Testes c/ Mocks

Você deseja testar seu controller. Porém, o mesmo depende de um service. Como você faria para testar o controller de forma isolada?

Premissa nº 1: injeção de dependências.

Premissa nº 2: uso de Interfaces.

func postPessoa(pessoaService PessoaService) func(c *gin.Context) {
    return func(c *gin.Context) {
        pessoa := &Pessoa{}

        if err := c.BindJSON(pessoa); err != nil {
            c.AbortWithError(http.StatusBadRequest, err)
            return
        }

        if err := pessoaService.Save(pessoa); err != nil {
            c.AbortWithError(http.StatusInternalServerError, err)
            return
        }

        c.JSON(http.StatusCreated, pessoa)
    }
}
type PessoaService interface {
    Save(pessoa *Pessoa) error
}
type PessoaServiceImpl struct {}

func (psi *PessoaServiceImpl) Save(pessoa *Pessoa) error {
    return nil
}
func main() {
    r := gin.New()

    pessoaService := &PessoaServiceImpl{}
    r.POST("/pessoas", postPessoa(pessoaService))

    r.Run()
}

E como fica o teste?

type PessoaServiceMock struct {
    SaveFn func(pessoa *Pessoa) error
}

func (psm *PessoaServiceMock) Save(pessoa *Pessoa) error {
    return psm.SaveFn(pessoa)
}
func TestWithHttpServer(t *testing.T) {
    pessoaServiceMock := &PessoaServiceMock{
        SaveFn: func(pessoa *Pessoa) error {
            return nil
        },
    }

    router := gin.New()
    router.POST("/pessoas", postPessoa(pessoaServiceMock))

    server := httptest.NewServer(router)

    req, _ := http.NewRequest("POST", endpoint, bytes.NewReader(body))

    res, _ := http.DefaultClient.Do(req)
    assert.Equal(t, http.StatusCreated, res.StatusCode)
}

Exercício 11

1. Passe para o controller uma instância de um service (implementação de uma interface).

2. Implemente os testes com mocks.

Goroutines

"Goroutines are functions or methods that run concurrently with other functions or methods." https://golangbot.com/goroutines/

"Goroutines are extremely cheap when compared to threads."

https://golangbot.com/goroutines/

"The Goroutines are multiplexed to fewer number of OS threads." https://golangbot.com/goroutines/

"Goroutines communicate using channels." https://golangbot.com/goroutines/

package main

import "fmt"

func hello() {
    fmt.Println("Hello world goroutine.")
}

func main() {
    go hello()
    fmt.Println("Main function.")
}
package main

import (
    "fmt"
    "time"
)

func hello() {
    fmt.Println("Hello world goroutine.")
}

func main() {
    go hello()
    fmt.Println("Main function.")
    time.Sleep(1 * time.Second)
}

Exemplo com múltiplas Goroutines.

func numbers() {
    for i := 1; i <= 5; i++ {
        time.Sleep(250 * time.Millisecond)
        fmt.Printf("%d ", i)
    }
}

func alphabets() {
    for i := 'a'; i <= 'e'; i++ {
        time.Sleep(400 * time.Millisecond)
        fmt.Printf("%c ", i)
    }
}

func main() {
    go numbers()
    go alphabets()
    time.Sleep(3000 * time.Millisecond)
    fmt.Println("main terminated")
}

"Goroutines run in the same address space, so access to shared memory must be synchronized." https://tour.golang.org/concurrency/1

func main() {
    var state = make(map[int]int)
    var mutex = &sync.Mutex{}
    var readOps uint64
    var writeOps uint64
    
    for r := 0; r < 100; r++ {
        go func() {
            total := 0
            for {
                key := rand.Intn(5)
                mutex.Lock()
                total += state[key]
                mutex.Unlock()
                atomic.AddUint64(&readOps, 1)
                time.Sleep(time.Millisecond)
            }
        }()
    }
    for w := 0; w < 10; w++ {
        go func() {
            for {
                key := rand.Intn(5)
                val := rand.Intn(100)

                mutex.Lock()
                state[key] = val
                mutex.Unlock()
                
                atomic.AddUint64(&writeOps, 1)

                time.Sleep(time.Millisecond)
            }
        }()
    }    
    time.Sleep(time.Second)
    
    readOpsFinal := atomic.LoadUint64(&readOps)
    fmt.Println("readOps:", readOpsFinal)
    
    writeOpsFinal := atomic.LoadUint64(&writeOps)
    fmt.Println("writeOps:", writeOpsFinal)
   
    mutex.Lock()
    fmt.Println("state:", state)
    mutex.Unlock()
}

 

readOps: 77691
writeOps: 7785
state: map[4:27 3:78 0:32 2:53 1:37]

Channels

Você pode imaginar um channel como sendo um tubo utilizado para a comunicação entre goroutines.

"Sends and receives to a channel are blocking by default."

package main

import (
    "fmt"
)

func hello(done chan bool) {
    fmt.Println("Hello world goroutine")
    done <- true
}

func main() {
    done := make(chan bool)
    go hello(done)
    <-done
    fmt.Println("main function")
}

Podemos criar buffered channels, onde o envio é blocked quando o canal estiver cheio.

func write(ch chan int) {
    for i := 0; i < 5; i++ {
        ch <- i
        fmt.Println("successfully wrote", i, "to ch")
    }

    close(ch)
}

func main() {
    ch := make(chan int, 2)
    go write(ch)
    time.Sleep(2 * time.Second)
   
    for v := range ch {
        fmt.Println("read value", v, "from ch")
        time.Sleep(2 * time.Second)
    }
}

 

"Only the sender should close a channel, never the receiver."
https://tour.golang.org/concurrency/4

"Closing is only necessary when the receiver must be told there are no more values coming."

Wait Group

"A WaitGroup is used to wait for a collection of Goroutines to finish executing."

func process(i int, wg *sync.WaitGroup) {
    fmt.Println("started Goroutine ", i)
    time.Sleep(2 * time.Second)
    fmt.Printf("Goroutine %d ended\n", i)
    wg.Done()
}

func main() {
    no := 3
    var wg sync.WaitGroup
    for i := 0; i < no; i++ {
        wg.Add(1)
        go process(i, &wg)
    }

    wg.Wait()

    fmt.Println("All go routines finished executing")
}

Bônus

Cobertura de pacotes

go get golang.org/x/tools/cmd/cover
go get github.com/axw/gocov/gocov
go get gopkg.in/matm/v1/gocov-html
./scripts/cover.sh
#!/usr/bin/env bash
export MONGO_URL=$(docker inspect -f '{{.NetworkSettings.Networks.creditmanager_default.IPAddress}}' creditmanager_mongodb_1)
echo "mode: set" > full_coverage.out
for pkg in $(go list ./... | grep -v /vendor/); do
    go test -v -failfast -cpu 1 -coverprofile=coverage.out -covermode=set $pkg
        if [ $? -ne 0 ]; then
            exit 1
        fi
    grep -h -v "^mode: set" coverage.out >> full_coverage.out
done

grep -v "_mock" full_coverage.out >> filtered_coverage.out
go tool cover -func filtered_coverage.out
#go tool cover -html filtered_coverage.out -o report/index.html
mkdir -p gitlab-pages/coverage
gocov convert filtered_coverage.out | gocov-html > gitlab-pages/coverage/index.html
rm -f coverage.out full_coverage.out filtered_coverage.out

exit 0

Containerização

Primeiro precisamos gerar nosso binário.

CGO_ENABLED=0 go build -v -a -installsuffix cgo -o pessoas ./cmd/server

No nosso Dockerfile, herdamos scratch e adicionamos o binário.

FROM scratch

ADD ./pessoas /pessoas

CMD ["/pessoas"]
docker build --no-cache -t pessoas:latest .
docker run -e MONGO_URL=172.25.0.2 -p 8090:8080 pessoas:latest

Dúvidas?

Outros links

Fontes do workshop

Programando em Go

Golang Bot Blog

Go by Example

Awesome Go

Go libraries and Apps

Go cheatsheet

Floripa Gophers (meetup)

Slack (#brazil / #floripa)

GopherCon Brasil 18

Agradecimentos

Apoiador oficial

Revisores técnicos

Obrigado!

Golang

By Ricardo Longa

Loading comments...

More from Ricardo Longa