https://slides.com/ricardolonga/golang/live
(SOA, Big Data, Arquitetura, Android, Java, Testes e Go)
* - Se faz "quack" e anda como um pato, então é um pato.
1. mkdir -p $home/go/src/github.com/ricardolonga
2. cd $home/go/src/github.com/ricardolonga
3. git clone https://github.com/ricardolonga/workshop-go.git
4. Abra o diretório do projeto no VS Code.
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
/$HOME/go/src/github.com/<your_user>/workshop-go/cmd/server
/$HOME/go/src/github.com/<your_user>/workshop-go/cmd/server/main.go
package main
import "fmt"
func main() {
fmt.Print("Olá mundo!")
}
go run main.go
.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
func main() { fmt.Println("Hello!") }
func main() { if crazy { fmt.Println("Hello!") } }
func main() { if crazy { fmt.Println("Hello!") } }
func main() { if err := human("developer"); err != nil { fmt.Printf("Crazy: %q", err) }
fmt.Printf("undefined err: %q", err)
}
func main() {
var roberta Pessoa
}
func main() {
roberta := Pessoa{}
}
func main() { for i, v := range valores { // ... } }
func main() { for i, v := range valores { // ... } }
for { break }
for _, rule := range rules {
operation, _ := rule.IsMatched(uri)
if operation == "" {
continue
}
// ... more code ...
}
teste: for { break teste }
func soma(a int, b int) int {
return a + b
}
func soma(a, b int) int { return a + b }
func soma(a, b int) int {
var x int
return a + b
}
func main() { for _, v := range valores { // ... } }
func getName(uf string) (string, error) { name, exists := estados[uf] if exists { return name, nil } return "", errors.New("Not found.") }
func getName(uf string) (name string, err error) {
var exists bool
name, exists = estados[uf]
if exists {
return
}
err = errors.New("Not found.")
return
}
type Pessoa struct { Nome string Idade int }
type Pessoa struct { Nome string } func (me Pessoa) getNome() string { return me.Nome }
type Pessoa struct {
Nome string
}
func (me Pessoa) GetNome() string {
return me.Nome
}
type Pessoa struct { Nome string }
func (me *Pessoa) SetNome(nome string) {
me.Nome = nome
}
pessoa := Pessoa{"Ricardo", 29}
pessoa := Pessoa{Nome: "Ricardo", Idade: 29}
pessoa := &Pessoa{"Ricardo", 29}
pessoa := &Pessoa{Nome: "Ricardo", Idade: 29}
type Pessoa struct {}
func (me *Pessoa) Andar() {}
func (me *Pessoa) Correr() {}
type Itens []string func (me *Itens) Categorizar() { // ... }
func main() { []string(Itens{"Pneu", "Freio"}) Itens([]string{"Pneu", "Freio"}) }
func DoSomething(v interface{}) { // ... }
package main
import "fmt"
func main() {
var i interface{}
describe(i)
i = 42
describe(i)
i = "hello"
describe(i)
}
func describe(i interface{}) {
fmt.Printf("(%v, %T)\n", i, i)
}
1. Crie o arquivo /domain/user.go.
2. Crie a estrutura User com Name (string) e Age (int).
3. Instancie User na função main() e execute:
go run main.go
4. O resultado dos comandos abaixo devem ser:
fmt.Printf("User: %s\n", user) ----------- User: {Ricardo %!s(int=31)}
fmt.Printf("User: %v\n", user) ----------- User: {Ricardo 31}
fmt.Printf("User: %+v\n", user) ----------- User: {Name:Ricardo Age:31}
var a [3]int numeros := [5]int{1, 2, 3, 4, 5} nomes := [...]string{"Ricardo", "Longa"}
numeros := [5]int{1, 2, 3, 4, 5}
primeiro := numeros[0]
numeros := [5]int{1, 2, 3, 4, 5}
ultimo := numeros[len(numeros) - 1]
1. Adicione um array em User chamado Phones.
2. Evolua a instanciação na main().
3. O resultado deve ser:
fmt.Printf("User: %+v", user) ----------- User: {Name:Ricardo Age:31 Phones:[48988792345]}
var a []int numeros := []int{1, 2, 3, 4, 5} nomes := []string{"Ricardo", "Longa"}
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
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]
// b = [1 2 3 4 5 6 7] result := append(b[:3], b[4:]...) fmt.Println(result) // [1 2 3 5 6 7]
numeros := []int{1, 2, 3}
for i, v := range numeros {
numeros[i] = v * 2
}
numeros := []int{1, 2, 3}
for i := range numeros { numeros[i] *= 2 }
numeros := []int{1, 2, 3}
for range numeros { // ... }
1. Altere o tipo do atributo Phones para slice.
2. Corrija a instanciação na main().
3. O resultado deve ser:
fmt.Printf("User: %v", user) ----------- User: {Name:Ricardo Age:31 Phones:[48988792345]}
4. Percorra os telefones e printe cada um na saída:
Phone [0]: 48988792345
Phone [1]: 4832220978
map1 := map[int]string{} map2 := make(map[int]string)
map3 := make(map[int]string, 400)
somenteComMake := make(map[int]string, 40)
_, encontrado := estados["sc"] if encontrado { // ... }
delete(estados, "sc")
for sigla, estado := range estados { // ... }
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 [siblings]: [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
type Pessoa interface {
Andar()
Correr()
Cumprimentar() string
}
type Homem struct {}
func (me *Homem) Andar() {}
func (me *Homem) Correr() {} func (me *Homem) Cumprimentar() string { return "Olá" }
1. Entre no arquivo fmt/print.go da Standard Library.
2. Observe a interface chamada Stringer.
3. Nossa estrutura User implementa Stringer?
type User struct { Name string Age int } user := User{"Ricardo", 29}
json, _ := json.Marshal(user)
fmt.Println(string(json))
{ "Name": "Ricardo", "Age": 29 }
{ "Name": "Ricardo", "Age": 29 }
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
}
j := []byte(`{
"name":"Ricardo",
"age":29
}`)
var user User
json.Unmarshal(j, &user)
j := []byte(`{ "name":"Ricardo", "age":29, "lastname": "Longa" }`) var user User
json.Unmarshal(j, &user)
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"}
func main() {
router := gin.New()
v1 := router.Group("/v1")
v1.GET("/users/:id", func(c *gin.Context) {
userID := c.Param("id")
c.JSON(http.StatusOK, gin.H{
"id": userID,
})
})
v1.POST("/users", func(c *gin.Context) {
var user *domain.User
if err := c.BindJSON(&user); err != nil {
return
}
c.AbortWithStatus(http.StatusCreated)
})
router.Run() // listen and server on 0.0.0.0:8080
}
1. Construa um web server Rest para simularmos um CRUD de User. Você pode implementar utilizar o Gin.
[POST] /v1/users
[GET/PUT/DELETE] /v1/users/:id
/workshop-go/cmd/server/main.go
/workshop-go/domain/user.go
/workshop-go/domain/user_test.go
func TestExport(t *testing.T) { t.Fail() }
/workshop-go go test ./...
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)
}
}
1. Crie um método de teste para validar o retorno do método String() de User.
func TestWithRecorder(t *testing.T) {
router := gin.New()
router.POST("/users", postPessoa)
response := httptest.NewRecorder()
endpoint := "/users"
body := []byte(`{"name": "Ricardo Longa"}`)
req, _ := http.NewRequest("POST", endpoint, bytes.NewReader(body))
router.ServeHTTP(response, req)
assert.Equal(t, http.StatusCreated, response.Code)
}
func TestWithHttpServer(t *testing.T) {
router := gin.New()
router.POST("/users", postPessoa)
server := httptest.NewServer(router)
defer server.Close()
URL, _ := url.Parse(server.URL)
endpoint := fmt.Sprintf("%s/users", URL)
body := []byte(`{"name": "Ricardo Longa"}`)
req, _ := http.NewRequest("POST", endpoint, bytes.NewReader(body))
res, _ := http.DefaultClient.Do(req)
assert.Equal(t, http.StatusCreated, res.StatusCode)
}
1. Crie um método de teste para validar a criação de um User via POST.
func postPessoa(userService UserService) func(c *gin.Context) {
return func(c *gin.Context) {
user := &User{}
if err := c.BindJSON(user); err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return
}
if err := userService.Save(user); err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
c.JSON(http.StatusCreated, user)
}
}
type UserService interface { Save(user *User) error }
type UserServiceImpl struct {}
func (usi *UserServiceImpl) Save(user *User) error {
return nil
}
func main() { r := gin.New() userService := &UserServiceImpl{} r.POST("/users", postUser(userService)) r.Run() }
type UserServiceMock struct {
SaveFn func(user *User) error
}
func (usm *UserServiceMock) Save(user *User) error {
return usm.SaveFn(user)
}
func TestWithHttpServer(t *testing.T) { userServiceMock := &UserServiceMock{ SaveFn: func(user *User) error { return nil }, } router := gin.New() router.POST("/users", postUser(userServiceMock)) server := httptest.NewServer(router) req, _ := http.NewRequest("POST", endpoint, bytes.NewReader(body)) res, _ := http.DefaultClient.Do(req) assert.Equal(t, http.StatusCreated, res.StatusCode) }
1. Passe para o controller uma instância de um service (implementação de uma interface).
2. Implemente os testes com mocks.
?
?
?
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)
}
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")
}
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]
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")
}
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) } }
select {
case v1 := <-canal1:
// ...
case v2 := <-canal2:
// ...
default:
// ...
}
1. Vamos escrever um programa que, dada uma lista de números, separa-os em duas listas distintas de pares e ímpares.
2. Vamos utilizar canais para receber os números separados e também para saber quando a separação foi concluída.
nums := []int{1, 23, 42, 5, 8, 6, 7, 4, 99, 100}
---
go run main.go
Ímpares: [1 23 5 7 99] | Pares: [42 8 6 4 100]
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")
}
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
go test -coverpkg=all ./...
CGO_ENABLED=0 go build -v -a -installsuffix cgo -o pessoas ./cmd/server
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