Cloud Native Go Building Scalable, Resilient Microservices for the - - PDF document

cloud native go
SMART_READER_LITE
LIVE PREVIEW

Cloud Native Go Building Scalable, Resilient Microservices for the - - PDF document

Cloud Native Go 6/28/17, 11)02 AM Cloud Native Go Building Scalable, Resilient Microservices for the Cloud in Go 1 / 29 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1 Page 1 of 38 Cloud Native Go 6/28/17, 11)02 AM Agenda


slide-1
SLIDE 1

6/28/17, 11)02 AM Cloud Native Go Page 1 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

Cloud Native Go

Building Scalable, Resilient Microservices for the Cloud in Go

1 / 29

slide-2
SLIDE 2

6/28/17, 11)02 AM Cloud Native Go Page 2 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

Agenda

  • 1. Introduction
  • 2. Boring Stuff
  • 3. Live Demos
  • 4. Q & A

2 / 29

Cloud Native Go

Building Scalable, Resilient Microservices for the Cloud in Go

1 / 29

slide-3
SLIDE 3

6/28/17, 11)02 AM Cloud Native Go Page 3 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

Introduction

Wrote a few books (2 fantasy, 15+ .NET/C#, Go) Maker, Tinkerer, Linguist Taught people how migrate/build cloud native for Pivotal Lead Software Engineer for Capital One Twitter: @KevinHoffman, github autodidaddict Go to gopherize.me gopherize.me for your Gopher Avatar 3 / 29

Agenda

  • 1. Introduction
  • 2. Boring Stuff
  • 3. Live Demos
  • 4. Q & A

2 / 29

slide-4
SLIDE 4

6/28/17, 11)02 AM Cloud Native Go Page 4 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

Why Go?

Fast Low memory, CPU, and Disk footprint Docker images from SCRATCH Fit more containers per node/VM Single, no-dependency binary Beautiful, simple language Short learning curve 4 / 29

Introduction

Wrote a few books (2 fantasy, 15+ .NET/C#, Go) Maker, Tinkerer, Linguist Taught people how migrate/build cloud native for Pivotal Lead Software Engineer for Capital One Twitter: @KevinHoffman, github autodidaddict Go to gopherize.me gopherize.me for your Gopher Avatar 3 / 29

slide-5
SLIDE 5

6/28/17, 11)02 AM Cloud Native Go Page 5 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

What is Cloud Native?

API First Dependency Management Design, Build, Release, Run Config, Credentials, and Code Logs Disposability Backing Services Environment Parity Administrative Processes Port Binding Stateless Processes Concurrency Telemetry Authentication and Authorization 5 / 29

Why Go?

Fast Low memory, CPU, and Disk footprint Docker images from SCRATCH Fit more containers per node/VM Single, no-dependency binary Beautiful, simple language Short learning curve 4 / 29

slide-6
SLIDE 6

6/28/17, 11)02 AM Cloud Native Go Page 6 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

Microservices Shopping List

HTTP Server Routing URL path variables Query strings JSON Encode/Decode Middleware (logging, security, etc) 6 / 29

What is Cloud Native?

API First Dependency Management Design, Build, Release, Run Config, Credentials, and Code Logs Disposability Backing Services Environment Parity Administrative Processes Port Binding Stateless Processes Concurrency Telemetry Authentication and Authorization 5 / 29

slide-7
SLIDE 7

6/28/17, 11)02 AM Cloud Native Go Page 7 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

HTTP Server

package main import ( ... ) func main() { port := os.Getenv("PORT") if len(port) == 0 { port = "8080" } mux := http.NewServeMux() mux.HandleFunc("/", hello) n := negroni.Classic() n.UseHandler(mux) hostString := fmt.Sprintf(":%s", port) n.Run(hostString) } func hello(res http.ResponseWriter, req *http.Request) { fmt.Fprintln(res, "Hello from Go!") }

7 / 29

Microservices Shopping List

HTTP Server Routing URL path variables Query strings JSON Encode/Decode Middleware (logging, security, etc) 6 / 29

slide-8
SLIDE 8

6/28/17, 11)02 AM Cloud Native Go Page 8 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

Routing

Routing with Gorilla Mux

r := mux.NewRouter() r.HandleFunc("/products/{key}", ProductHandler) r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler) r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler) s := r.PathPrefix("/zombies").Subrouter() s.HandleFunc("/", ZombiesHandler) s.HandleFunc("/sightings", NewSightingHandler).Methods("POST") s.HandleFunc("/{key}", QueryZombieHandler).Methods("GET")

URLs: /zombies /zombies/sightings (POST) /zombies/bob (GET) 8 / 29

HTTP Server

package main import ( ... ) func main() { port := os.Getenv("PORT") if len(port) == 0 { port = "8080" } mux := http.NewServeMux() mux.HandleFunc("/", hello) n := negroni.Classic() n.UseHandler(mux) hostString := fmt.Sprintf(":%s", port) n.Run(hostString) } func hello(res http.ResponseWriter, req *http.Request) { fmt.Fprintln(res, "Hello from Go!") }

7 / 29

slide-9
SLIDE 9

6/28/17, 11)02 AM Cloud Native Go Page 9 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

Routing

Accessing Route Data

vars := mux.Vars(req) zombieID := vars["zombie-key"]

Building URLs

r := mux.NewRouter() r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). Name("article") ... url, err := r.Get("article").URL("category", "technology", "id", "42")

Builds - /articles/technology/42 9 / 29

Routing

Routing with Gorilla Mux

r := mux.NewRouter() r.HandleFunc("/products/{key}", ProductHandler) r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler) r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler) s := r.PathPrefix("/zombies").Subrouter() s.HandleFunc("/", ZombiesHandler) s.HandleFunc("/sightings", NewSightingHandler).Methods("POST") s.HandleFunc("/{key}", QueryZombieHandler).Methods("GET")

URLs: /zombies /zombies/sightings (POST) /zombies/bob (GET) 8 / 29

slide-10
SLIDE 10

6/28/17, 11)02 AM Cloud Native Go Page 10 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

JSON Marshaling

Read JSON from Body:

payload, _ := ioutil.ReadAll(req.Body) var newMatchRequest newMatchRequest err := json.Unmarshal(payload, &newMatchRequest)

Write JSON to Response:

var mr newMatchResponse mr.copyMatch(newMatch) w.Header().Add("Location", "/matches/"+newMatch.ID) formatter.JSON(w, http.StatusCreated, &mr)

10 / 29

Routing

Accessing Route Data

vars := mux.Vars(req) zombieID := vars["zombie-key"]

Building URLs

r := mux.NewRouter() r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). Name("article") ... url, err := r.Get("article").URL("category", "technology", "id", "42")

Builds - /articles/technology/42 9 / 29

slide-11
SLIDE 11

6/28/17, 11)02 AM Cloud Native Go Page 11 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

Middleware

apiRouter := mux.NewRouter() apiRouter.HandleFunc("/api/post", apiPostHandler(formatter)).Methods("POST") router.PathPrefix("/api").Handler(negroni.New( negroni.HandlerFunc(isAuthorized(formatter)), negroni.Wrap(apiRouter), )) func isAuthorized(formatter *render.Render) negroni.HandlerFunc { return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { providedKey := r.Header.Get(APIKey) if providedKey == "" { formatter.JSON(w, http.StatusUnauthorized, struct{ Error string }{"Unauthorized." } else if providedKey != apikey { formatter.JSON(w, http.StatusForbidden, struct{ Error string }{"Insufficient credentials." } else { next(w, r) } } }

11 / 29

JSON Marshaling

Read JSON from Body:

payload, _ := ioutil.ReadAll(req.Body) var newMatchRequest newMatchRequest err := json.Unmarshal(payload, &newMatchRequest)

Write JSON to Response:

var mr newMatchResponse mr.copyMatch(newMatch) w.Header().Add("Location", "/matches/"+newMatch.ID) formatter.JSON(w, http.StatusCreated, &mr)

10 / 29

slide-12
SLIDE 12

6/28/17, 11)02 AM Cloud Native Go Page 12 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

Shopping List

✅ HTTP Server ✅ Routing ✅ URL path variables ✅ Query strings ✅ JSON Encode/Decode ✅ Middleware (logging, security, etc) 12 / 29

Middleware

apiRouter := mux.NewRouter() apiRouter.HandleFunc("/api/post", apiPostHandler(formatter)).Methods("POST") router.PathPrefix("/api").Handler(negroni.New( negroni.HandlerFunc(isAuthorized(formatter)), negroni.Wrap(apiRouter), )) func isAuthorized(formatter *render.Render) negroni.HandlerFunc { return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { providedKey := r.Header.Get(APIKey) if providedKey == "" { formatter.JSON(w, http.StatusUnauthorized, struct{ Error string }{"Unauthorized." } else if providedKey != apikey { formatter.JSON(w, http.StatusForbidden, struct{ Error string }{"Insufficient credentials." } else { next(w, r) } } }

11 / 29

slide-13
SLIDE 13

6/28/17, 11)02 AM Cloud Native Go Page 13 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

We're Doing This All Wrong

13 / 29

Shopping List

✅ HTTP Server ✅ Routing ✅ URL path variables ✅ Query strings ✅ JSON Encode/Decode ✅ Middleware (logging, security, etc) 12 / 29

slide-14
SLIDE 14

6/28/17, 11)02 AM Cloud Native Go Page 14 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

Do One Thing Great

A fox knows many things, but a hedgehog one important thing - Archilochus 14 / 29

We're Doing This All Wrong

13 / 29

slide-15
SLIDE 15

6/28/17, 11)02 AM Cloud Native Go Page 15 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

Hexagonal Architecture

Ports and Adapters 15 / 29

Do One Thing Great

A fox knows many things, but a hedgehog one important thing - Archilochus 14 / 29

slide-16
SLIDE 16

6/28/17, 11)02 AM Cloud Native Go Page 16 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

Ports and Adapters in Go

Build your Core Functionality First Decouple HTTP, JSON, Headers, Security from Core Function Should be able to test your core function in isolation Code is blissfully ignorant of source and nature of inputs and ultimate destination of replies 16 / 29

Hexagonal Architecture

Ports and Adapters 15 / 29

slide-17
SLIDE 17

6/28/17, 11)02 AM Cloud Native Go Page 17 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

Ports & Adapters in a μService Ecosystem

17 / 29

Ports and Adapters in Go

Build your Core Functionality First Decouple HTTP, JSON, Headers, Security from Core Function Should be able to test your core function in isolation Code is blissfully ignorant of source and nature of inputs and ultimate destination of replies 16 / 29

slide-18
SLIDE 18

6/28/17, 11)02 AM Cloud Native Go Page 18 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

Ports & Adapters in a μService Ecosystem

API Gateways 17 / 29

Ports & Adapters in a μService Ecosystem

17 / 29

slide-19
SLIDE 19

6/28/17, 11)02 AM Cloud Native Go Page 19 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

Ports & Adapters in a μService Ecosystem

API Gateways Aggregator Services 17 / 29

Ports & Adapters in a μService Ecosystem

API Gateways 17 / 29

slide-20
SLIDE 20

6/28/17, 11)02 AM Cloud Native Go Page 20 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

Ports & Adapters in a μService Ecosystem

API Gateways Aggregator Services Message Brokers Topics Pub/Sub 17 / 29

Ports & Adapters in a μService Ecosystem

API Gateways Aggregator Services 17 / 29

slide-21
SLIDE 21

6/28/17, 11)02 AM Cloud Native Go Page 21 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

Ports & Adapters in a μService Ecosystem

API Gateways Aggregator Services Message Brokers Topics Pub/Sub ACL / Translators 17 / 29

Ports & Adapters in a μService Ecosystem

API Gateways Aggregator Services Message Brokers Topics Pub/Sub 17 / 29

slide-22
SLIDE 22

6/28/17, 11)02 AM Cloud Native Go Page 22 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

Ports & Adapters in a μService Ecosystem

API Gateways Aggregator Services Message Brokers Topics Pub/Sub ACL / Translators Dynamic Service Discovery 17 / 29

Ports & Adapters in a μService Ecosystem

API Gateways Aggregator Services Message Brokers Topics Pub/Sub ACL / Translators 17 / 29

slide-23
SLIDE 23

6/28/17, 11)02 AM Cloud Native Go Page 23 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

Ports & Adapters in a μService Ecosystem

API Gateways Aggregator Services Message Brokers Topics Pub/Sub ACL / Translators Dynamic Service Discovery External Configuration 17 / 29

Ports & Adapters in a μService Ecosystem

API Gateways Aggregator Services Message Brokers Topics Pub/Sub ACL / Translators Dynamic Service Discovery 17 / 29

slide-24
SLIDE 24

6/28/17, 11)02 AM Cloud Native Go Page 24 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

Ports & Adapters in a μService Ecosystem

API Gateways Aggregator Services Message Brokers Topics Pub/Sub ACL / Translators Dynamic Service Discovery External Configuration Adapter paths through system Gateways, Proxies Translators on Data Streams / Queues 17 / 29

Ports & Adapters in a μService Ecosystem

API Gateways Aggregator Services Message Brokers Topics Pub/Sub ACL / Translators Dynamic Service Discovery External Configuration 17 / 29

slide-25
SLIDE 25

6/28/17, 11)02 AM Cloud Native Go Page 25 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

Ports & Adapters in a μService Ecosystem

API Gateways Aggregator Services Message Brokers Topics Pub/Sub ACL / Translators Dynamic Service Discovery External Configuration Adapter paths through system Gateways, Proxies Translators on Data Streams / Queues Hexagonal arch inside and outside of services 17 / 29

Ports & Adapters in a μService Ecosystem

API Gateways Aggregator Services Message Brokers Topics Pub/Sub ACL / Translators Dynamic Service Discovery External Configuration Adapter paths through system Gateways, Proxies Translators on Data Streams / Queues 17 / 29

slide-26
SLIDE 26

6/28/17, 11)02 AM Cloud Native Go Page 26 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

The Shopping List We Ignore

Security Monitoring Log Aggregation Zero Downtime Deployment Build/Deploy Automation Tracing Metrics Audit Automatic / Dynamic Horizontal Scaling Configuration Secrets Management Discovery 18 / 29

Ports & Adapters in a μService Ecosystem

API Gateways Aggregator Services Message Brokers Topics Pub/Sub ACL / Translators Dynamic Service Discovery External Configuration Adapter paths through system Gateways, Proxies Translators on Data Streams / Queues Hexagonal arch inside and outside of services 17 / 29

slide-27
SLIDE 27

6/28/17, 11)02 AM Cloud Native Go Page 27 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

Starting at the Core

Define what we're building Define why we're building it Who are we building it for? Build the core Add onion layers around the core Ports and Adapters Remember we're building an ecosystem, not hello world 19 / 29

The Shopping List We Ignore

Security Monitoring Log Aggregation Zero Downtime Deployment Build/Deploy Automation Tracing Metrics Audit Automatic / Dynamic Horizontal Scaling Configuration Secrets Management Discovery 18 / 29

slide-28
SLIDE 28

6/28/17, 11)02 AM Cloud Native Go Page 28 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

The Go Shopping Sample

20 / 29

Starting at the Core

Define what we're building Define why we're building it Who are we building it for? Build the core Add onion layers around the core Ports and Adapters Remember we're building an ecosystem, not hello world 19 / 29

slide-29
SLIDE 29

6/28/17, 11)02 AM Cloud Native Go Page 29 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

Go Shopping Sample - Tech

go micro Framework https://micro.mu/ Ports and Adapters are services Not boilerplate inside service Ecosystem-aware from bottom-up Dynamic Service Discovery gRPC Transport RESTful Aggregate API ... and much more 21 / 29

The Go Shopping Sample

20 / 29

slide-30
SLIDE 30

6/28/17, 11)02 AM Cloud Native Go Page 30 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

Framework vs Library

Opinionated vs boilerplate Spend boilerplate to insulate from opinions Mitigate ceremony with code generation? Go-kit - suite of libraries, incur BP cost Go-micro - opinionated, less BP Spring Boot - example of opinionated framework 22 / 29

Go Shopping Sample - Tech

go micro Framework https://micro.mu/ Ports and Adapters are services Not boilerplate inside service Ecosystem-aware from bottom-up Dynamic Service Discovery gRPC Transport RESTful Aggregate API ... and much more 21 / 29

slide-31
SLIDE 31

6/28/17, 11)02 AM Cloud Native Go Page 31 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

API First

API First is NOT: RESTful Endpoint First Swagger-First One API to Rule them All API First is: A strict, semantically versioned, interaction specification. A team and organization-wide discipline Different consumers, channels may need different APIs Ports and Adapters 23 / 29

Framework vs Library

Opinionated vs boilerplate Spend boilerplate to insulate from opinions Mitigate ceremony with code generation? Go-kit - suite of libraries, incur BP cost Go-micro - opinionated, less BP Spring Boot - example of opinionated framework 22 / 29

slide-32
SLIDE 32

6/28/17, 11)02 AM Cloud Native Go Page 32 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

Defining the Warehouse Service API

Protocol Buffer IDL gRPC Service

syntax = "proto3"; package warehouse; service Warehouse { rpc GetWarehouseDetails(DetailsRequest) returns (DetailsResponse); } message DetailsRequest { string sku = 1; } message DetailsResponse { WarehouseDetails details = 1; } message WarehouseDetails { string sku = 1; uint32 stock_remaining = 2; string manufacturer = 3; string model_number = 4; }

24 / 29

API First

API First is NOT: RESTful Endpoint First Swagger-First One API to Rule them All API First is: A strict, semantically versioned, interaction specification. A team and organization-wide discipline Different consumers, channels may need different APIs Ports and Adapters 23 / 29

slide-33
SLIDE 33

6/28/17, 11)02 AM Cloud Native Go Page 33 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

Service Implementation

package service import ( "github.com/autodidaddict/go-shopping/warehouse/proto" "golang.org/x/net/context" ) type warehouseService struct{} // NewWarehouseService returns an instance of a warehouse handler func NewWarehouseService() warehouse.WarehouseHandler { return &warehouseService{} } func (w *warehouseService) GetWarehouseDetails(ctx context.Context, request *warehouse.DetailsRequest, response *warehouse.DetailsResponse) error { response.Manufacturer = "TOSHIBA" response.ModelNumber = "T-1000" response.SKU = request.SKU response.StockRemaining = 35 return nil }

25 / 29

Defining the Warehouse Service API

Protocol Buffer IDL gRPC Service

syntax = "proto3"; package warehouse; service Warehouse { rpc GetWarehouseDetails(DetailsRequest) returns (DetailsResponse); } message DetailsRequest { string sku = 1; } message DetailsResponse { WarehouseDetails details = 1; } message WarehouseDetails { string sku = 1; uint32 stock_remaining = 2; string manufacturer = 3; string model_number = 4; }

24 / 29

slide-34
SLIDE 34

6/28/17, 11)02 AM Cloud Native Go Page 34 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

Let's do some Live Demos

What could possibly go wrong? 26 / 29

Service Implementation

package service import ( "github.com/autodidaddict/go-shopping/warehouse/proto" "golang.org/x/net/context" ) type warehouseService struct{} // NewWarehouseService returns an instance of a warehouse handler func NewWarehouseService() warehouse.WarehouseHandler { return &warehouseService{} } func (w *warehouseService) GetWarehouseDetails(ctx context.Context, request *warehouse.DetailsRequest, response *warehouse.DetailsResponse) error { response.Manufacturer = "TOSHIBA" response.ModelNumber = "T-1000" response.SKU = request.SKU response.StockRemaining = 35 return nil }

25 / 29

slide-35
SLIDE 35

6/28/17, 11)02 AM Cloud Native Go Page 35 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

Lessons Learned

Question Everything Beware of Protobuf Default Values Service-first development Code is the easiest and simplest part of Microservices NFRs are everything - logging, metrics, alerts, discovery, health, autoscale, async messaging, etc. Decide where you want to be on the opinion vs boilerplate curve Go is an ideal microservices development language 27 / 29

Let's do some Live Demos

What could possibly go wrong? 26 / 29

slide-36
SLIDE 36

6/28/17, 11)02 AM Cloud Native Go Page 36 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

Next Steps

Create Services Test everything Automate everything Deploy all the time Try out go-micro Try out go-kit Go Shopping http://github.com/autodidaddict/go-shopping These slides https://github.com/autodidaddict/cng-presentation 28 / 29

Lessons Learned

Question Everything Beware of Protobuf Default Values Service-first development Code is the easiest and simplest part of Microservices NFRs are everything - logging, metrics, alerts, discovery, health, autoscale, async messaging, etc. Decide where you want to be on the opinion vs boilerplate curve Go is an ideal microservices development language 27 / 29

slide-37
SLIDE 37

6/28/17, 11)02 AM Cloud Native Go Page 37 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

Q & A

29 / 29

Next Steps

Create Services Test everything Automate everything Deploy all the time Try out go-micro Try out go-kit Go Shopping http://github.com/autodidaddict/go-shopping These slides https://github.com/autodidaddict/cng-presentation 28 / 29

slide-38
SLIDE 38

6/28/17, 11)02 AM Cloud Native Go Page 38 of 38 file:///Users/kimberlyamaral/Desktop/cng-presentation/pres.html#1

Q & A

29 / 29