High Order Functions e Functional Interfaces
Devcamp
16/08/2019
1
Especialista em desenvolvimento
backend.
11 anos em desenvolvimento
Java Certi ed Associate
Go desde 2016
Sobre
2
Idiomático?!
3
Foco em simplicidade de soluções
Sintaxe simples
Uma única (ou poucas) formas de se
fazer algo
Leitura e escrita homogêneas por todo
ecossistema
Por que os exemplos em Go?
4
Idiomático
public class SimpleHashSetExample {
public static void main(String[] args) {
HashSet hSet = new HashSet();
hSet.add(new Integer("1"));
hSet.add(new Integer("2"));
hSet.add(new Integer("3"));
System.out.println("HashSet contains.." + hSet);
}
}
5
Idiomático
class Animal(val name: String)
class Zoo(val animals: List<Animal>) {
operator fun iterator(): Iterator<Animal> {
return animals.iterator()
}
}
fun main() {
val zoo = Zoo(listOf(Animal("zebra"), Animal("lion")))
for (animal in zoo) {
println("Watch out, it's a ${animal.name}")
}
}
6
Funções idiomáticas
7
Domínio
Estrutura das entidades
Composição ou herança
Estrutura de persistência
type Entity struct {
name string
...
}
Regra de negócio
Fluxo e manipulação de entidades
Regra de negócio
Adição ou remoção de etapas de
processamento
func (e Entity) transformedName() string {
return e.name + " with transformation"
}
...
The right tool for the right job
8
Server simples em Go
package main
import (
"log"
"net/http"
)
func main() {
log.Println(NewServer(":8080"))
}
func NewServer(address string) error {
r := http.NewServeMux()
r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("My Web Servern"))
})
r.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("{"success":true}"))
})
srv := &http.Server{
Addr: address,
Handler: r,
}
return srv.ListenAndServe()
}
Server simples em Go + Timeout
func main() {
log.Println(NewServer(":8080", 2*time.Second, 5*time.Second))
}
func NewServer(address string, rt, wt time.Duration) error {
r := http.NewServeMux()
r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("My Web Servern"))
})
r.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("{"success":true}"))
})
srv := &http.Server{
Addr: address,
Handler: r,
ReadTimeout: rt,
WriteTimeout: wt,
}
return srv.ListenAndServe()
}
10
Server simples em Go + Timeout + TLS
func main() {
log.Println(NewServer(":8080", "cert.pem", "key.pem", 2*time.Second, 5*time.Second))
}
func NewServer(address, cert, key string, rt, wt time.Duration) error {
r := http.NewServeMux()
r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("My Web Servern"))
})
r.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("{"success":true}"))
})
srv := &http.Server{
Addr: address,
Handler: r,
ReadTimeout: rt,
WriteTimeout: wt,
}
return srv.ListenAndServeTLS(cert, key)
}
11
Muitas assinaturas para "mesma" função
func NewServer(address string) error {}
func NewServer(address string, rt, wt time.Duration) error {}
func NewServer(address, cert, key string) error {}
func NewServer(address, cert, key string, rt, wt time.Duration) error {}
12
Server simples em Go + Con g
type Config struct {
Address string
CertPath string
KeyPath string
ReadTimeout time.Duration
WriteTimeout time.Duration
}
func main() {
config := Config{":8080", "cert.pem", "key.pem", 2 * time.Second, 5 * time.Second}
log.Println(NewServer(config))
}
13
Server simples em Go + Con g
func NewServer(c Config) error {
r := http.NewServeMux()
r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("My Web Servern"))
})
r.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("{"success":true}"))
})
srv := &http.Server{
Addr: c.Address,
Handler: r,
ReadTimeout: c.ReadTimeout,
WriteTimeout: c.WriteTimeout,
}
return srv.ListenAndServeTLS(c.CertPath, c.KeyPath)
}
14
Con guration structs
Vantagens
Melhora a documentação e entendimento do código.
Permite utilizar "zero values" como regra
Crescimento das con gurações sem quebrar assinatura
15
Con guration structs
Desvantagens
Gera diversos possíveis problemas com "zero values", exemplo: porta 0
c := Config{
"cert.pem", "key.pem"
}
O cenário ideal para "default" value seria aleatoriamente escolher uma porta e não ir
direto para a zero. Mas e se quisermos tentar a porta zero?
Quais campos são obrigatórios e quais são opcionais?
16
Con guration structs
Problema da mutabilidade
config := Config{
":8080", "cert.pem", "key.pem", 2 * time.Second, 5 * time.Second
}
go func() {
config.Address = ":9090"
}()
log.Println(NewServer(config))
Problema da obrigatoridade
func main() {
//Quero o server mais simples possível!!
log.Println(NewServer(????))
}
17
High-Order Function
Função que faz pelo menos uma das duas:
1. Recebe uma (ou mais) funções como parâmetros
2. Retorna uma função
Exemplos:
Cálculo: dy/dx (operador de diferencial) que recebe uma função e retorna a sua
derivada
callbacks em Javascript
http.HandlerFunc(pattern, handlerFunc)
18
High-Order Function + rst class citizen
package main
import "fmt"
//High-Order: retorna uma função
func myFunction() func(int, int) int {
return func(a, b int) int {
return a + b
}
}
func main() {
//first class citizen: atribuir a uma variável
f := myFunction()
//first class citizen: chamar através da variável
soma := f(2, 3)
fmt.Println(soma)
}
19
High-Order Function + Type Alias
package main
import "fmt"
//type alias
type SomaFunc func(int, int) int
//High-Order: retorna uma função
func myFunction() SomaFunc {
return func(a, b int) int {
return a + b
}
}
func main() {
//first class citizen: atribuir a uma variável
f := myFunction()
//first class citizen: chamar através da variável
soma := f(2, 3)
fmt.Println(soma)
}
20
High-Order na Prática
package main
import "net/http"
func main() {
//first class citizen: atribuir a uma variável
f := handlerFunction()
http.HandleFunc("/", f)
http.ListenAndServe(":8080", nil)
}
//High-Order: retorna uma função
func handlerFunction() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Contentn"))
}
}
21
High-order functions como ferramenta
22
Relembrar: server deprecado
func main() {
log.Println(NewServer(":8080", "cert.pem", "key.pem", 2*time.Second, 5*time.Second))
}
func NewServer(address, cert, key string, rt, wt time.Duration) error {
r := http.NewServeMux()
r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("My Web Servern"))
})
r.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("{"success":true}"))
})
srv := &http.Server{
Addr: address,
Handler: r,
ReadTimeout: rt,
WriteTimeout: wt,
}
return srv.ListenAndServeTLS(cert, key)
23
Functional Server
Ajuste na estrutura do server: criando a struct
type Server struct {
Ltn net.Listener
ReadTimeout time.Duration
WriteTimeout time.Duration
Handler http.Handler
}
func NewServer(addr string) (*Server, error) {
dftLtn, err := net.Listen("tcp", addr)
return &Server{
Ltn: dftLtn,
ReadTimeout: 3 * time.Second,
WriteTimeout: 5 * time.Second,
}, err
}
24
Functional Server
Ajuste na estrutura do server: usando a struct
func (s *Server) Serve() error {
srv := &http.Server{
Handler: s.Handler,
ReadTimeout: s.ReadTimeout,
WriteTimeout: s.WriteTimeout,
}
return srv.Serve(s.Ltn)
}
func main() {
sv, err := NewServer(":8080")
if err != nil {
panic(err)
}
log.Println(sv.Serve())
}
25
Functional Server Com Options
Como inserir um read time out no nosso functional server?
Opção 1: parametro na criação (muda algo?)
func NewServer(addr string, readTimeout time.Duration) (*Server, error) {
dftLtn, err := net.Listen("tcp", addr)
return &Server{
Ltn: dftLtn,
ReadTimeout: readTimeout,
WriteTimeout: 5 * time.Second,
}, err
}
26
Functional Server Com Options
Opção 2: mutar o server depois de criado
func main() {
sv, err := NewServer(":8080")
if err != nil {
panic(err)
}
sv.ReadTimeout = 3*time.Second
log.Println(sv.Serve())
}
Imperativo: baixo reaproveitamento
Poucas abstrações
Clara e distribuída quebra de imutabilidade
27
Functional Server Com Options
Opção 3: criar um método no server
func (s *Server) setTimeout(timeout time.Duration) error {
sv.ReadTimeout = timeout
}
func main() {
sv, err := NewServer(":8080")
if err != nil {
panic(err)
}
sv.setTimeout(3*time.Second)
log.Println(sv.Serve())
}
Clara e distribuída quebra de imutabilidade
28
Functional Server Com Options
Opção 4: criar uma função que altera timeout
func WithTimeout(read, write time.Duration) func(*Server) {
return func(s *Server) {
s.ReadTimeout = read
s.WriteTimeout = write
}
}
func main() {
sv, err := NewServer(":8080")
if err != nil {
panic(err)
}
f := WithTimeout(3*time.Second, 5*time.Second)
f(sv)
log.Println(sv.Serve())
}
Sensação de maior encapsulamento
Clara e distribuída quebra de imutabilidade
29
Functional Server Com Option
Opção 5: criar uma função que altera timeout E recebê-la somente no Serve
func WithTimeout(read, write time.Duration) func(*Server) {...}
func (s *Server) Serve(option func(*Server)) error {
option(s)
srv := &http.Server{
Handler: s.Handler,
ReadTimeout: s.ReadTimeout,
WriteTimeout: s.WriteTimeout,
}
return srv.Serve(s.Ltn)
}
func main() {
sv, err := NewServer(":8080")
if err != nil {
panic(err)
}
log.Println(sv.Serve(
WithTimeout(3*time.Second, 5*time.Second)
))
}
Mutação ainda acontece, mas somente no uso nal
Functional Server Com Options (plural)
func (s *Server) Serve(options ...func(*Server)) error {
for _, opt := range options {
opt(s)
}
srv := &http.Server{
Handler: s.Handler,
ReadTimeout: s.ReadTimeout,
WriteTimeout: s.WriteTimeout,
}
return srv.Serve(s.Ltn)
}
func main() {
sv, err := NewServer(":8080")
if err != nil {
panic(err)
}
log.Println(sv.Serve(
WithTimeout(3*time.Second, 5*time.Second)
))
}
31
Functional Server Com Options (plural)
Inserindo rotas
func WithRoutes() func(*Server) {
return func(s *Server) {
r := http.NewServeMux()
r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("My Web Servern"))
})
r.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("{"success":true}"))
})
s.Handler = r
}
}
32
Functional Server Com Options (plural)
Inserindo certi cado
func WithCertificate(cert, key string) func(*Server) {
return func(s *Server) {
cert, err := tls.LoadX509KeyPair(cert, key)
if err != nil {
panic(err)
}
config := &tls.Config{Certificates: []tls.Certificate{cert}, InsecureSkipVerify: true}
//Close old listener
if err := s.Ltn.Close(); err != nil {
panic(err)
}
//Keep old address
originalAddr := s.Ltn.Addr().String()
ltn, err := tls.Listen("tcp", originalAddr, config)
if err != nil {
panic(err)
}
s.Ltn = ltn
}
}
33
Criando o server com Go idiomático
34
Functional Server Com Options (plural)
Criando o server
func WithCertificate(cert, key string) func(*Server) {...}
func WithTimeout(read, write time.Duration) func(*Server) {...}
func WithRoutes() func(*Server) {...}
func main() {
sv, err := NewServer(":8080")
if err != nil {
panic(err)
}
log.Println(sv.Serve(
WithRoutes(),
WithTimeout(2*time.Second, 3*time.Second),
WithCertificate("cert.pem", "key.pem"),
))
}
Toda a con guração do server está no uxo de criação do server
Permite o criação de uma API uente
35
Onde usar?
36
Onde usar?
Con guração de serviços, clientes ou structs
De nição de strategy pattern
Cenários de sincronia -> passagem de comportamento vs
passagem de dados
37
Onde usar?
Con guração de serviços, clientes ou structs
De nição de strategy pattern
Cenários de sincronia -> passagem de comportamento vs
passagem de dados
38
Mais simples bank account
package main
import "fmt"
type BankAccount struct {
balance float64
}
func (b *BankAccount) Deposit(amount float64) { b.balance += amount }
func (b *BankAccount) Withdraw(amount float64) { b.balance -= amount }
func (b *BankAccount) Balance() float64 { return b.balance }
func main() {
acc := BankAccount{}
acc.Deposit(50)
acc.Withdraw(40)
fmt.Println(acc.Balance())
}
39
Cenário de simples "concorrência"
type BankAccount struct{ balance float64 }
func (b *BankAccount) Deposit(amount float64) { b.balance += amount }
func (b *BankAccount) Withdraw(amount float64) { b.balance -= amount }
func (b *BankAccount) Balance() float64 { return b.balance }
func main() {
var wg sync.WaitGroup
acc := BankAccount{}
for i := 0; i < 100000; i++ {
wg.Add(1)
go func(account *BankAccount) {
account.Deposit(10)
account.Withdraw(account.Balance() / 2)
wg.Done()
}(&acc)
}
wg.Wait()
fmt.Println(acc.Balance())
}
40
Tratamento padrão de concorrência
package main
import (
"fmt"
"sync"
)
type BankAccount struct {
balance float64
mu sync.Mutex
}
func (b *BankAccount) Deposit(amount float64) {
b.mu.Lock()
defer b.mu.Unlock()
b.balance += amount
}
func (b *BankAccount) Withdraw(amount float64) {
b.mu.Lock()
defer b.mu.Unlock()
b.balance -= amount
}
...
41
Tratamento funcional de concorrência
type BankAccount struct {
balance float64
operations chan func(float64) float64
}
func (b *BankAccount) Deposit(amount float64) {
b.operations <- func(oldBalance float64) float64 {
return oldBalance + amount
}
}
func (b *BankAccount) Withdraw(amount float64) {
b.operations <- func(oldBalance float64) float64 {
return oldBalance - amount
}
}
42
Tratamento funcional de concorrência
func (b *BankAccount) Loop() {
for op := range b.operations {
b.balance = op(b.balance)
}
}
func NewBankAccount() *BankAccount {
return &BankAccount{
balance: 0,
operations: make(chan func(float64) float64),
}
}
func main() {
acc := NewBankAccount()
...
go func(account *BankAccount) {
acc.Loop()
}(acc)
43
func(func(func(func(func(func(...))))))
44
Simple Server Novamente
package main
import (
"log"
"net/http"
)
func main() {
log.Println(NewServer(":8080"))
}
func NewServer(address string) error {
r := http.NewServeMux()
r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("My Web Servern"))
})
srv := &http.Server{
Addr: address,
Handler: r,
}
return srv.ListenAndServe()
}
45
Simple Server com Funções
func main() {
log.Println(NewServer(":8080"))
}
func NewServer(address string) error {
r := http.NewServeMux()
r.HandleFunc("/", GetMainResponse())
srv := &http.Server{
Addr: address,
Handler: r,
}
return srv.ListenAndServe()
}
func GetMainResponse() func (http.ResponseWriter,*http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("My Web Servern"))
}
}
46
Simple Server - Assinatura
func GetMainResponse() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("My Web Servern"))
}
}
func(w http.ResponseWriter, r *http.Request)
47
Decorated Simple Server
func TraceRequest(f func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
log.Printf("Start request: %s", time.Now())
f(w, r)
log.Printf("End request: %s", time.Now())
}
}
48
Decorated Simple Server
func Authenticate(f func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
auth := r.Header.Get("Authorization")
if len(auth) == 0 { //Check token
w.WriteHeader(401)
} else {
f(w, r)
}
}
}
49
Decorated Simple Server
func NewServer(address string) error {
r := http.NewServeMux()
r.HandleFunc("/", Authenticate(TraceRequest(GetMainResponse())))
srv := &http.Server{
Addr: address,
Handler: r,
}
return srv.ListenAndServe()
}
50
Real world examples!!!
https://github.com/prometheus/prometheus/blob/master/web/api/v1/api.go
type apiFunc func(r *http.Request) apiFuncResult
func (api *API) Register(r *route.Router) {
wrap := func(f apiFunc) http.HandlerFunc {
hf := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
httputil.SetCORS(w, api.CORSOrigin, r)
result := f(r)
if result.err != nil {
api.respondError(w, result.err, result.data)
} else if result.data != nil {
api.respond(w, result.data, result.warnings)
} else {
w.WriteHeader(http.StatusNoContent)
}
if result.finalizer != nil {
result.finalizer()
}
})
return api.ready(httputil.CompressionHandler{
Handler: hf,
}.ServeHTTP)
}
...
Real world examples!!!
https://github.com/prometheus/prometheus/blob/master/web/api/v1/api.go
wrap := func(f apiFunc) http.HandlerFunc {}
r.Options("/*path", wrap(api.options))
r.Get("/query", wrap(api.query))
r.Post("/query", wrap(api.query))
r.Get("/query_range", wrap(api.queryRange))
52
Real world examples!!!
https://github.com/golang/text/blob/master/cases/cases.go#L93
type Option func(o options) options
func Fold(opts ...Option) Caser {
return Caser{makeFold(getOpts(opts...))}
}
func getOpts(o ...Option) (res options) {
for _, f := range o {
res = f(res)
}
return
}
53
Real world examples!!!
Gorm Database Client
Formato com encadeamento de métodos
db, _ := gorm.Open(...)
var user User
err := db.Where("email = ?", "email@host.com").
Where("age >= ?", 18).
First(&user).
Error
Exige a codi cação de uma estrutura
var user User
err = db.First(&user,
Where("email = ?", "jon@calhoun.io"),
Where("id = ?", 2),
)
54
Vantagens
API mais fáceis e compreensíveis no
uso
Possibilidade de se entregar a closure
pronta para uso
Facilidade de se estender (novas
closures)
Valores default já pré-con gurados
Criação de uent-API com
encadeamento de closures
Decorator pattern com baixa
complexidade
Desvantagens
Mutabilidade da estrutura de
con guração
Maior quantidade de código
Maior demanda cognitiva
Mudança de paradigma para criação
soluções
The right tool for the right job
55
High-order functions como ferramenta
56
Muito obrigado!
Conteúdo
slides: https://zamariola.com.br/slides/devcamp2019
exemplos: https://github.com/zamariola
Referências
https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-
design.html
https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis
https://www.youtube.com/watch?v=5IKcPMJXkKs
https://dave.cheney.net/2016/11/13/do-not-fear- rst-class-functions
https://www.calhoun.io/using-functional-options-instead-of-method-chaining-in-go/
57

Leonardo Zamariola - High Order Functions e Functional Interfaces

  • 1.
    High Order Functionse Functional Interfaces Devcamp 16/08/2019 1
  • 2.
    Especialista em desenvolvimento backend. 11anos em desenvolvimento Java Certi ed Associate Go desde 2016 Sobre 2
  • 3.
  • 4.
    Foco em simplicidadede soluções Sintaxe simples Uma única (ou poucas) formas de se fazer algo Leitura e escrita homogêneas por todo ecossistema Por que os exemplos em Go? 4
  • 5.
    Idiomático public class SimpleHashSetExample{ public static void main(String[] args) { HashSet hSet = new HashSet(); hSet.add(new Integer("1")); hSet.add(new Integer("2")); hSet.add(new Integer("3")); System.out.println("HashSet contains.." + hSet); } } 5
  • 6.
    Idiomático class Animal(val name:String) class Zoo(val animals: List<Animal>) { operator fun iterator(): Iterator<Animal> { return animals.iterator() } } fun main() { val zoo = Zoo(listOf(Animal("zebra"), Animal("lion"))) for (animal in zoo) { println("Watch out, it's a ${animal.name}") } } 6
  • 7.
  • 8.
    Domínio Estrutura das entidades Composiçãoou herança Estrutura de persistência type Entity struct { name string ... } Regra de negócio Fluxo e manipulação de entidades Regra de negócio Adição ou remoção de etapas de processamento func (e Entity) transformedName() string { return e.name + " with transformation" } ... The right tool for the right job 8
  • 9.
    Server simples emGo package main import ( "log" "net/http" ) func main() { log.Println(NewServer(":8080")) } func NewServer(address string) error { r := http.NewServeMux() r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("My Web Servern")) }) r.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("{"success":true}")) }) srv := &http.Server{ Addr: address, Handler: r, } return srv.ListenAndServe() }
  • 10.
    Server simples emGo + Timeout func main() { log.Println(NewServer(":8080", 2*time.Second, 5*time.Second)) } func NewServer(address string, rt, wt time.Duration) error { r := http.NewServeMux() r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("My Web Servern")) }) r.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("{"success":true}")) }) srv := &http.Server{ Addr: address, Handler: r, ReadTimeout: rt, WriteTimeout: wt, } return srv.ListenAndServe() } 10
  • 11.
    Server simples emGo + Timeout + TLS func main() { log.Println(NewServer(":8080", "cert.pem", "key.pem", 2*time.Second, 5*time.Second)) } func NewServer(address, cert, key string, rt, wt time.Duration) error { r := http.NewServeMux() r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("My Web Servern")) }) r.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("{"success":true}")) }) srv := &http.Server{ Addr: address, Handler: r, ReadTimeout: rt, WriteTimeout: wt, } return srv.ListenAndServeTLS(cert, key) } 11
  • 12.
    Muitas assinaturas para"mesma" função func NewServer(address string) error {} func NewServer(address string, rt, wt time.Duration) error {} func NewServer(address, cert, key string) error {} func NewServer(address, cert, key string, rt, wt time.Duration) error {} 12
  • 13.
    Server simples emGo + Con g type Config struct { Address string CertPath string KeyPath string ReadTimeout time.Duration WriteTimeout time.Duration } func main() { config := Config{":8080", "cert.pem", "key.pem", 2 * time.Second, 5 * time.Second} log.Println(NewServer(config)) } 13
  • 14.
    Server simples emGo + Con g func NewServer(c Config) error { r := http.NewServeMux() r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("My Web Servern")) }) r.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("{"success":true}")) }) srv := &http.Server{ Addr: c.Address, Handler: r, ReadTimeout: c.ReadTimeout, WriteTimeout: c.WriteTimeout, } return srv.ListenAndServeTLS(c.CertPath, c.KeyPath) } 14
  • 15.
    Con guration structs Vantagens Melhoraa documentação e entendimento do código. Permite utilizar "zero values" como regra Crescimento das con gurações sem quebrar assinatura 15
  • 16.
    Con guration structs Desvantagens Geradiversos possíveis problemas com "zero values", exemplo: porta 0 c := Config{ "cert.pem", "key.pem" } O cenário ideal para "default" value seria aleatoriamente escolher uma porta e não ir direto para a zero. Mas e se quisermos tentar a porta zero? Quais campos são obrigatórios e quais são opcionais? 16
  • 17.
    Con guration structs Problemada mutabilidade config := Config{ ":8080", "cert.pem", "key.pem", 2 * time.Second, 5 * time.Second } go func() { config.Address = ":9090" }() log.Println(NewServer(config)) Problema da obrigatoridade func main() { //Quero o server mais simples possível!! log.Println(NewServer(????)) } 17
  • 18.
    High-Order Function Função quefaz pelo menos uma das duas: 1. Recebe uma (ou mais) funções como parâmetros 2. Retorna uma função Exemplos: Cálculo: dy/dx (operador de diferencial) que recebe uma função e retorna a sua derivada callbacks em Javascript http.HandlerFunc(pattern, handlerFunc) 18
  • 19.
    High-Order Function +rst class citizen package main import "fmt" //High-Order: retorna uma função func myFunction() func(int, int) int { return func(a, b int) int { return a + b } } func main() { //first class citizen: atribuir a uma variável f := myFunction() //first class citizen: chamar através da variável soma := f(2, 3) fmt.Println(soma) } 19
  • 20.
    High-Order Function +Type Alias package main import "fmt" //type alias type SomaFunc func(int, int) int //High-Order: retorna uma função func myFunction() SomaFunc { return func(a, b int) int { return a + b } } func main() { //first class citizen: atribuir a uma variável f := myFunction() //first class citizen: chamar através da variável soma := f(2, 3) fmt.Println(soma) } 20
  • 21.
    High-Order na Prática packagemain import "net/http" func main() { //first class citizen: atribuir a uma variável f := handlerFunction() http.HandleFunc("/", f) http.ListenAndServe(":8080", nil) } //High-Order: retorna uma função func handlerFunction() func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Contentn")) } } 21
  • 22.
  • 23.
    Relembrar: server deprecado funcmain() { log.Println(NewServer(":8080", "cert.pem", "key.pem", 2*time.Second, 5*time.Second)) } func NewServer(address, cert, key string, rt, wt time.Duration) error { r := http.NewServeMux() r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("My Web Servern")) }) r.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("{"success":true}")) }) srv := &http.Server{ Addr: address, Handler: r, ReadTimeout: rt, WriteTimeout: wt, } return srv.ListenAndServeTLS(cert, key) 23
  • 24.
    Functional Server Ajuste naestrutura do server: criando a struct type Server struct { Ltn net.Listener ReadTimeout time.Duration WriteTimeout time.Duration Handler http.Handler } func NewServer(addr string) (*Server, error) { dftLtn, err := net.Listen("tcp", addr) return &Server{ Ltn: dftLtn, ReadTimeout: 3 * time.Second, WriteTimeout: 5 * time.Second, }, err } 24
  • 25.
    Functional Server Ajuste naestrutura do server: usando a struct func (s *Server) Serve() error { srv := &http.Server{ Handler: s.Handler, ReadTimeout: s.ReadTimeout, WriteTimeout: s.WriteTimeout, } return srv.Serve(s.Ltn) } func main() { sv, err := NewServer(":8080") if err != nil { panic(err) } log.Println(sv.Serve()) } 25
  • 26.
    Functional Server ComOptions Como inserir um read time out no nosso functional server? Opção 1: parametro na criação (muda algo?) func NewServer(addr string, readTimeout time.Duration) (*Server, error) { dftLtn, err := net.Listen("tcp", addr) return &Server{ Ltn: dftLtn, ReadTimeout: readTimeout, WriteTimeout: 5 * time.Second, }, err } 26
  • 27.
    Functional Server ComOptions Opção 2: mutar o server depois de criado func main() { sv, err := NewServer(":8080") if err != nil { panic(err) } sv.ReadTimeout = 3*time.Second log.Println(sv.Serve()) } Imperativo: baixo reaproveitamento Poucas abstrações Clara e distribuída quebra de imutabilidade 27
  • 28.
    Functional Server ComOptions Opção 3: criar um método no server func (s *Server) setTimeout(timeout time.Duration) error { sv.ReadTimeout = timeout } func main() { sv, err := NewServer(":8080") if err != nil { panic(err) } sv.setTimeout(3*time.Second) log.Println(sv.Serve()) } Clara e distribuída quebra de imutabilidade 28
  • 29.
    Functional Server ComOptions Opção 4: criar uma função que altera timeout func WithTimeout(read, write time.Duration) func(*Server) { return func(s *Server) { s.ReadTimeout = read s.WriteTimeout = write } } func main() { sv, err := NewServer(":8080") if err != nil { panic(err) } f := WithTimeout(3*time.Second, 5*time.Second) f(sv) log.Println(sv.Serve()) } Sensação de maior encapsulamento Clara e distribuída quebra de imutabilidade 29
  • 30.
    Functional Server ComOption Opção 5: criar uma função que altera timeout E recebê-la somente no Serve func WithTimeout(read, write time.Duration) func(*Server) {...} func (s *Server) Serve(option func(*Server)) error { option(s) srv := &http.Server{ Handler: s.Handler, ReadTimeout: s.ReadTimeout, WriteTimeout: s.WriteTimeout, } return srv.Serve(s.Ltn) } func main() { sv, err := NewServer(":8080") if err != nil { panic(err) } log.Println(sv.Serve( WithTimeout(3*time.Second, 5*time.Second) )) } Mutação ainda acontece, mas somente no uso nal
  • 31.
    Functional Server ComOptions (plural) func (s *Server) Serve(options ...func(*Server)) error { for _, opt := range options { opt(s) } srv := &http.Server{ Handler: s.Handler, ReadTimeout: s.ReadTimeout, WriteTimeout: s.WriteTimeout, } return srv.Serve(s.Ltn) } func main() { sv, err := NewServer(":8080") if err != nil { panic(err) } log.Println(sv.Serve( WithTimeout(3*time.Second, 5*time.Second) )) } 31
  • 32.
    Functional Server ComOptions (plural) Inserindo rotas func WithRoutes() func(*Server) { return func(s *Server) { r := http.NewServeMux() r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("My Web Servern")) }) r.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("{"success":true}")) }) s.Handler = r } } 32
  • 33.
    Functional Server ComOptions (plural) Inserindo certi cado func WithCertificate(cert, key string) func(*Server) { return func(s *Server) { cert, err := tls.LoadX509KeyPair(cert, key) if err != nil { panic(err) } config := &tls.Config{Certificates: []tls.Certificate{cert}, InsecureSkipVerify: true} //Close old listener if err := s.Ltn.Close(); err != nil { panic(err) } //Keep old address originalAddr := s.Ltn.Addr().String() ltn, err := tls.Listen("tcp", originalAddr, config) if err != nil { panic(err) } s.Ltn = ltn } } 33
  • 34.
    Criando o servercom Go idiomático 34
  • 35.
    Functional Server ComOptions (plural) Criando o server func WithCertificate(cert, key string) func(*Server) {...} func WithTimeout(read, write time.Duration) func(*Server) {...} func WithRoutes() func(*Server) {...} func main() { sv, err := NewServer(":8080") if err != nil { panic(err) } log.Println(sv.Serve( WithRoutes(), WithTimeout(2*time.Second, 3*time.Second), WithCertificate("cert.pem", "key.pem"), )) } Toda a con guração do server está no uxo de criação do server Permite o criação de uma API uente 35
  • 36.
  • 37.
    Onde usar? Con guraçãode serviços, clientes ou structs De nição de strategy pattern Cenários de sincronia -> passagem de comportamento vs passagem de dados 37
  • 38.
    Onde usar? Con guraçãode serviços, clientes ou structs De nição de strategy pattern Cenários de sincronia -> passagem de comportamento vs passagem de dados 38
  • 39.
    Mais simples bankaccount package main import "fmt" type BankAccount struct { balance float64 } func (b *BankAccount) Deposit(amount float64) { b.balance += amount } func (b *BankAccount) Withdraw(amount float64) { b.balance -= amount } func (b *BankAccount) Balance() float64 { return b.balance } func main() { acc := BankAccount{} acc.Deposit(50) acc.Withdraw(40) fmt.Println(acc.Balance()) } 39
  • 40.
    Cenário de simples"concorrência" type BankAccount struct{ balance float64 } func (b *BankAccount) Deposit(amount float64) { b.balance += amount } func (b *BankAccount) Withdraw(amount float64) { b.balance -= amount } func (b *BankAccount) Balance() float64 { return b.balance } func main() { var wg sync.WaitGroup acc := BankAccount{} for i := 0; i < 100000; i++ { wg.Add(1) go func(account *BankAccount) { account.Deposit(10) account.Withdraw(account.Balance() / 2) wg.Done() }(&acc) } wg.Wait() fmt.Println(acc.Balance()) } 40
  • 41.
    Tratamento padrão deconcorrência package main import ( "fmt" "sync" ) type BankAccount struct { balance float64 mu sync.Mutex } func (b *BankAccount) Deposit(amount float64) { b.mu.Lock() defer b.mu.Unlock() b.balance += amount } func (b *BankAccount) Withdraw(amount float64) { b.mu.Lock() defer b.mu.Unlock() b.balance -= amount } ... 41
  • 42.
    Tratamento funcional deconcorrência type BankAccount struct { balance float64 operations chan func(float64) float64 } func (b *BankAccount) Deposit(amount float64) { b.operations <- func(oldBalance float64) float64 { return oldBalance + amount } } func (b *BankAccount) Withdraw(amount float64) { b.operations <- func(oldBalance float64) float64 { return oldBalance - amount } } 42
  • 43.
    Tratamento funcional deconcorrência func (b *BankAccount) Loop() { for op := range b.operations { b.balance = op(b.balance) } } func NewBankAccount() *BankAccount { return &BankAccount{ balance: 0, operations: make(chan func(float64) float64), } } func main() { acc := NewBankAccount() ... go func(account *BankAccount) { acc.Loop() }(acc) 43
  • 44.
  • 45.
    Simple Server Novamente packagemain import ( "log" "net/http" ) func main() { log.Println(NewServer(":8080")) } func NewServer(address string) error { r := http.NewServeMux() r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("My Web Servern")) }) srv := &http.Server{ Addr: address, Handler: r, } return srv.ListenAndServe() } 45
  • 46.
    Simple Server comFunções func main() { log.Println(NewServer(":8080")) } func NewServer(address string) error { r := http.NewServeMux() r.HandleFunc("/", GetMainResponse()) srv := &http.Server{ Addr: address, Handler: r, } return srv.ListenAndServe() } func GetMainResponse() func (http.ResponseWriter,*http.Request) { return func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("My Web Servern")) } } 46
  • 47.
    Simple Server -Assinatura func GetMainResponse() func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("My Web Servern")) } } func(w http.ResponseWriter, r *http.Request) 47
  • 48.
    Decorated Simple Server funcTraceRequest(f func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { log.Printf("Start request: %s", time.Now()) f(w, r) log.Printf("End request: %s", time.Now()) } } 48
  • 49.
    Decorated Simple Server funcAuthenticate(f func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { auth := r.Header.Get("Authorization") if len(auth) == 0 { //Check token w.WriteHeader(401) } else { f(w, r) } } } 49
  • 50.
    Decorated Simple Server funcNewServer(address string) error { r := http.NewServeMux() r.HandleFunc("/", Authenticate(TraceRequest(GetMainResponse()))) srv := &http.Server{ Addr: address, Handler: r, } return srv.ListenAndServe() } 50
  • 51.
    Real world examples!!! https://github.com/prometheus/prometheus/blob/master/web/api/v1/api.go typeapiFunc func(r *http.Request) apiFuncResult func (api *API) Register(r *route.Router) { wrap := func(f apiFunc) http.HandlerFunc { hf := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { httputil.SetCORS(w, api.CORSOrigin, r) result := f(r) if result.err != nil { api.respondError(w, result.err, result.data) } else if result.data != nil { api.respond(w, result.data, result.warnings) } else { w.WriteHeader(http.StatusNoContent) } if result.finalizer != nil { result.finalizer() } }) return api.ready(httputil.CompressionHandler{ Handler: hf, }.ServeHTTP) } ...
  • 52.
    Real world examples!!! https://github.com/prometheus/prometheus/blob/master/web/api/v1/api.go wrap:= func(f apiFunc) http.HandlerFunc {} r.Options("/*path", wrap(api.options)) r.Get("/query", wrap(api.query)) r.Post("/query", wrap(api.query)) r.Get("/query_range", wrap(api.queryRange)) 52
  • 53.
    Real world examples!!! https://github.com/golang/text/blob/master/cases/cases.go#L93 typeOption func(o options) options func Fold(opts ...Option) Caser { return Caser{makeFold(getOpts(opts...))} } func getOpts(o ...Option) (res options) { for _, f := range o { res = f(res) } return } 53
  • 54.
    Real world examples!!! GormDatabase Client Formato com encadeamento de métodos db, _ := gorm.Open(...) var user User err := db.Where("email = ?", "email@host.com"). Where("age >= ?", 18). First(&user). Error Exige a codi cação de uma estrutura var user User err = db.First(&user, Where("email = ?", "jon@calhoun.io"), Where("id = ?", 2), ) 54
  • 55.
    Vantagens API mais fáceise compreensíveis no uso Possibilidade de se entregar a closure pronta para uso Facilidade de se estender (novas closures) Valores default já pré-con gurados Criação de uent-API com encadeamento de closures Decorator pattern com baixa complexidade Desvantagens Mutabilidade da estrutura de con guração Maior quantidade de código Maior demanda cognitiva Mudança de paradigma para criação soluções The right tool for the right job 55
  • 56.
  • 57.
    Muito obrigado! Conteúdo slides: https://zamariola.com.br/slides/devcamp2019 exemplos:https://github.com/zamariola Referências https://commandcenter.blogspot.com/2014/01/self-referential-functions-and- design.html https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis https://www.youtube.com/watch?v=5IKcPMJXkKs https://dave.cheney.net/2016/11/13/do-not-fear- rst-class-functions https://www.calhoun.io/using-functional-options-instead-of-method-chaining-in-go/ 57