SlideShare ist ein Scribd-Unternehmen logo
Go überden Wolken
W-JAX 2023: Go über den Wolken
W-JAX 2023: Go über den Wolken
Go App Engine Cold Start
Up and running in 500ms! 🚀
5 Fakten zu Go
1. statisches Typsystem
2. Garbage Collection
3. keine Vererbung
4. Concurrency eingebaut
5. native Ausführung
Linux, Win, z/OS, 386, amd64, ARM, WebAssembly, ...
Was ist cloud-native?
Cloud Native Technologien befähigen
skalierbare Anwendungen
zu entwickeln und betreiben,
in modernen, dynamischen Umgebungen.
Cloud Native Computing Foundation
Cloud Native Booster
💪Skalierbar
🚂Zuverlässig
☯️Einfach
TarifrechnerDogOP
TarifrechnerDogOp v0.1
API die Hunde OPVersicherung berechnet
#
Bereitstellung als Container
#
Konfiguration überUmgebungsvariablen
#
TarifrechnerDogOp v0.2
CRUD Operationen in REST API
#
Angebote in Postgres speichern
#
Fallschirm und Rettungsgurt
Health Check
#
Problem Details
#
Middleware
#
Tarifrechner
DogOp v0.1
Projekt DogOPaufsetzen
go mod init crossnative/dogop // Go Modul initialisieren
go get github.com/go-chi/chi/v5 // Chi Dependency einbinden
Projektstruktur
go.mod // Modul Deskriptor mit Dependencies
go.sum // Checksummen der Dependencies
Projekt DogOPaufsetzen
package main
import (
"net/http"
"github.com/go-chi/chi/v5"
)
func main() {
}
1
2
3
4
5
6
7
8
r := chi.NewRouter()
9
r.Get("/", func(w http.ResponseWriter, req *http.Request
10
w.Write([]byte("Hello DogOp!"))
11
})
12
http.ListenAndServe(":8080", r)
13
14
r := chi.NewRouter()
package main
1
2
import (
3
"net/http"
4
"github.com/go-chi/chi/v5"
5
)
6
7
func main() {
8
9
r.Get("/", func(w http.ResponseWriter, req *http.Request
10
w.Write([]byte("Hello DogOp!"))
11
})
12
http.ListenAndServe(":8080", r)
13
}
14
r.Get("/", func(w http.ResponseWriter, req *http.Request
w.Write([]byte("Hello DogOp!"))
})
package main
1
2
import (
3
"net/http"
4
"github.com/go-chi/chi/v5"
5
)
6
7
func main() {
8
r := chi.NewRouter()
9
10
11
12
http.ListenAndServe(":8080", r)
13
}
14
http.ListenAndServe(":8080", r)
package main
1
2
import (
3
"net/http"
4
"github.com/go-chi/chi/v5"
5
)
6
7
func main() {
8
r := chi.NewRouter()
9
r.Get("/", func(w http.ResponseWriter, req *http.Request
10
w.Write([]byte("Hello DogOp!"))
11
})
12
13
}
14
package main
import (
"net/http"
"github.com/go-chi/chi/v5"
)
func main() {
r := chi.NewRouter()
r.Get("/", func(w http.ResponseWriter, req *http.Request
w.Write([]byte("Hello DogOp!"))
})
http.ListenAndServe(":8080", r)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Bauen und Ausführen
// 1. Kompilieren in Binary
go build -o build/dogop .
// 2. Binary ausführen
./build/dogop
Bauen und Ausführen
REST API fürRechner
Request
POST /api/quote
content-type: application/json
{
"age": 8,
"breed": "chow"
}
Response
HTTP/1.1 200 OK
Content-Type: application/json
{
"age": 8,
"breed": "chow",
"tariffs": [
{
"name": "Dog OP _ Basic",
"rate": 12.4
}
]
}
REST API fürRechner
func main() {
r.Post("/api/quote", HandleQuote)
}
func HandleQuote(w http.ResponseWriter, r *http.Request) {
1
// 1. JSON Request lesen
2
var q Quote
3
json.NewDecoder(r.Body).Decode(&q)
4
5
// 2. Tarif berechnen
6
tariff := Tariff{Name: "Dog OP _ Basic", Rate: 12.4}
7
quote.Tariffs = []Tariff{tariff}
8
9
// 3. JSON Response schreiben
10
json.NewEncoder(w).Encode(quote)
11
}
12
13
14
r := chi.NewRouter()
15
16
17
18
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
19
w.Write([]byte("Hello DogOp!"))
20
})
21
22
http.ListenAndServe(":8080", r)
23
24
func HandleQuote(w http.ResponseWriter, r *http.Request) {
}
1
// 1. JSON Request lesen
2
var q Quote
3
json.NewDecoder(r.Body).Decode(&q)
4
5
// 2. Tarif berechnen
6
tariff := Tariff{Name: "Dog OP _ Basic", Rate: 12.4}
7
quote.Tariffs = []Tariff{tariff}
8
9
// 3. JSON Response schreiben
10
json.NewEncoder(w).Encode(quote)
11
12
13
func main() {
14
r := chi.NewRouter()
15
16
r.Post("/api/quote", HandleQuote)
17
18
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
19
w.Write([]byte("Hello DogOp!"))
20
})
21
22
http.ListenAndServe(":8080", r)
23
}
24
// 1. JSON Request lesen
var q Quote
json.NewDecoder(r.Body).Decode(&q)
func HandleQuote(w http.ResponseWriter, r *http.Request) {
1
2
3
4
5
// 2. Tarif berechnen
6
tariff := Tariff{Name: "Dog OP _ Basic", Rate: 12.4}
7
quote.Tariffs = []Tariff{tariff}
8
9
// 3. JSON Response schreiben
10
json.NewEncoder(w).Encode(quote)
11
}
12
13
func main() {
14
r := chi.NewRouter()
15
16
r.Post("/api/quote", HandleQuote)
17
18
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
19
w.Write([]byte("Hello DogOp!"))
20
})
21
22
http.ListenAndServe(":8080", r)
23
}
24
// 2. Tarif berechnen
tariff := Tariff{Name: "Dog OP _ Basic", Rate: 12.4}
quote.Tariffs = []Tariff{tariff}
func HandleQuote(w http.ResponseWriter, r *http.Request) {
1
// 1. JSON Request lesen
2
var q Quote
3
json.NewDecoder(r.Body).Decode(&q)
4
5
6
7
8
9
// 3. JSON Response schreiben
10
json.NewEncoder(w).Encode(quote)
11
}
12
13
func main() {
14
r := chi.NewRouter()
15
16
r.Post("/api/quote", HandleQuote)
17
18
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
19
w.Write([]byte("Hello DogOp!"))
20
})
21
22
http.ListenAndServe(":8080", r)
23
}
24
// 3. JSON Response schreiben
json.NewEncoder(w).Encode(quote)
func HandleQuote(w http.ResponseWriter, r *http.Request) {
1
// 1. JSON Request lesen
2
var q Quote
3
json.NewDecoder(r.Body).Decode(&q)
4
5
// 2. Tarif berechnen
6
tariff := Tariff{Name: "Dog OP _ Basic", Rate: 12.4}
7
quote.Tariffs = []Tariff{tariff}
8
9
10
11
}
12
13
func main() {
14
r := chi.NewRouter()
15
16
r.Post("/api/quote", HandleQuote)
17
18
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
19
w.Write([]byte("Hello DogOp!"))
20
})
21
22
http.ListenAndServe(":8080", r)
23
}
24
func HandleQuote(w http.ResponseWriter, r *http.Request) {
// 1. JSON Request lesen
var q Quote
json.NewDecoder(r.Body).Decode(&q)
// 2. Tarif berechnen
tariff := Tariff{Name: "Dog OP _ Basic", Rate: 12.4}
quote.Tariffs = []Tariff{tariff}
// 3. JSON Response schreiben
json.NewEncoder(w).Encode(quote)
}
func main() {
r := chi.NewRouter()
r.Post("/api/quote", HandleQuote)
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello DogOp!"))
})
http.ListenAndServe(":8080", r)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Achtung, noch ohne Fehlerhandling! 💣
W-JAX 2023: Go über den Wolken
Struct statt Klasse
type Tariff struct {
Name string `json:"name"`
Rate float64 `json:"rate"`
}
1
2
3
4
5
type Quote struct {
6
Age int `json:"age"`
7
Breed string `json:"breed"`
8
Tariffs []Tariff `json:"tariffs"`
9
}
10
type Quote struct {
Age int `json:"age"`
Breed string `json:"breed"`
Tariffs []Tariff `json:"tariffs"`
}
type Tariff struct {
1
Name string `json:"name"`
2
Rate float64 `json:"rate"`
3
}
4
5
6
7
8
9
10
type Tariff struct {
Name string `json:"name"`
Rate float64 `json:"rate"`
}
type Quote struct {
Age int `json:"age"`
Breed string `json:"breed"`
Tariffs []Tariff `json:"tariffs"`
}
1
2
3
4
5
6
7
8
9
10
// Struct erzeugen
tariff := Tariff{Name: "Dog OP _ Basic", Rate: 12.4}
Fehler💣
func HandleQuote(w http.ResponseWriter, r *http.Request) {
}
1
// 1. JSON Request lesen
2
var q Quote
3
json.NewDecoder(r.Body).Decode(&q) // 💣 Fehler möglich!
4
5
// 2. Tarif berechnen
6
tariff := Tariff{Name: "Dog OP _ Basic", Rate: 12.4}
7
quote.Tariffs = []Tariff{tariff}
8
9
// 3. JSON Response schreiben
10
json.NewEncoder(w).Encode(quote) // 💣 Fehler möglich!
11
12
json.NewDecoder(r.Body).Decode(&q) // 💣 Fehler möglich!
json.NewEncoder(w).Encode(quote) // 💣 Fehler möglich!
func HandleQuote(w http.ResponseWriter, r *http.Request) {
1
// 1. JSON Request lesen
2
var q Quote
3
4
5
// 2. Tarif berechnen
6
tariff := Tariff{Name: "Dog OP _ Basic", Rate: 12.4}
7
quote.Tariffs = []Tariff{tariff}
8
9
// 3. JSON Response schreiben
10
11
}
12
W-JAX 2023: Go über den Wolken
Fehler💣
Go kennt kein spezielles Konstrukt zur
Fehlerbehandlung
#
Fehlersind normale Rückgabewerte
#
Fehler💣
func HandleQuote(w http.ResponseWriter, r *http.Request) {
}
1
// 1. JSON Request lesen
2
var q Quote
3
4
// Potentieller Fehler
5
err := json.NewDecoder(r.Body).Decode(&q)
6
7
// Auf Fehler prüfen
8
if err != nil {
9
// Fehler behandeln
10
http.Error(w, "Could not decode quote.😔", http.StatusBadRequest)
11
return
12
}
13
14
// ...
15
16
// Potentieller Fehler
err := json.NewDecoder(r.Body).Decode(&q)
func HandleQuote(w http.ResponseWriter, r *http.Request) {
1
// 1. JSON Request lesen
2
var q Quote
3
4
5
6
7
// Auf Fehler prüfen
8
if err != nil {
9
// Fehler behandeln
10
http.Error(w, "Could not decode quote.😔", http.StatusBadRequest)
11
return
12
}
13
14
// ...
15
}
16
// Auf Fehler prüfen
if err != nil {
}
func HandleQuote(w http.ResponseWriter, r *http.Request) {
1
// 1. JSON Request lesen
2
var q Quote
3
4
// Potentieller Fehler
5
err := json.NewDecoder(r.Body).Decode(&q)
6
7
8
9
// Fehler behandeln
10
http.Error(w, "Could not decode quote.😔", http.StatusBadRequest)
11
return
12
13
14
// ...
15
}
16
func HandleQuote(w http.ResponseWriter, r *http.Request) {
// 1. JSON Request lesen
var q Quote
// Potentieller Fehler
err := json.NewDecoder(r.Body).Decode(&q)
// Auf Fehler prüfen
if err != nil {
// Fehler behandeln
http.Error(w, "Could not decode quote.😔", http.StatusBadRequest)
return
}
// ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
HTTPHandlertesten
Unit Tests in Standardlib enthalten
#
Tests in Datei main_test.go
#
HTTPHandlertesten
func TestHandleQuote(t *testing.T) {
}
1
// 1. HTTP Recorder erstellen
2
recorder := httptest.NewRecorder()
3
4
// 2. Request erstellen (mit Body)
5
body := `
6
{
7
"age": 8,
8
"breed": "chow"
9
}
10
`
11
req, _ := http.NewRequest("GET", "/api/quote", strings.NewReader(body))
12
13
// 3. Handler Funktion aufrufen
14
HandleQuote(recorder, req)
15
16
// 4. Return Code prüfen
17
if recorder.Code != http.StatusOK {
18
t.Errorf("Wrong status: got %v expected %v", recorder.Code, http.StatusOK)
19
}
20
21
// 1. HTTP Recorder erstellen
recorder := httptest.NewRecorder()
func TestHandleQuote(t *testing.T) {
1
2
3
4
// 2. Request erstellen (mit Body)
5
body := `
6
{
7
"age": 8,
8
"breed": "chow"
9
}
10
`
11
req, _ := http.NewRequest("GET", "/api/quote", strings.NewReader(body))
12
13
// 3. Handler Funktion aufrufen
14
HandleQuote(recorder, req)
15
16
// 4. Return Code prüfen
17
if recorder.Code != http.StatusOK {
18
t.Errorf("Wrong status: got %v expected %v", recorder.Code, http.StatusOK)
19
}
20
}
21
// 2. Request erstellen (mit Body)
body := `
{
"age": 8,
"breed": "chow"
}
`
req, _ := http.NewRequest("GET", "/api/quote", strings.NewReader(body))
func TestHandleQuote(t *testing.T) {
1
// 1. HTTP Recorder erstellen
2
recorder := httptest.NewRecorder()
3
4
5
6
7
8
9
10
11
12
13
// 3. Handler Funktion aufrufen
14
HandleQuote(recorder, req)
15
16
// 4. Return Code prüfen
17
if recorder.Code != http.StatusOK {
18
t.Errorf("Wrong status: got %v expected %v", recorder.Code, http.StatusOK)
19
}
20
}
21
// 3. Handler Funktion aufrufen
HandleQuote(recorder, req)
func TestHandleQuote(t *testing.T) {
1
// 1. HTTP Recorder erstellen
2
recorder := httptest.NewRecorder()
3
4
// 2. Request erstellen (mit Body)
5
body := `
6
{
7
"age": 8,
8
"breed": "chow"
9
}
10
`
11
req, _ := http.NewRequest("GET", "/api/quote", strings.NewReader(body))
12
13
14
15
16
// 4. Return Code prüfen
17
if recorder.Code != http.StatusOK {
18
t.Errorf("Wrong status: got %v expected %v", recorder.Code, http.StatusOK)
19
}
20
}
21
// 4. Return Code prüfen
if recorder.Code != http.StatusOK {
t.Errorf("Wrong status: got %v expected %v", recorder.Code, http.StatusOK)
}
func TestHandleQuote(t *testing.T) {
1
// 1. HTTP Recorder erstellen
2
recorder := httptest.NewRecorder()
3
4
// 2. Request erstellen (mit Body)
5
body := `
6
{
7
"age": 8,
8
"breed": "chow"
9
}
10
`
11
req, _ := http.NewRequest("GET", "/api/quote", strings.NewReader(body))
12
13
// 3. Handler Funktion aufrufen
14
HandleQuote(recorder, req)
15
16
17
18
19
20
}
21
func TestHandleQuote(t *testing.T) {
// 1. HTTP Recorder erstellen
recorder := httptest.NewRecorder()
// 2. Request erstellen (mit Body)
body := `
{
"age": 8,
"breed": "chow"
}
`
req, _ := http.NewRequest("GET", "/api/quote", strings.NewReader(body))
// 3. Handler Funktion aufrufen
HandleQuote(recorder, req)
// 4. Return Code prüfen
if recorder.Code != http.StatusOK {
t.Errorf("Wrong status: got %v expected %v", recorder.Code, http.StatusOK)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
HTTPHandlertesten
go test -v ./...
=== RUN TestHandleQuote
--- PASS: TestHandleQuote (0.00s)
PASS
ok crossnative/dogop 0.228s
HTTPHandlertesten
Port perUmgebungsvariable
konfigurieren
// Einfach per Standardlib
port := os.Getenv("DOGOP_PORT")
1
2
function main() {
3
4
http.ListenAndServe(fmt.Sprintf(":%v", config.Port), r)
5
}
6
// Einfach per Standardlib
port := os.Getenv("DOGOP_PORT")
http.ListenAndServe(fmt.Sprintf(":%v", config.Port), r)
1
2
function main() {
3
4
5
}
6
Port perUmgebungsvariable
konfigurieren
// Oder mit envconfig von Kelsey Hightower
type Config struct {
Port string `default:"8080"`
}
1
2
3
4
5
6
function main() {
7
var config Config
8
err := envconfig.Process("dogop", &config)
9
if err != nil {
10
log.Fatal(err.Error())
11
}
12
13
http.ListenAndServe(fmt.Sprintf(":%v", config.Port), r)
14
}
15
// Oder mit envconfig von Kelsey Hightower
type Config struct {
Port string `default:"8080"`
}
1
2
3
4
5
6
function main() {
7
var config Config
8
err := envconfig.Process("dogop", &config)
9
if err != nil {
10
log.Fatal(err.Error())
11
}
12
13
http.ListenAndServe(fmt.Sprintf(":%v", config.Port), r)
14
}
15
// Oder mit envconfig von Kelsey Hightower
type Config struct {
Port string `default:"8080"`
}
var config Config
err := envconfig.Process("dogop", &config)
1
2
3
4
5
6
function main() {
7
8
9
if err != nil {
10
log.Fatal(err.Error())
11
}
12
13
http.ListenAndServe(fmt.Sprintf(":%v", config.Port), r)
14
}
15
// Oder mit envconfig von Kelsey Hightower
type Config struct {
Port string `default:"8080"`
}
var config Config
err := envconfig.Process("dogop", &config)
http.ListenAndServe(fmt.Sprintf(":%v", config.Port), r)
1
2
3
4
5
6
function main() {
7
8
9
if err != nil {
10
log.Fatal(err.Error())
11
}
12
13
14
}
15
Dockerfile fürDogOP
# 1. DogOp Builder
FROM golang as builder
WORKDIR /app
ADD . /app
RUN CGO_ENABLED=0 go build -ldflags="-w -s" -o build/dogop .
1
2
3
4
5
6
# 2. DogOp Container
7
FROM alpine
8
COPY --from=builder /app/build/dogop /usr/bin/
9
EXPOSE 8080
10
ENTRYPOINT ["/usr/bin/dogop"]
11
# 2. DogOp Container
FROM alpine
COPY --from=builder /app/build/dogop /usr/bin/
EXPOSE 8080
ENTRYPOINT ["/usr/bin/dogop"]
# 1. DogOp Builder
1
FROM golang as builder
2
WORKDIR /app
3
ADD . /app
4
RUN CGO_ENABLED=0 go build -ldflags="-w -s" -o build/dogop .
5
6
7
8
9
10
11
# 1. DogOp Builder
FROM golang as builder
WORKDIR /app
ADD . /app
RUN CGO_ENABLED=0 go build -ldflags="-w -s" -o build/dogop .
# 2. DogOp Container
FROM alpine
COPY --from=builder /app/build/dogop /usr/bin/
EXPOSE 8080
ENTRYPOINT ["/usr/bin/dogop"]
1
2
3
4
5
6
7
8
9
10
11
Tarifrechner
DogOp v0.2
CRUD API fürAngebote
Create Request
POST /api/offer
content-type: application/json
{
"name": "Rollo",
"age": 8,
"breed": "chow",
"customer": "jan"
}
Create Response
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": "427ed4de",
"customer": "jan",
"age": 8,
"breed": "chow",
"name": "Rollo"
}
Read Request
GET /api/offer/427ed4de
Read Response
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": "427ed4de",
"customer": "jan",
"age": 8,
"breed": "chow",
"name": "Rollo"
}
CRUD API fürAngebote
r.Route("/api/offer", func(r chi.Router) {
r.Post("/", HandleCreateOffer(offerRepository))
r.Get("/{ID}", HandleReadOffer(offerRepository))
})
Create Request mit HandlerFunction
func HandleCreateOffer(offerRepository *OfferRepository) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
}
}
1
2
// 1. JSON Request lesen
3
var offer Offer
4
json.NewDecoder(req.Body).Decode(&offer)
5
6
// 2. Offer speichern
7
createdOffer, _ := offerRepository.Insert(req.Context(), &offer)
8
9
// 3. JSON Response schreiben
10
json.NewEncoder(w).Encode(createdOffer)
11
12
13
// 1. JSON Request lesen
var offer Offer
json.NewDecoder(req.Body).Decode(&offer)
// 2. Offer speichern
createdOffer, _ := offerRepository.Insert(req.Context(), &offer)
// 3. JSON Response schreiben
json.NewEncoder(w).Encode(createdOffer)
func HandleCreateOffer(offerRepository *OfferRepository) http.HandlerFunc {
1
return func(w http.ResponseWriter, req *http.Request) {
2
3
4
5
6
7
8
9
10
11
}
12
}
13
func HandleCreateOffer(offerRepository *OfferRepository) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
// 1. JSON Request lesen
var offer Offer
json.NewDecoder(req.Body).Decode(&offer)
// 2. Offer speichern
createdOffer, _ := offerRepository.Insert(req.Context(), &offer)
// 3. JSON Response schreiben
json.NewEncoder(w).Encode(createdOffer)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
Angebote speichern in Postgres
pgx als Postgres Driverund Toolkit
#
Interface database/sql in Go Standardlib
#
Angebote speichern in Postgres
type OfferRepository struct {
connPool *pgxpool.Pool
1
2
}
3
4
func (r *OfferRepository) Insert(ctx context.Context, offer *Offer)
5
// ...
6
}
7
}
func (r *OfferRepository) Insert(ctx context.Context, offer *Offer)
// ...
}
type OfferRepository struct {
1
connPool *pgxpool.Pool
2
3
4
5
6
7
type OfferRepository struct {
connPool *pgxpool.Pool
}
func (r *OfferRepository) Insert(ctx context.Context, offer *Offer)
// ...
}
1
2
3
4
5
6
7
Angebote speichern in Postgres
func (r *OfferRepository) Insert(ctx context.Context, offer *Offer) (*Offer, error) {
}
1
// 1. ID generieren
2
offer.ID = uuid.New().String()
3
4
// 2. Transaktion beginnen
5
tx, _ := r.connPool.Begin(ctx)
6
defer tx.Rollback(ctx)
7
8
// 3. Offer per Insert speichern
9
_, err := tx.Exec(
10
ctx,
11
`INSERT INTO offers
12
(id, customer, age, breed, name)
13
VALUES
14
($1, $2, $3, $4, $5)`,
15
offer.ID, offer.Customer, offer.Age, offer.Breed, offer.Name,
16
)
17
if err != nil {
18
return nil, err
19
}
20
21
// 4. Transaktion commiten
22
tx.Commit(ctx)
23
24
// 5. Gespeicherte Offer zurückgeben
25
return offer, nil
26
27
// 1. ID generieren
offer.ID = uuid.New().String()
func (r *OfferRepository) Insert(ctx context.Context, offer *Offer) (*Offer, error) {
1
2
3
4
// 2. Transaktion beginnen
5
tx, _ := r.connPool.Begin(ctx)
6
defer tx.Rollback(ctx)
7
8
// 3. Offer per Insert speichern
9
_, err := tx.Exec(
10
ctx,
11
`INSERT INTO offers
12
(id, customer, age, breed, name)
13
VALUES
14
($1, $2, $3, $4, $5)`,
15
offer.ID, offer.Customer, offer.Age, offer.Breed, offer.Name,
16
)
17
if err != nil {
18
return nil, err
19
}
20
21
// 4. Transaktion commiten
22
tx.Commit(ctx)
23
24
// 5. Gespeicherte Offer zurückgeben
25
return offer, nil
26
}
27
// 2. Transaktion beginnen
tx, _ := r.connPool.Begin(ctx)
defer tx.Rollback(ctx)
func (r *OfferRepository) Insert(ctx context.Context, offer *Offer) (*Offer, error) {
1
// 1. ID generieren
2
offer.ID = uuid.New().String()
3
4
5
6
7
8
// 3. Offer per Insert speichern
9
_, err := tx.Exec(
10
ctx,
11
`INSERT INTO offers
12
(id, customer, age, breed, name)
13
VALUES
14
($1, $2, $3, $4, $5)`,
15
offer.ID, offer.Customer, offer.Age, offer.Breed, offer.Name,
16
)
17
if err != nil {
18
return nil, err
19
}
20
21
// 4. Transaktion commiten
22
tx.Commit(ctx)
23
24
// 5. Gespeicherte Offer zurückgeben
25
return offer, nil
26
}
27
// 3. Offer per Insert speichern
_, err := tx.Exec(
ctx,
`INSERT INTO offers
(id, customer, age, breed, name)
VALUES
($1, $2, $3, $4, $5)`,
offer.ID, offer.Customer, offer.Age, offer.Breed, offer.Name,
)
if err != nil {
return nil, err
}
func (r *OfferRepository) Insert(ctx context.Context, offer *Offer) (*Offer, error) {
1
// 1. ID generieren
2
offer.ID = uuid.New().String()
3
4
// 2. Transaktion beginnen
5
tx, _ := r.connPool.Begin(ctx)
6
defer tx.Rollback(ctx)
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 4. Transaktion commiten
22
tx.Commit(ctx)
23
24
// 5. Gespeicherte Offer zurückgeben
25
return offer, nil
26
}
27
// 4. Transaktion commiten
tx.Commit(ctx)
func (r *OfferRepository) Insert(ctx context.Context, offer *Offer) (*Offer, error) {
1
// 1. ID generieren
2
offer.ID = uuid.New().String()
3
4
// 2. Transaktion beginnen
5
tx, _ := r.connPool.Begin(ctx)
6
defer tx.Rollback(ctx)
7
8
// 3. Offer per Insert speichern
9
_, err := tx.Exec(
10
ctx,
11
`INSERT INTO offers
12
(id, customer, age, breed, name)
13
VALUES
14
($1, $2, $3, $4, $5)`,
15
offer.ID, offer.Customer, offer.Age, offer.Breed, offer.Name,
16
)
17
if err != nil {
18
return nil, err
19
}
20
21
22
23
24
// 5. Gespeicherte Offer zurückgeben
25
return offer, nil
26
}
27
// 5. Gespeicherte Offer zurückgeben
return offer, nil
func (r *OfferRepository) Insert(ctx context.Context, offer *Offer) (*Offer, error) {
1
// 1. ID generieren
2
offer.ID = uuid.New().String()
3
4
// 2. Transaktion beginnen
5
tx, _ := r.connPool.Begin(ctx)
6
defer tx.Rollback(ctx)
7
8
// 3. Offer per Insert speichern
9
_, err := tx.Exec(
10
ctx,
11
`INSERT INTO offers
12
(id, customer, age, breed, name)
13
VALUES
14
($1, $2, $3, $4, $5)`,
15
offer.ID, offer.Customer, offer.Age, offer.Breed, offer.Name,
16
)
17
if err != nil {
18
return nil, err
19
}
20
21
// 4. Transaktion commiten
22
tx.Commit(ctx)
23
24
25
26
}
27
func (r *OfferRepository) Insert(ctx context.Context, offer *Offer) (*Offer, error) {
// 1. ID generieren
offer.ID = uuid.New().String()
// 2. Transaktion beginnen
tx, _ := r.connPool.Begin(ctx)
defer tx.Rollback(ctx)
// 3. Offer per Insert speichern
_, err := tx.Exec(
ctx,
`INSERT INTO offers
(id, customer, age, breed, name)
VALUES
($1, $2, $3, $4, $5)`,
offer.ID, offer.Customer, offer.Age, offer.Breed, offer.Name,
)
if err != nil {
return nil, err
}
// 4. Transaktion commiten
tx.Commit(ctx)
// 5. Gespeicherte Offer zurückgeben
return offer, nil
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Aus dem Context gerissen
createdOffer, _ := offerRepository.Insert(req.Context(), &offer)
tx, _ := r.connPool.Begin(ctx)
defer tx.Rollback(ctx)
_, err := tx.Exec(
ctx,
)
tx.Commit(ctx)
return func(w http.ResponseWriter, req *http.Request) {
1
// ...
2
3
// 2. Offer speichern
4
5
6
// ...
7
}
8
9
func (r *OfferRepository) Insert(ctx context.Context, offer *Offer) (*Offer, error) {
10
// ..
11
12
// 2. Transaktion beginnen
13
14
15
16
// 3. Offer per Insert speichern
17
18
19
`INSERT INTO offers
20
(id, customer, age, breed, name)
21
VALUES
22
($1, $2, $3, $4, $5)`,
23
offer.ID, offer.Customer, offer.Age, offer.Breed, offer.Name,
24
25
// ...
26
27
// 4. Transaktion commiten
28
29
30
// ...
31
}
32
Fallschirm und
Rettungsgurt
Health Check
Nutzung von health-go (hellofresh)
Integration des Checks fürpgx
#
#
Health Check
// Register Health Check
h, _ := health.New(health.WithChecks(
health.Config{
Name: "db",
Timeout: time.Second * 2,
SkipOnErr: false,
Check: healthPgx.New(healthPgx.Config{
DSN: config.Db,
}),
},
))
1
2
3
4
5
6
7
8
9
10
11
12
// Register Handler Function
13
r.Get("/health", h.HandlerFunc)
14
// Register Handler Function
r.Get("/health", h.HandlerFunc)
// Register Health Check
1
h, _ := health.New(health.WithChecks(
2
health.Config{
3
Name: "db",
4
Timeout: time.Second * 2,
5
SkipOnErr: false,
6
Check: healthPgx.New(healthPgx.Config{
7
DSN: config.Db,
8
}),
9
},
10
))
11
12
13
14
// Register Health Check
h, _ := health.New(health.WithChecks(
health.Config{
Name: "db",
Timeout: time.Second * 2,
SkipOnErr: false,
Check: healthPgx.New(healthPgx.Config{
DSN: config.Db,
}),
},
))
// Register Handler Function
r.Get("/health", h.HandlerFunc)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Problem Details
UngültigerRequest
GET /api/offer/-invalid-
Response mit Problem Details
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json
{
"reason": "invalid UUID format",
"status": 400,
"title": "invalid request"
}
Problem Details
W-JAX 2023: Go über den Wolken
Fehlerbehandeln
Mit Problem Details
if err != nil {
problem.New(
problem.Title("invalid request"),
problem.Wrap(err),
problem.Status(http.StatusBadRequest),
).WriteTo(w)
return
}
Mit Standardbibliothek
if err != nil {
http.Error(w, "invalid request", http.StatusBadRequest)
return
}
Middleware
cross-cutting Features füralle Requests
(z.B. Logging, Authentifizierung)
#
erzeugen eine Kette von Handlern
router=> middleware handler=> application handler
#
implementieren das Interface http.Handler
#
Middleware loggt Requests
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("%v requested URL %v", r.Host, r.URL)
next.ServeHTTP(w, r)
})
}
1
2
3
4
5
6
7
func main() {
8
r := chi.NewRouter()
9
10
// Nutze Logging Middleware
11
r.Use(loggingMiddleware)
12
13
http.ListenAndServe(":8080", r)
14
}
15
// Nutze Logging Middleware
r.Use(loggingMiddleware)
func loggingMiddleware(next http.Handler) http.Handler {
1
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2
log.Printf("%v requested URL %v", r.Host, r.URL)
3
next.ServeHTTP(w, r)
4
})
5
}
6
7
func main() {
8
r := chi.NewRouter()
9
10
11
12
13
http.ListenAndServe(":8080", r)
14
}
15
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("%v requested URL %v", r.Host, r.URL)
next.ServeHTTP(w, r)
})
}
func main() {
r := chi.NewRouter()
// Nutze Logging Middleware
r.Use(loggingMiddleware)
http.ListenAndServe(":8080", r)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Middlewares nutzen
// Basis Middleware
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
func main() {
1
r := chi.NewRouter()
2
3
4
5
6
7
8
9
// Timeout über Request Context setzen
10
r.Use(middleware.Timeout(60 * time.Second))
11
12
http.ListenAndServe(":8080", r)
13
}
14
// Timeout über Request Context setzen
r.Use(middleware.Timeout(60 * time.Second))
func main() {
1
r := chi.NewRouter()
2
3
// Basis Middleware
4
r.Use(middleware.RequestID)
5
r.Use(middleware.RealIP)
6
r.Use(middleware.Logger)
7
r.Use(middleware.Recoverer)
8
9
10
11
12
http.ListenAndServe(":8080", r)
13
}
14
// Basis Middleware
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
// Timeout über Request Context setzen
r.Use(middleware.Timeout(60 * time.Second))
func main() {
1
r := chi.NewRouter()
2
3
4
5
6
7
8
9
10
11
12
http.ListenAndServe(":8080", r)
13
}
14
Zusammenfassung DogOP
v0.1
1. Go Projekt aufsetzen
2. einfache Rest API Handler
3. Konfiguration per
Umgebungsvariable
4. Structs
5. Fehlerbehandeln
6. Unit Tests
7. DockerContainerbauen
v0.2
1. CRUD API fürAngebote
2. Angebote in Postgres speichern
3. Context
4. Health Check
5. Problem Details
6. Middleware
Cloud Native Booster
💪Skalierbar
🚂Zuverlässig
☯️Einfach
3 Gründe fürGo
1. Einfach
2. Mächtig
3. Langweilig
Jan Stamer
jan.stamer@crossnative.com
Go überden Wolken
W-JAX 2023: Go über den Wolken

Weitere ähnliche Inhalte

Ähnlich wie W-JAX 2023: Go über den Wolken

.NET Summit 2016 München: EcmaScript 2015+ with TypeScript
.NET Summit 2016 München: EcmaScript 2015+ with TypeScript.NET Summit 2016 München: EcmaScript 2015+ with TypeScript
.NET Summit 2016 München: EcmaScript 2015+ with TypeScript
Manfred Steyer
 
Prototype 1.7
Prototype 1.7Prototype 1.7
Prototype 1.7
msebel
 
Backend-Services mit Rust
Backend-Services mit RustBackend-Services mit Rust
Backend-Services mit Rust
Jens Siebert
 
Rack-Middleware
Rack-MiddlewareRack-Middleware
Rack-Middleware
Hussein Morsy
 
Spark vs. PL/SQL
Spark vs. PL/SQLSpark vs. PL/SQL
Skalierbare Anwendungen mit Google Go
Skalierbare Anwendungen mit Google GoSkalierbare Anwendungen mit Google Go
Skalierbare Anwendungen mit Google Go
Frank Müller
 
Einführung React Native
Einführung React NativeEinführung React Native
Einführung React Native
Markus Günther
 
Testing tools
Testing toolsTesting tools
Testing tools
Sebastian Springer
 
Javascript auf Client und Server mit node.js - webtech 2010
Javascript auf Client und Server mit node.js - webtech 2010Javascript auf Client und Server mit node.js - webtech 2010
Javascript auf Client und Server mit node.js - webtech 2010
Dirk Ginader
 
Funktionale Reaktive Programmierung mit Sodium
Funktionale Reaktive Programmierung mit SodiumFunktionale Reaktive Programmierung mit Sodium
Funktionale Reaktive Programmierung mit Sodium
Torsten Fink
 
APIs mit Zend\Expressive erstellen
APIs mit Zend\Expressive erstellenAPIs mit Zend\Expressive erstellen
APIs mit Zend\Expressive erstellen
Ralf Eggert
 
Devs@Home - Einführung in Go
Devs@Home - Einführung in GoDevs@Home - Einführung in Go
Devs@Home - Einführung in Go
Frank Müller
 
Check cisco voice
Check cisco voiceCheck cisco voice
Check cisco voice
bboguhn
 
Modern angular 02_angular_mit_type_script
Modern angular 02_angular_mit_type_scriptModern angular 02_angular_mit_type_script
Modern angular 02_angular_mit_type_script
Manfred Steyer
 
Migrationspfade für Angular 2
Migrationspfade für Angular 2Migrationspfade für Angular 2
Migrationspfade für Angular 2
Manfred Steyer
 
Angular von 0 auf 100
Angular von 0 auf 100Angular von 0 auf 100
Angular von 0 auf 100
Yvette Teiken
 
Android Development ...and the daily challenges
Android Development ...and the daily challengesAndroid Development ...and the daily challenges
Android Development ...and the daily challenges
Dominik Helleberg
 
Wjax integrationsprojekte auf dem weg zur continuous delivery 2011 11-10
Wjax integrationsprojekte auf dem weg zur continuous delivery 2011 11-10Wjax integrationsprojekte auf dem weg zur continuous delivery 2011 11-10
Wjax integrationsprojekte auf dem weg zur continuous delivery 2011 11-10
Ralf Sigmund
 
Ein Gopher im Netz
Ein Gopher im NetzEin Gopher im Netz
Ein Gopher im Netz
Frank Müller
 

Ähnlich wie W-JAX 2023: Go über den Wolken (20)

.NET Summit 2016 München: EcmaScript 2015+ with TypeScript
.NET Summit 2016 München: EcmaScript 2015+ with TypeScript.NET Summit 2016 München: EcmaScript 2015+ with TypeScript
.NET Summit 2016 München: EcmaScript 2015+ with TypeScript
 
Prototype 1.7
Prototype 1.7Prototype 1.7
Prototype 1.7
 
Backend-Services mit Rust
Backend-Services mit RustBackend-Services mit Rust
Backend-Services mit Rust
 
Rack-Middleware
Rack-MiddlewareRack-Middleware
Rack-Middleware
 
Spark vs. PL/SQL
Spark vs. PL/SQLSpark vs. PL/SQL
Spark vs. PL/SQL
 
Skalierbare Anwendungen mit Google Go
Skalierbare Anwendungen mit Google GoSkalierbare Anwendungen mit Google Go
Skalierbare Anwendungen mit Google Go
 
Ruby on Rails SS09 08
Ruby on Rails SS09 08Ruby on Rails SS09 08
Ruby on Rails SS09 08
 
Einführung React Native
Einführung React NativeEinführung React Native
Einführung React Native
 
Testing tools
Testing toolsTesting tools
Testing tools
 
Javascript auf Client und Server mit node.js - webtech 2010
Javascript auf Client und Server mit node.js - webtech 2010Javascript auf Client und Server mit node.js - webtech 2010
Javascript auf Client und Server mit node.js - webtech 2010
 
Funktionale Reaktive Programmierung mit Sodium
Funktionale Reaktive Programmierung mit SodiumFunktionale Reaktive Programmierung mit Sodium
Funktionale Reaktive Programmierung mit Sodium
 
APIs mit Zend\Expressive erstellen
APIs mit Zend\Expressive erstellenAPIs mit Zend\Expressive erstellen
APIs mit Zend\Expressive erstellen
 
Devs@Home - Einführung in Go
Devs@Home - Einführung in GoDevs@Home - Einführung in Go
Devs@Home - Einführung in Go
 
Check cisco voice
Check cisco voiceCheck cisco voice
Check cisco voice
 
Modern angular 02_angular_mit_type_script
Modern angular 02_angular_mit_type_scriptModern angular 02_angular_mit_type_script
Modern angular 02_angular_mit_type_script
 
Migrationspfade für Angular 2
Migrationspfade für Angular 2Migrationspfade für Angular 2
Migrationspfade für Angular 2
 
Angular von 0 auf 100
Angular von 0 auf 100Angular von 0 auf 100
Angular von 0 auf 100
 
Android Development ...and the daily challenges
Android Development ...and the daily challengesAndroid Development ...and the daily challenges
Android Development ...and the daily challenges
 
Wjax integrationsprojekte auf dem weg zur continuous delivery 2011 11-10
Wjax integrationsprojekte auf dem weg zur continuous delivery 2011 11-10Wjax integrationsprojekte auf dem weg zur continuous delivery 2011 11-10
Wjax integrationsprojekte auf dem weg zur continuous delivery 2011 11-10
 
Ein Gopher im Netz
Ein Gopher im NetzEin Gopher im Netz
Ein Gopher im Netz
 

Mehr von Jan Stamer

CloudLand 2023: Rock, Paper, Scissors Cloud Competition - Go vs. Java
CloudLand 2023: Rock, Paper, Scissors Cloud Competition - Go vs. JavaCloudLand 2023: Rock, Paper, Scissors Cloud Competition - Go vs. Java
CloudLand 2023: Rock, Paper, Scissors Cloud Competition - Go vs. Java
Jan Stamer
 
IT-Tage 2021: Java to Go - Google Go für Java-Entwickler
IT-Tage 2021: Java to Go - Google Go für Java-Entwickler IT-Tage 2021: Java to Go - Google Go für Java-Entwickler
IT-Tage 2021: Java to Go - Google Go für Java-Entwickler
Jan Stamer
 
entwickler.de Go Day: Go Web Development 101
entwickler.de Go Day: Go Web Development 101entwickler.de Go Day: Go Web Development 101
entwickler.de Go Day: Go Web Development 101
Jan Stamer
 
betterCode() Go: Einstieg in Go, Standard-Library und Ökosystem
betterCode() Go: Einstieg in Go, Standard-Library und ÖkosystembetterCode() Go: Einstieg in Go, Standard-Library und Ökosystem
betterCode() Go: Einstieg in Go, Standard-Library und Ökosystem
Jan Stamer
 
JCON 2021: Turbo powered Web Apps
JCON 2021: Turbo powered Web AppsJCON 2021: Turbo powered Web Apps
JCON 2021: Turbo powered Web Apps
Jan Stamer
 
DevOpsCon 2021: Go Web Development 101
DevOpsCon 2021: Go Web Development 101DevOpsCon 2021: Go Web Development 101
DevOpsCon 2021: Go Web Development 101
Jan Stamer
 
JavaForum Nord 2021: Java to Go - Google Go für Java-Entwickler
JavaForum Nord 2021: Java to Go - Google Go für Java-EntwicklerJavaForum Nord 2021: Java to Go - Google Go für Java-Entwickler
JavaForum Nord 2021: Java to Go - Google Go für Java-Entwickler
Jan Stamer
 
Karlsruher Entwicklertag 2021: Turbo powered Web Apps
Karlsruher Entwicklertag 2021: Turbo powered Web AppsKarlsruher Entwicklertag 2021: Turbo powered Web Apps
Karlsruher Entwicklertag 2021: Turbo powered Web Apps
Jan Stamer
 
Jugs HH: Elefant unter Strom - von OldSQL über NoSQL zu NewSQL?
Jugs HH: Elefant unter Strom - von OldSQL über NoSQL zu NewSQL?Jugs HH: Elefant unter Strom - von OldSQL über NoSQL zu NewSQL?
Jugs HH: Elefant unter Strom - von OldSQL über NoSQL zu NewSQL?
Jan Stamer
 

Mehr von Jan Stamer (9)

CloudLand 2023: Rock, Paper, Scissors Cloud Competition - Go vs. Java
CloudLand 2023: Rock, Paper, Scissors Cloud Competition - Go vs. JavaCloudLand 2023: Rock, Paper, Scissors Cloud Competition - Go vs. Java
CloudLand 2023: Rock, Paper, Scissors Cloud Competition - Go vs. Java
 
IT-Tage 2021: Java to Go - Google Go für Java-Entwickler
IT-Tage 2021: Java to Go - Google Go für Java-Entwickler IT-Tage 2021: Java to Go - Google Go für Java-Entwickler
IT-Tage 2021: Java to Go - Google Go für Java-Entwickler
 
entwickler.de Go Day: Go Web Development 101
entwickler.de Go Day: Go Web Development 101entwickler.de Go Day: Go Web Development 101
entwickler.de Go Day: Go Web Development 101
 
betterCode() Go: Einstieg in Go, Standard-Library und Ökosystem
betterCode() Go: Einstieg in Go, Standard-Library und ÖkosystembetterCode() Go: Einstieg in Go, Standard-Library und Ökosystem
betterCode() Go: Einstieg in Go, Standard-Library und Ökosystem
 
JCON 2021: Turbo powered Web Apps
JCON 2021: Turbo powered Web AppsJCON 2021: Turbo powered Web Apps
JCON 2021: Turbo powered Web Apps
 
DevOpsCon 2021: Go Web Development 101
DevOpsCon 2021: Go Web Development 101DevOpsCon 2021: Go Web Development 101
DevOpsCon 2021: Go Web Development 101
 
JavaForum Nord 2021: Java to Go - Google Go für Java-Entwickler
JavaForum Nord 2021: Java to Go - Google Go für Java-EntwicklerJavaForum Nord 2021: Java to Go - Google Go für Java-Entwickler
JavaForum Nord 2021: Java to Go - Google Go für Java-Entwickler
 
Karlsruher Entwicklertag 2021: Turbo powered Web Apps
Karlsruher Entwicklertag 2021: Turbo powered Web AppsKarlsruher Entwicklertag 2021: Turbo powered Web Apps
Karlsruher Entwicklertag 2021: Turbo powered Web Apps
 
Jugs HH: Elefant unter Strom - von OldSQL über NoSQL zu NewSQL?
Jugs HH: Elefant unter Strom - von OldSQL über NoSQL zu NewSQL?Jugs HH: Elefant unter Strom - von OldSQL über NoSQL zu NewSQL?
Jugs HH: Elefant unter Strom - von OldSQL über NoSQL zu NewSQL?
 

W-JAX 2023: Go über den Wolken

  • 4. Go App Engine Cold Start Up and running in 500ms! 🚀
  • 5. 5 Fakten zu Go 1. statisches Typsystem 2. Garbage Collection 3. keine Vererbung 4. Concurrency eingebaut 5. native Ausführung Linux, Win, z/OS, 386, amd64, ARM, WebAssembly, ...
  • 7. Cloud Native Technologien befähigen skalierbare Anwendungen zu entwickeln und betreiben, in modernen, dynamischen Umgebungen. Cloud Native Computing Foundation
  • 10. TarifrechnerDogOp v0.1 API die Hunde OPVersicherung berechnet # Bereitstellung als Container # Konfiguration überUmgebungsvariablen #
  • 11. TarifrechnerDogOp v0.2 CRUD Operationen in REST API # Angebote in Postgres speichern # Fallschirm und Rettungsgurt Health Check # Problem Details # Middleware #
  • 13. Projekt DogOPaufsetzen go mod init crossnative/dogop // Go Modul initialisieren go get github.com/go-chi/chi/v5 // Chi Dependency einbinden Projektstruktur go.mod // Modul Deskriptor mit Dependencies go.sum // Checksummen der Dependencies
  • 14. Projekt DogOPaufsetzen package main import ( "net/http" "github.com/go-chi/chi/v5" ) func main() { } 1 2 3 4 5 6 7 8 r := chi.NewRouter() 9 r.Get("/", func(w http.ResponseWriter, req *http.Request 10 w.Write([]byte("Hello DogOp!")) 11 }) 12 http.ListenAndServe(":8080", r) 13 14 r := chi.NewRouter() package main 1 2 import ( 3 "net/http" 4 "github.com/go-chi/chi/v5" 5 ) 6 7 func main() { 8 9 r.Get("/", func(w http.ResponseWriter, req *http.Request 10 w.Write([]byte("Hello DogOp!")) 11 }) 12 http.ListenAndServe(":8080", r) 13 } 14 r.Get("/", func(w http.ResponseWriter, req *http.Request w.Write([]byte("Hello DogOp!")) }) package main 1 2 import ( 3 "net/http" 4 "github.com/go-chi/chi/v5" 5 ) 6 7 func main() { 8 r := chi.NewRouter() 9 10 11 12 http.ListenAndServe(":8080", r) 13 } 14 http.ListenAndServe(":8080", r) package main 1 2 import ( 3 "net/http" 4 "github.com/go-chi/chi/v5" 5 ) 6 7 func main() { 8 r := chi.NewRouter() 9 r.Get("/", func(w http.ResponseWriter, req *http.Request 10 w.Write([]byte("Hello DogOp!")) 11 }) 12 13 } 14 package main import ( "net/http" "github.com/go-chi/chi/v5" ) func main() { r := chi.NewRouter() r.Get("/", func(w http.ResponseWriter, req *http.Request w.Write([]byte("Hello DogOp!")) }) http.ListenAndServe(":8080", r) } 1 2 3 4 5 6 7 8 9 10 11 12 13 14
  • 15. Bauen und Ausführen // 1. Kompilieren in Binary go build -o build/dogop . // 2. Binary ausführen ./build/dogop
  • 17. REST API fürRechner Request POST /api/quote content-type: application/json { "age": 8, "breed": "chow" } Response HTTP/1.1 200 OK Content-Type: application/json { "age": 8, "breed": "chow", "tariffs": [ { "name": "Dog OP _ Basic", "rate": 12.4 } ] }
  • 18. REST API fürRechner func main() { r.Post("/api/quote", HandleQuote) } func HandleQuote(w http.ResponseWriter, r *http.Request) { 1 // 1. JSON Request lesen 2 var q Quote 3 json.NewDecoder(r.Body).Decode(&q) 4 5 // 2. Tarif berechnen 6 tariff := Tariff{Name: "Dog OP _ Basic", Rate: 12.4} 7 quote.Tariffs = []Tariff{tariff} 8 9 // 3. JSON Response schreiben 10 json.NewEncoder(w).Encode(quote) 11 } 12 13 14 r := chi.NewRouter() 15 16 17 18 r.Get("/", func(w http.ResponseWriter, r *http.Request) { 19 w.Write([]byte("Hello DogOp!")) 20 }) 21 22 http.ListenAndServe(":8080", r) 23 24 func HandleQuote(w http.ResponseWriter, r *http.Request) { } 1 // 1. JSON Request lesen 2 var q Quote 3 json.NewDecoder(r.Body).Decode(&q) 4 5 // 2. Tarif berechnen 6 tariff := Tariff{Name: "Dog OP _ Basic", Rate: 12.4} 7 quote.Tariffs = []Tariff{tariff} 8 9 // 3. JSON Response schreiben 10 json.NewEncoder(w).Encode(quote) 11 12 13 func main() { 14 r := chi.NewRouter() 15 16 r.Post("/api/quote", HandleQuote) 17 18 r.Get("/", func(w http.ResponseWriter, r *http.Request) { 19 w.Write([]byte("Hello DogOp!")) 20 }) 21 22 http.ListenAndServe(":8080", r) 23 } 24 // 1. JSON Request lesen var q Quote json.NewDecoder(r.Body).Decode(&q) func HandleQuote(w http.ResponseWriter, r *http.Request) { 1 2 3 4 5 // 2. Tarif berechnen 6 tariff := Tariff{Name: "Dog OP _ Basic", Rate: 12.4} 7 quote.Tariffs = []Tariff{tariff} 8 9 // 3. JSON Response schreiben 10 json.NewEncoder(w).Encode(quote) 11 } 12 13 func main() { 14 r := chi.NewRouter() 15 16 r.Post("/api/quote", HandleQuote) 17 18 r.Get("/", func(w http.ResponseWriter, r *http.Request) { 19 w.Write([]byte("Hello DogOp!")) 20 }) 21 22 http.ListenAndServe(":8080", r) 23 } 24 // 2. Tarif berechnen tariff := Tariff{Name: "Dog OP _ Basic", Rate: 12.4} quote.Tariffs = []Tariff{tariff} func HandleQuote(w http.ResponseWriter, r *http.Request) { 1 // 1. JSON Request lesen 2 var q Quote 3 json.NewDecoder(r.Body).Decode(&q) 4 5 6 7 8 9 // 3. JSON Response schreiben 10 json.NewEncoder(w).Encode(quote) 11 } 12 13 func main() { 14 r := chi.NewRouter() 15 16 r.Post("/api/quote", HandleQuote) 17 18 r.Get("/", func(w http.ResponseWriter, r *http.Request) { 19 w.Write([]byte("Hello DogOp!")) 20 }) 21 22 http.ListenAndServe(":8080", r) 23 } 24 // 3. JSON Response schreiben json.NewEncoder(w).Encode(quote) func HandleQuote(w http.ResponseWriter, r *http.Request) { 1 // 1. JSON Request lesen 2 var q Quote 3 json.NewDecoder(r.Body).Decode(&q) 4 5 // 2. Tarif berechnen 6 tariff := Tariff{Name: "Dog OP _ Basic", Rate: 12.4} 7 quote.Tariffs = []Tariff{tariff} 8 9 10 11 } 12 13 func main() { 14 r := chi.NewRouter() 15 16 r.Post("/api/quote", HandleQuote) 17 18 r.Get("/", func(w http.ResponseWriter, r *http.Request) { 19 w.Write([]byte("Hello DogOp!")) 20 }) 21 22 http.ListenAndServe(":8080", r) 23 } 24 func HandleQuote(w http.ResponseWriter, r *http.Request) { // 1. JSON Request lesen var q Quote json.NewDecoder(r.Body).Decode(&q) // 2. Tarif berechnen tariff := Tariff{Name: "Dog OP _ Basic", Rate: 12.4} quote.Tariffs = []Tariff{tariff} // 3. JSON Response schreiben json.NewEncoder(w).Encode(quote) } func main() { r := chi.NewRouter() r.Post("/api/quote", HandleQuote) r.Get("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello DogOp!")) }) http.ListenAndServe(":8080", r) } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 Achtung, noch ohne Fehlerhandling! 💣
  • 20. Struct statt Klasse type Tariff struct { Name string `json:"name"` Rate float64 `json:"rate"` } 1 2 3 4 5 type Quote struct { 6 Age int `json:"age"` 7 Breed string `json:"breed"` 8 Tariffs []Tariff `json:"tariffs"` 9 } 10 type Quote struct { Age int `json:"age"` Breed string `json:"breed"` Tariffs []Tariff `json:"tariffs"` } type Tariff struct { 1 Name string `json:"name"` 2 Rate float64 `json:"rate"` 3 } 4 5 6 7 8 9 10 type Tariff struct { Name string `json:"name"` Rate float64 `json:"rate"` } type Quote struct { Age int `json:"age"` Breed string `json:"breed"` Tariffs []Tariff `json:"tariffs"` } 1 2 3 4 5 6 7 8 9 10 // Struct erzeugen tariff := Tariff{Name: "Dog OP _ Basic", Rate: 12.4}
  • 21. Fehler💣 func HandleQuote(w http.ResponseWriter, r *http.Request) { } 1 // 1. JSON Request lesen 2 var q Quote 3 json.NewDecoder(r.Body).Decode(&q) // 💣 Fehler möglich! 4 5 // 2. Tarif berechnen 6 tariff := Tariff{Name: "Dog OP _ Basic", Rate: 12.4} 7 quote.Tariffs = []Tariff{tariff} 8 9 // 3. JSON Response schreiben 10 json.NewEncoder(w).Encode(quote) // 💣 Fehler möglich! 11 12 json.NewDecoder(r.Body).Decode(&q) // 💣 Fehler möglich! json.NewEncoder(w).Encode(quote) // 💣 Fehler möglich! func HandleQuote(w http.ResponseWriter, r *http.Request) { 1 // 1. JSON Request lesen 2 var q Quote 3 4 5 // 2. Tarif berechnen 6 tariff := Tariff{Name: "Dog OP _ Basic", Rate: 12.4} 7 quote.Tariffs = []Tariff{tariff} 8 9 // 3. JSON Response schreiben 10 11 } 12
  • 23. Fehler💣 Go kennt kein spezielles Konstrukt zur Fehlerbehandlung # Fehlersind normale Rückgabewerte #
  • 24. Fehler💣 func HandleQuote(w http.ResponseWriter, r *http.Request) { } 1 // 1. JSON Request lesen 2 var q Quote 3 4 // Potentieller Fehler 5 err := json.NewDecoder(r.Body).Decode(&q) 6 7 // Auf Fehler prüfen 8 if err != nil { 9 // Fehler behandeln 10 http.Error(w, "Could not decode quote.😔", http.StatusBadRequest) 11 return 12 } 13 14 // ... 15 16 // Potentieller Fehler err := json.NewDecoder(r.Body).Decode(&q) func HandleQuote(w http.ResponseWriter, r *http.Request) { 1 // 1. JSON Request lesen 2 var q Quote 3 4 5 6 7 // Auf Fehler prüfen 8 if err != nil { 9 // Fehler behandeln 10 http.Error(w, "Could not decode quote.😔", http.StatusBadRequest) 11 return 12 } 13 14 // ... 15 } 16 // Auf Fehler prüfen if err != nil { } func HandleQuote(w http.ResponseWriter, r *http.Request) { 1 // 1. JSON Request lesen 2 var q Quote 3 4 // Potentieller Fehler 5 err := json.NewDecoder(r.Body).Decode(&q) 6 7 8 9 // Fehler behandeln 10 http.Error(w, "Could not decode quote.😔", http.StatusBadRequest) 11 return 12 13 14 // ... 15 } 16 func HandleQuote(w http.ResponseWriter, r *http.Request) { // 1. JSON Request lesen var q Quote // Potentieller Fehler err := json.NewDecoder(r.Body).Decode(&q) // Auf Fehler prüfen if err != nil { // Fehler behandeln http.Error(w, "Could not decode quote.😔", http.StatusBadRequest) return } // ... } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
  • 25. HTTPHandlertesten Unit Tests in Standardlib enthalten # Tests in Datei main_test.go #
  • 26. HTTPHandlertesten func TestHandleQuote(t *testing.T) { } 1 // 1. HTTP Recorder erstellen 2 recorder := httptest.NewRecorder() 3 4 // 2. Request erstellen (mit Body) 5 body := ` 6 { 7 "age": 8, 8 "breed": "chow" 9 } 10 ` 11 req, _ := http.NewRequest("GET", "/api/quote", strings.NewReader(body)) 12 13 // 3. Handler Funktion aufrufen 14 HandleQuote(recorder, req) 15 16 // 4. Return Code prüfen 17 if recorder.Code != http.StatusOK { 18 t.Errorf("Wrong status: got %v expected %v", recorder.Code, http.StatusOK) 19 } 20 21 // 1. HTTP Recorder erstellen recorder := httptest.NewRecorder() func TestHandleQuote(t *testing.T) { 1 2 3 4 // 2. Request erstellen (mit Body) 5 body := ` 6 { 7 "age": 8, 8 "breed": "chow" 9 } 10 ` 11 req, _ := http.NewRequest("GET", "/api/quote", strings.NewReader(body)) 12 13 // 3. Handler Funktion aufrufen 14 HandleQuote(recorder, req) 15 16 // 4. Return Code prüfen 17 if recorder.Code != http.StatusOK { 18 t.Errorf("Wrong status: got %v expected %v", recorder.Code, http.StatusOK) 19 } 20 } 21 // 2. Request erstellen (mit Body) body := ` { "age": 8, "breed": "chow" } ` req, _ := http.NewRequest("GET", "/api/quote", strings.NewReader(body)) func TestHandleQuote(t *testing.T) { 1 // 1. HTTP Recorder erstellen 2 recorder := httptest.NewRecorder() 3 4 5 6 7 8 9 10 11 12 13 // 3. Handler Funktion aufrufen 14 HandleQuote(recorder, req) 15 16 // 4. Return Code prüfen 17 if recorder.Code != http.StatusOK { 18 t.Errorf("Wrong status: got %v expected %v", recorder.Code, http.StatusOK) 19 } 20 } 21 // 3. Handler Funktion aufrufen HandleQuote(recorder, req) func TestHandleQuote(t *testing.T) { 1 // 1. HTTP Recorder erstellen 2 recorder := httptest.NewRecorder() 3 4 // 2. Request erstellen (mit Body) 5 body := ` 6 { 7 "age": 8, 8 "breed": "chow" 9 } 10 ` 11 req, _ := http.NewRequest("GET", "/api/quote", strings.NewReader(body)) 12 13 14 15 16 // 4. Return Code prüfen 17 if recorder.Code != http.StatusOK { 18 t.Errorf("Wrong status: got %v expected %v", recorder.Code, http.StatusOK) 19 } 20 } 21 // 4. Return Code prüfen if recorder.Code != http.StatusOK { t.Errorf("Wrong status: got %v expected %v", recorder.Code, http.StatusOK) } func TestHandleQuote(t *testing.T) { 1 // 1. HTTP Recorder erstellen 2 recorder := httptest.NewRecorder() 3 4 // 2. Request erstellen (mit Body) 5 body := ` 6 { 7 "age": 8, 8 "breed": "chow" 9 } 10 ` 11 req, _ := http.NewRequest("GET", "/api/quote", strings.NewReader(body)) 12 13 // 3. Handler Funktion aufrufen 14 HandleQuote(recorder, req) 15 16 17 18 19 20 } 21 func TestHandleQuote(t *testing.T) { // 1. HTTP Recorder erstellen recorder := httptest.NewRecorder() // 2. Request erstellen (mit Body) body := ` { "age": 8, "breed": "chow" } ` req, _ := http.NewRequest("GET", "/api/quote", strings.NewReader(body)) // 3. Handler Funktion aufrufen HandleQuote(recorder, req) // 4. Return Code prüfen if recorder.Code != http.StatusOK { t.Errorf("Wrong status: got %v expected %v", recorder.Code, http.StatusOK) } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
  • 27. HTTPHandlertesten go test -v ./... === RUN TestHandleQuote --- PASS: TestHandleQuote (0.00s) PASS ok crossnative/dogop 0.228s
  • 29. Port perUmgebungsvariable konfigurieren // Einfach per Standardlib port := os.Getenv("DOGOP_PORT") 1 2 function main() { 3 4 http.ListenAndServe(fmt.Sprintf(":%v", config.Port), r) 5 } 6 // Einfach per Standardlib port := os.Getenv("DOGOP_PORT") http.ListenAndServe(fmt.Sprintf(":%v", config.Port), r) 1 2 function main() { 3 4 5 } 6
  • 30. Port perUmgebungsvariable konfigurieren // Oder mit envconfig von Kelsey Hightower type Config struct { Port string `default:"8080"` } 1 2 3 4 5 6 function main() { 7 var config Config 8 err := envconfig.Process("dogop", &config) 9 if err != nil { 10 log.Fatal(err.Error()) 11 } 12 13 http.ListenAndServe(fmt.Sprintf(":%v", config.Port), r) 14 } 15 // Oder mit envconfig von Kelsey Hightower type Config struct { Port string `default:"8080"` } 1 2 3 4 5 6 function main() { 7 var config Config 8 err := envconfig.Process("dogop", &config) 9 if err != nil { 10 log.Fatal(err.Error()) 11 } 12 13 http.ListenAndServe(fmt.Sprintf(":%v", config.Port), r) 14 } 15 // Oder mit envconfig von Kelsey Hightower type Config struct { Port string `default:"8080"` } var config Config err := envconfig.Process("dogop", &config) 1 2 3 4 5 6 function main() { 7 8 9 if err != nil { 10 log.Fatal(err.Error()) 11 } 12 13 http.ListenAndServe(fmt.Sprintf(":%v", config.Port), r) 14 } 15 // Oder mit envconfig von Kelsey Hightower type Config struct { Port string `default:"8080"` } var config Config err := envconfig.Process("dogop", &config) http.ListenAndServe(fmt.Sprintf(":%v", config.Port), r) 1 2 3 4 5 6 function main() { 7 8 9 if err != nil { 10 log.Fatal(err.Error()) 11 } 12 13 14 } 15
  • 31. Dockerfile fürDogOP # 1. DogOp Builder FROM golang as builder WORKDIR /app ADD . /app RUN CGO_ENABLED=0 go build -ldflags="-w -s" -o build/dogop . 1 2 3 4 5 6 # 2. DogOp Container 7 FROM alpine 8 COPY --from=builder /app/build/dogop /usr/bin/ 9 EXPOSE 8080 10 ENTRYPOINT ["/usr/bin/dogop"] 11 # 2. DogOp Container FROM alpine COPY --from=builder /app/build/dogop /usr/bin/ EXPOSE 8080 ENTRYPOINT ["/usr/bin/dogop"] # 1. DogOp Builder 1 FROM golang as builder 2 WORKDIR /app 3 ADD . /app 4 RUN CGO_ENABLED=0 go build -ldflags="-w -s" -o build/dogop . 5 6 7 8 9 10 11 # 1. DogOp Builder FROM golang as builder WORKDIR /app ADD . /app RUN CGO_ENABLED=0 go build -ldflags="-w -s" -o build/dogop . # 2. DogOp Container FROM alpine COPY --from=builder /app/build/dogop /usr/bin/ EXPOSE 8080 ENTRYPOINT ["/usr/bin/dogop"] 1 2 3 4 5 6 7 8 9 10 11
  • 33. CRUD API fürAngebote Create Request POST /api/offer content-type: application/json { "name": "Rollo", "age": 8, "breed": "chow", "customer": "jan" } Create Response HTTP/1.1 200 OK Content-Type: application/json { "id": "427ed4de", "customer": "jan", "age": 8, "breed": "chow", "name": "Rollo" } Read Request GET /api/offer/427ed4de Read Response HTTP/1.1 200 OK Content-Type: application/json { "id": "427ed4de", "customer": "jan", "age": 8, "breed": "chow", "name": "Rollo" }
  • 34. CRUD API fürAngebote r.Route("/api/offer", func(r chi.Router) { r.Post("/", HandleCreateOffer(offerRepository)) r.Get("/{ID}", HandleReadOffer(offerRepository)) })
  • 35. Create Request mit HandlerFunction func HandleCreateOffer(offerRepository *OfferRepository) http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { } } 1 2 // 1. JSON Request lesen 3 var offer Offer 4 json.NewDecoder(req.Body).Decode(&offer) 5 6 // 2. Offer speichern 7 createdOffer, _ := offerRepository.Insert(req.Context(), &offer) 8 9 // 3. JSON Response schreiben 10 json.NewEncoder(w).Encode(createdOffer) 11 12 13 // 1. JSON Request lesen var offer Offer json.NewDecoder(req.Body).Decode(&offer) // 2. Offer speichern createdOffer, _ := offerRepository.Insert(req.Context(), &offer) // 3. JSON Response schreiben json.NewEncoder(w).Encode(createdOffer) func HandleCreateOffer(offerRepository *OfferRepository) http.HandlerFunc { 1 return func(w http.ResponseWriter, req *http.Request) { 2 3 4 5 6 7 8 9 10 11 } 12 } 13 func HandleCreateOffer(offerRepository *OfferRepository) http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { // 1. JSON Request lesen var offer Offer json.NewDecoder(req.Body).Decode(&offer) // 2. Offer speichern createdOffer, _ := offerRepository.Insert(req.Context(), &offer) // 3. JSON Response schreiben json.NewEncoder(w).Encode(createdOffer) } } 1 2 3 4 5 6 7 8 9 10 11 12 13
  • 36. Angebote speichern in Postgres pgx als Postgres Driverund Toolkit # Interface database/sql in Go Standardlib #
  • 37. Angebote speichern in Postgres type OfferRepository struct { connPool *pgxpool.Pool 1 2 } 3 4 func (r *OfferRepository) Insert(ctx context.Context, offer *Offer) 5 // ... 6 } 7 } func (r *OfferRepository) Insert(ctx context.Context, offer *Offer) // ... } type OfferRepository struct { 1 connPool *pgxpool.Pool 2 3 4 5 6 7 type OfferRepository struct { connPool *pgxpool.Pool } func (r *OfferRepository) Insert(ctx context.Context, offer *Offer) // ... } 1 2 3 4 5 6 7
  • 38. Angebote speichern in Postgres func (r *OfferRepository) Insert(ctx context.Context, offer *Offer) (*Offer, error) { } 1 // 1. ID generieren 2 offer.ID = uuid.New().String() 3 4 // 2. Transaktion beginnen 5 tx, _ := r.connPool.Begin(ctx) 6 defer tx.Rollback(ctx) 7 8 // 3. Offer per Insert speichern 9 _, err := tx.Exec( 10 ctx, 11 `INSERT INTO offers 12 (id, customer, age, breed, name) 13 VALUES 14 ($1, $2, $3, $4, $5)`, 15 offer.ID, offer.Customer, offer.Age, offer.Breed, offer.Name, 16 ) 17 if err != nil { 18 return nil, err 19 } 20 21 // 4. Transaktion commiten 22 tx.Commit(ctx) 23 24 // 5. Gespeicherte Offer zurückgeben 25 return offer, nil 26 27 // 1. ID generieren offer.ID = uuid.New().String() func (r *OfferRepository) Insert(ctx context.Context, offer *Offer) (*Offer, error) { 1 2 3 4 // 2. Transaktion beginnen 5 tx, _ := r.connPool.Begin(ctx) 6 defer tx.Rollback(ctx) 7 8 // 3. Offer per Insert speichern 9 _, err := tx.Exec( 10 ctx, 11 `INSERT INTO offers 12 (id, customer, age, breed, name) 13 VALUES 14 ($1, $2, $3, $4, $5)`, 15 offer.ID, offer.Customer, offer.Age, offer.Breed, offer.Name, 16 ) 17 if err != nil { 18 return nil, err 19 } 20 21 // 4. Transaktion commiten 22 tx.Commit(ctx) 23 24 // 5. Gespeicherte Offer zurückgeben 25 return offer, nil 26 } 27 // 2. Transaktion beginnen tx, _ := r.connPool.Begin(ctx) defer tx.Rollback(ctx) func (r *OfferRepository) Insert(ctx context.Context, offer *Offer) (*Offer, error) { 1 // 1. ID generieren 2 offer.ID = uuid.New().String() 3 4 5 6 7 8 // 3. Offer per Insert speichern 9 _, err := tx.Exec( 10 ctx, 11 `INSERT INTO offers 12 (id, customer, age, breed, name) 13 VALUES 14 ($1, $2, $3, $4, $5)`, 15 offer.ID, offer.Customer, offer.Age, offer.Breed, offer.Name, 16 ) 17 if err != nil { 18 return nil, err 19 } 20 21 // 4. Transaktion commiten 22 tx.Commit(ctx) 23 24 // 5. Gespeicherte Offer zurückgeben 25 return offer, nil 26 } 27 // 3. Offer per Insert speichern _, err := tx.Exec( ctx, `INSERT INTO offers (id, customer, age, breed, name) VALUES ($1, $2, $3, $4, $5)`, offer.ID, offer.Customer, offer.Age, offer.Breed, offer.Name, ) if err != nil { return nil, err } func (r *OfferRepository) Insert(ctx context.Context, offer *Offer) (*Offer, error) { 1 // 1. ID generieren 2 offer.ID = uuid.New().String() 3 4 // 2. Transaktion beginnen 5 tx, _ := r.connPool.Begin(ctx) 6 defer tx.Rollback(ctx) 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // 4. Transaktion commiten 22 tx.Commit(ctx) 23 24 // 5. Gespeicherte Offer zurückgeben 25 return offer, nil 26 } 27 // 4. Transaktion commiten tx.Commit(ctx) func (r *OfferRepository) Insert(ctx context.Context, offer *Offer) (*Offer, error) { 1 // 1. ID generieren 2 offer.ID = uuid.New().String() 3 4 // 2. Transaktion beginnen 5 tx, _ := r.connPool.Begin(ctx) 6 defer tx.Rollback(ctx) 7 8 // 3. Offer per Insert speichern 9 _, err := tx.Exec( 10 ctx, 11 `INSERT INTO offers 12 (id, customer, age, breed, name) 13 VALUES 14 ($1, $2, $3, $4, $5)`, 15 offer.ID, offer.Customer, offer.Age, offer.Breed, offer.Name, 16 ) 17 if err != nil { 18 return nil, err 19 } 20 21 22 23 24 // 5. Gespeicherte Offer zurückgeben 25 return offer, nil 26 } 27 // 5. Gespeicherte Offer zurückgeben return offer, nil func (r *OfferRepository) Insert(ctx context.Context, offer *Offer) (*Offer, error) { 1 // 1. ID generieren 2 offer.ID = uuid.New().String() 3 4 // 2. Transaktion beginnen 5 tx, _ := r.connPool.Begin(ctx) 6 defer tx.Rollback(ctx) 7 8 // 3. Offer per Insert speichern 9 _, err := tx.Exec( 10 ctx, 11 `INSERT INTO offers 12 (id, customer, age, breed, name) 13 VALUES 14 ($1, $2, $3, $4, $5)`, 15 offer.ID, offer.Customer, offer.Age, offer.Breed, offer.Name, 16 ) 17 if err != nil { 18 return nil, err 19 } 20 21 // 4. Transaktion commiten 22 tx.Commit(ctx) 23 24 25 26 } 27 func (r *OfferRepository) Insert(ctx context.Context, offer *Offer) (*Offer, error) { // 1. ID generieren offer.ID = uuid.New().String() // 2. Transaktion beginnen tx, _ := r.connPool.Begin(ctx) defer tx.Rollback(ctx) // 3. Offer per Insert speichern _, err := tx.Exec( ctx, `INSERT INTO offers (id, customer, age, breed, name) VALUES ($1, $2, $3, $4, $5)`, offer.ID, offer.Customer, offer.Age, offer.Breed, offer.Name, ) if err != nil { return nil, err } // 4. Transaktion commiten tx.Commit(ctx) // 5. Gespeicherte Offer zurückgeben return offer, nil } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
  • 39. Aus dem Context gerissen createdOffer, _ := offerRepository.Insert(req.Context(), &offer) tx, _ := r.connPool.Begin(ctx) defer tx.Rollback(ctx) _, err := tx.Exec( ctx, ) tx.Commit(ctx) return func(w http.ResponseWriter, req *http.Request) { 1 // ... 2 3 // 2. Offer speichern 4 5 6 // ... 7 } 8 9 func (r *OfferRepository) Insert(ctx context.Context, offer *Offer) (*Offer, error) { 10 // .. 11 12 // 2. Transaktion beginnen 13 14 15 16 // 3. Offer per Insert speichern 17 18 19 `INSERT INTO offers 20 (id, customer, age, breed, name) 21 VALUES 22 ($1, $2, $3, $4, $5)`, 23 offer.ID, offer.Customer, offer.Age, offer.Breed, offer.Name, 24 25 // ... 26 27 // 4. Transaktion commiten 28 29 30 // ... 31 } 32
  • 41. Health Check Nutzung von health-go (hellofresh) Integration des Checks fürpgx # #
  • 42. Health Check // Register Health Check h, _ := health.New(health.WithChecks( health.Config{ Name: "db", Timeout: time.Second * 2, SkipOnErr: false, Check: healthPgx.New(healthPgx.Config{ DSN: config.Db, }), }, )) 1 2 3 4 5 6 7 8 9 10 11 12 // Register Handler Function 13 r.Get("/health", h.HandlerFunc) 14 // Register Handler Function r.Get("/health", h.HandlerFunc) // Register Health Check 1 h, _ := health.New(health.WithChecks( 2 health.Config{ 3 Name: "db", 4 Timeout: time.Second * 2, 5 SkipOnErr: false, 6 Check: healthPgx.New(healthPgx.Config{ 7 DSN: config.Db, 8 }), 9 }, 10 )) 11 12 13 14 // Register Health Check h, _ := health.New(health.WithChecks( health.Config{ Name: "db", Timeout: time.Second * 2, SkipOnErr: false, Check: healthPgx.New(healthPgx.Config{ DSN: config.Db, }), }, )) // Register Handler Function r.Get("/health", h.HandlerFunc) 1 2 3 4 5 6 7 8 9 10 11 12 13 14
  • 43. Problem Details UngültigerRequest GET /api/offer/-invalid- Response mit Problem Details HTTP/1.1 400 Bad Request Content-Type: application/problem+json { "reason": "invalid UUID format", "status": 400, "title": "invalid request" }
  • 46. Fehlerbehandeln Mit Problem Details if err != nil { problem.New( problem.Title("invalid request"), problem.Wrap(err), problem.Status(http.StatusBadRequest), ).WriteTo(w) return } Mit Standardbibliothek if err != nil { http.Error(w, "invalid request", http.StatusBadRequest) return }
  • 47. Middleware cross-cutting Features füralle Requests (z.B. Logging, Authentifizierung) # erzeugen eine Kette von Handlern router=> middleware handler=> application handler # implementieren das Interface http.Handler #
  • 48. Middleware loggt Requests func loggingMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Printf("%v requested URL %v", r.Host, r.URL) next.ServeHTTP(w, r) }) } 1 2 3 4 5 6 7 func main() { 8 r := chi.NewRouter() 9 10 // Nutze Logging Middleware 11 r.Use(loggingMiddleware) 12 13 http.ListenAndServe(":8080", r) 14 } 15 // Nutze Logging Middleware r.Use(loggingMiddleware) func loggingMiddleware(next http.Handler) http.Handler { 1 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 2 log.Printf("%v requested URL %v", r.Host, r.URL) 3 next.ServeHTTP(w, r) 4 }) 5 } 6 7 func main() { 8 r := chi.NewRouter() 9 10 11 12 13 http.ListenAndServe(":8080", r) 14 } 15 func loggingMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Printf("%v requested URL %v", r.Host, r.URL) next.ServeHTTP(w, r) }) } func main() { r := chi.NewRouter() // Nutze Logging Middleware r.Use(loggingMiddleware) http.ListenAndServe(":8080", r) } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
  • 49. Middlewares nutzen // Basis Middleware r.Use(middleware.RequestID) r.Use(middleware.RealIP) r.Use(middleware.Logger) r.Use(middleware.Recoverer) func main() { 1 r := chi.NewRouter() 2 3 4 5 6 7 8 9 // Timeout über Request Context setzen 10 r.Use(middleware.Timeout(60 * time.Second)) 11 12 http.ListenAndServe(":8080", r) 13 } 14 // Timeout über Request Context setzen r.Use(middleware.Timeout(60 * time.Second)) func main() { 1 r := chi.NewRouter() 2 3 // Basis Middleware 4 r.Use(middleware.RequestID) 5 r.Use(middleware.RealIP) 6 r.Use(middleware.Logger) 7 r.Use(middleware.Recoverer) 8 9 10 11 12 http.ListenAndServe(":8080", r) 13 } 14 // Basis Middleware r.Use(middleware.RequestID) r.Use(middleware.RealIP) r.Use(middleware.Logger) r.Use(middleware.Recoverer) // Timeout über Request Context setzen r.Use(middleware.Timeout(60 * time.Second)) func main() { 1 r := chi.NewRouter() 2 3 4 5 6 7 8 9 10 11 12 http.ListenAndServe(":8080", r) 13 } 14
  • 50. Zusammenfassung DogOP v0.1 1. Go Projekt aufsetzen 2. einfache Rest API Handler 3. Konfiguration per Umgebungsvariable 4. Structs 5. Fehlerbehandeln 6. Unit Tests 7. DockerContainerbauen v0.2 1. CRUD API fürAngebote 2. Angebote in Postgres speichern 3. Context 4. Health Check 5. Problem Details 6. Middleware
  • 52. 3 Gründe fürGo 1. Einfach 2. Mächtig 3. Langweilig