SlideShare ist ein Scribd-Unternehmen logo
Go überden Wolken
entwickler.de 05/2023: Go über den Wolken
entwickler.de 05/2023: Go über den Wolken
Go App Engine Cold Start
Up and running in 500ms! 🚀
entwickler.de 05/2023: Go über den Wolken
Hello Gopher
package main
import "fmt"
func main() {
fmt.Println("Hello Gopher!")
}
Ausführen
go build hellogopher.go // 1. Code kompilieren
./hellogopher // 2. Binary ausführen
go run hellogopher.go // Code kompilieren und ausführen
Entwicklung
JetBrains
GoLand
Visual
Studio Code Vim Go
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, wasm, ...
Was ist
cloud-native?
Cloud Native Technologien befähigen
skalierbare Anwendungen
zu entwickeln und betreiben,
in modernen, dynamischen Umgebungen.
Cloud Native Computing Foundation
Skalierbarheisst ...
Last wird auf 2, 3, ... Instanzen verteilt
eine Instanz kann mehrCPU und Speicher
bekommen
ist nichts los, läuft auch nichts
#
#
#
Moderne, dynamische Umgebung
heißt ...
Instanzen sind flüchtig
Adressen sind flüchtig
Hostnamen sind flüchtig
kein Dateisystem
#
#
#
#
12 FactorApp
1. Codebasis
2. Abhängigkeiten
3. Konfiguration
4. Unterstützende Dienste
5. Build, Release, Run
6. Prozesse
7. Bindung an Ports
8. Nebenläufigkeit und Skalierung
9. Einweggebrauch
10. Dev-Prod-Vergleichbarkeit
11. Logs
12. Admin-Prozesse
✅
✅
✅
TarifrechnerDogOP
TarifrechnerDogOp v0.1
TarifrechnerfürHunde OPVersicherung
#
REST API die einen Tarif berechnet
#
Bereitstellung als Container
#
Konfiguration überUmgebungsvariablen
#
TarifrechnerDogOp v0.2
Angebote in PostgreSQL DB speichern
#
Migration DatenbankSchema
#
Bereitstellung als Container
#
CRUD Operationen in REST API
#
Fallschirm und Rettungsgurt
Problem Details
#
Health Check
#
Logging und Tracing
#
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. Compilieren 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! 💣
entwickler.de 05/2023: Go über den Wolken
Struct statt Klasse
// Structs definieren
type Tariff struct {
Name string `json:"name"`
Rate float64 `json:"rate"`
}
1
2
3
4
5
6
7
type Quote struct {
8
Age int `json:"age"`
9
Breed string `json:"breed"`
10
Tariffs []Tariff `json:"tariffs"`
11
}
12
type Quote struct {
Age int `json:"age"`
Breed string `json:"breed"`
Tariffs []Tariff `json:"tariffs"`
}
// Structs definieren
1
2
type Tariff struct {
3
Name string `json:"name"`
4
Rate float64 `json:"rate"`
5
}
6
7
8
9
10
11
12
// Structs definieren
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
11
12
// 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
entwickler.de 05/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
function main() {
port := os.Getenv("DOGOP_PORT")
http.ListenAndServe(fmt.Sprintf(":%v", config.Port), r)
}
Port perUmgebungsvariable
konfigurieren
// Oder mit envconfig von Kelsey Hightower
type Config struct {
Port string `default:"8080"`
}
function main() {
var config Config
err := envconfig.Process("dogop", &config)
if err != nil {
log.Fatal(err.Error())
}
http.ListenAndServe(fmt.Sprintf(":%v", config.Port), r)
}
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
>_ Demo
Zusammenfassung DogOPv0.1
1. Go Projekt aufsetzen
2. einfache Rest API Handler
3. Konfiguration perUmgebungsvariable
4. Structs
5. Fehlerbehandeln
6. Unit Tests
7. DockerContainerbauen
12 FactorApp DogOPv0.1
1. Codebasis
2. Abhängigkeiten
3. Konfiguration
4. Unterstützende Dienste
5. Build, Release, Run
6. Prozesse
7. Bindung an Ports
8. Nebenläufigkeit und Skalierung
9. Einweggebrauch
10. Dev-Prod-Vergleichbarkeit
11. Logs
12. Admin-Prozesse
✅
✅
✅
✅
✅
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": "e8b948c6-28f6-40b5-9d51-dc14a6c1
"customer": "jan",
"age": 8,
"breed": "chow",
"name": "Rollo"
}
CRUD API fürAngebote
r.Route("/api/offer", func(sr chi.Router) {
sr.Post("/", HandleCreateOffer(offerRepository))
sr.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 PostgreSQL
pgx als PostgreSQL Driverund Toolkit
#
Interface database/sql in Go Standardlib
#
Angebote speichern in PostgreSQL
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 PostgreSQL
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
Connection Pool nutzen
connPool, err := pgxpool.New(context.Background(), config.Db)
offerRepository := &OfferRepository{connPool: connPool}
type Config struct {
1
Port string `default:"8080"`
2
Db string `default:"postgres://postgres:postgres@localhost:5432/dogop"`
3
}
4
5
func main() {
6
7
8
9
10
// ...
11
}
12
type Config struct {
Port string `default:"8080"`
Db string `default:"postgres://postgres:postgres@localhost:5432/dogop"`
}
1
2
3
4
5
func main() {
6
connPool, err := pgxpool.New(context.Background(), config.Db)
7
8
offerRepository := &OfferRepository{connPool: connPool}
9
10
// ...
11
}
12
type Config struct {
Port string `default:"8080"`
Db string `default:"postgres://postgres:postgres@localhost:5432/dogop"`
}
func main() {
connPool, err := pgxpool.New(context.Background(), config.Db)
offerRepository := &OfferRepository{connPool: connPool}
// ...
}
1
2
3
4
5
6
7
8
9
10
11
12
DatenbankSchema migrieren
DB Schema Update beim Start derAnwendung
#
DB Schema migrieren mit golang-migrate
#
SQL Migrations Skripte Teil des Binary
#
DatenbankSchema migrieren
//go:embed migrations
var migrations embed.FS
1
2
3
func main() {
4
// IO Filesystem erstellen
5
source, err := iofs.New(migrations, "migrations")
6
7
// Connection String für Migration anpassen
8
dbMigrateURL := strings.Replace(config.Db, "postgres://", "pgx://", 1)
9
10
// Golang Migrate intialisieren
11
m, _ := migrate.NewWithSourceInstance("iofs", source, dbMigrateURL)
12
13
// Datenbank Schema aktualisieren
14
m.Up()
15
16
// ...
17
}
18
// IO Filesystem erstellen
source, err := iofs.New(migrations, "migrations")
//go:embed migrations
1
var migrations embed.FS
2
3
func main() {
4
5
6
7
// Connection String für Migration anpassen
8
dbMigrateURL := strings.Replace(config.Db, "postgres://", "pgx://", 1)
9
10
// Golang Migrate intialisieren
11
m, _ := migrate.NewWithSourceInstance("iofs", source, dbMigrateURL)
12
13
// Datenbank Schema aktualisieren
14
m.Up()
15
16
// ...
17
}
18
// Connection String für Migration anpassen
dbMigrateURL := strings.Replace(config.Db, "postgres://", "pgx://", 1)
//go:embed migrations
1
var migrations embed.FS
2
3
func main() {
4
// IO Filesystem erstellen
5
source, err := iofs.New(migrations, "migrations")
6
7
8
9
10
// Golang Migrate intialisieren
11
m, _ := migrate.NewWithSourceInstance("iofs", source, dbMigrateURL)
12
13
// Datenbank Schema aktualisieren
14
m.Up()
15
16
// ...
17
}
18
// Golang Migrate intialisieren
m, _ := migrate.NewWithSourceInstance("iofs", source, dbMigrateURL)
//go:embed migrations
1
var migrations embed.FS
2
3
func main() {
4
// IO Filesystem erstellen
5
source, err := iofs.New(migrations, "migrations")
6
7
// Connection String für Migration anpassen
8
dbMigrateURL := strings.Replace(config.Db, "postgres://", "pgx://", 1)
9
10
11
12
13
// Datenbank Schema aktualisieren
14
m.Up()
15
16
// ...
17
}
18
// Datenbank Schema aktualisieren
m.Up()
//go:embed migrations
1
var migrations embed.FS
2
3
func main() {
4
// IO Filesystem erstellen
5
source, err := iofs.New(migrations, "migrations")
6
7
// Connection String für Migration anpassen
8
dbMigrateURL := strings.Replace(config.Db, "postgres://", "pgx://", 1)
9
10
// Golang Migrate intialisieren
11
m, _ := migrate.NewWithSourceInstance("iofs", source, dbMigrateURL)
12
13
14
15
16
// ...
17
}
18
//go:embed migrations
var migrations embed.FS
func main() {
// IO Filesystem erstellen
source, err := iofs.New(migrations, "migrations")
// Connection String für Migration anpassen
dbMigrateURL := strings.Replace(config.Db, "postgres://", "pgx://", 1)
// Golang Migrate intialisieren
m, _ := migrate.NewWithSourceInstance("iofs", source, dbMigrateURL)
// Datenbank Schema aktualisieren
m.Up()
// ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>_ Demo
Zusammenfassung DogOPv0.2
1. CRUD API fürAngebote
2. Angebote in PostgreSQL speichern
3. Context
4. Connection Pool nutzen
5. DatenbankSchema migrieren
12 FactorApp DogOPv0.2
1. Codebasis
2. Abhängigkeiten
3. Konfiguration
4. Unterstützende Dienste
5. Build, Release, Run
6. Prozesse
7. Bindung an Ports
8. Nebenläufigkeit und Skalierung
9. Einweggebrauch
10. Dev-Prod-Vergleichbarkeit
11. Logs
12. Admin-Prozesse
✅
✅
✅
✅
✅
✅
Fallschirm und
Rettungsgurt
Fallschirm und Rettungsgurt
Problem Details
Health Check
Logging und Tracing
#
#
#
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
entwickler.de 05/2023: Go über den Wolken
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
id, err := uuid.Parse(chi.URLParam(r, "ID"))
return func(w http.ResponseWriter, r *http.Request) {
1
// 1. Offer lesen
2
3
if err != nil {
4
problem.New(
5
problem.Title("invalid request"),
6
problem.Wrap(err),
7
problem.Status(http.StatusBadRequest),
8
).WriteTo(w)
9
return
10
}
11
}
12
id, err := uuid.Parse(chi.URLParam(r, "ID"))
if err != nil {
problem.New(
problem.Title("invalid request"),
problem.Wrap(err),
problem.Status(http.StatusBadRequest),
).WriteTo(w)
return
return func(w http.ResponseWriter, r *http.Request) {
1
// 1. Offer lesen
2
3
4
5
6
7
8
9
10
}
11
}
12
id, err := uuid.Parse(chi.URLParam(r, "ID"))
if err != nil {
problem.New(
problem.Title("invalid request"),
problem.Wrap(err),
problem.Status(http.StatusBadRequest),
).WriteTo(w)
return
return func(w http.ResponseWriter, r *http.Request) {
1
// 1. Offer lesen
2
3
4
5
6
7
8
9
10
}
11
}
12
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 Middlewares
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 Middlewares
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 Middlewares
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 PostgreSQL
speichern
3. Context
4. Connection Pool nutzen
5. DatenbankSchema migrieren
3 Gründe fürGo
1. Einfach
2. Mächtig
3. Langweilig
Jan Stamer
jan.stamer@crossnative.com
Backup
entwickler.de 05/2023: Go über den Wolken

Weitere ähnliche Inhalte

Ähnlich wie entwickler.de 05/2023: Go über den Wolken

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
 
Backend-Services mit Rust
Backend-Services mit RustBackend-Services mit Rust
Backend-Services mit Rust
Jens Siebert
 
Check cisco voice
Check cisco voiceCheck cisco voice
Check cisco voice
bboguhn
 
Tech Talk: Groovy
Tech Talk: GroovyTech Talk: Groovy
Tech Talk: Groovy
mwie
 
Rack-Middleware
Rack-MiddlewareRack-Middleware
Rack-Middleware
Hussein Morsy
 
Einsteiger Workshop
Einsteiger WorkshopEinsteiger Workshop
Einsteiger Workshop
FunThomas424242
 
Skalierbare Anwendungen mit Google Go
Skalierbare Anwendungen mit Google GoSkalierbare Anwendungen mit Google Go
Skalierbare Anwendungen mit Google Go
Frank Müller
 
Prototype 1.7
Prototype 1.7Prototype 1.7
Prototype 1.7
msebel
 
Einführung React Native
Einführung React NativeEinführung React Native
Einführung React Native
Markus Günther
 
.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
 
XML-Socket-Server zur Kommunikation mit Flash
XML-Socket-Server zur Kommunikation mit FlashXML-Socket-Server zur Kommunikation mit Flash
XML-Socket-Server zur Kommunikation mit Flash
Stephan Schmidt
 
HTML5 und node.js Grundlagen
HTML5 und node.js GrundlagenHTML5 und node.js Grundlagen
HTML5 und node.js Grundlagen
Mayflower GmbH
 
Bottle - Python Web Microframework
Bottle - Python Web MicroframeworkBottle - Python Web Microframework
Bottle - Python Web Microframework
Markus Zapke-Gründemann
 
Socket Programmierung mit IPv6
Socket Programmierung mit IPv6Socket Programmierung mit IPv6
Socket Programmierung mit IPv6
Christian Kauhaus
 
APIs mit Zend\Expressive erstellen
APIs mit Zend\Expressive erstellenAPIs mit Zend\Expressive erstellen
APIs mit Zend\Expressive erstellen
Ralf Eggert
 
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
 
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
 
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
 
Ein Gopher im Netz
Ein Gopher im NetzEin Gopher im Netz
Ein Gopher im Netz
Frank Müller
 

Ähnlich wie entwickler.de 05/2023: Go über den Wolken (20)

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
 
Backend-Services mit Rust
Backend-Services mit RustBackend-Services mit Rust
Backend-Services mit Rust
 
Check cisco voice
Check cisco voiceCheck cisco voice
Check cisco voice
 
Tech Talk: Groovy
Tech Talk: GroovyTech Talk: Groovy
Tech Talk: Groovy
 
Rack-Middleware
Rack-MiddlewareRack-Middleware
Rack-Middleware
 
Einsteiger Workshop
Einsteiger WorkshopEinsteiger Workshop
Einsteiger Workshop
 
Skalierbare Anwendungen mit Google Go
Skalierbare Anwendungen mit Google GoSkalierbare Anwendungen mit Google Go
Skalierbare Anwendungen mit Google Go
 
Prototype 1.7
Prototype 1.7Prototype 1.7
Prototype 1.7
 
Einführung React Native
Einführung React NativeEinführung React Native
Einführung React Native
 
.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
 
XML-Socket-Server zur Kommunikation mit Flash
XML-Socket-Server zur Kommunikation mit FlashXML-Socket-Server zur Kommunikation mit Flash
XML-Socket-Server zur Kommunikation mit Flash
 
HTML5 und node.js Grundlagen
HTML5 und node.js GrundlagenHTML5 und node.js Grundlagen
HTML5 und node.js Grundlagen
 
Bottle - Python Web Microframework
Bottle - Python Web MicroframeworkBottle - Python Web Microframework
Bottle - Python Web Microframework
 
Socket Programmierung mit IPv6
Socket Programmierung mit IPv6Socket Programmierung mit IPv6
Socket Programmierung mit IPv6
 
APIs mit Zend\Expressive erstellen
APIs mit Zend\Expressive erstellenAPIs mit Zend\Expressive erstellen
APIs mit Zend\Expressive erstellen
 
Ruby, Ruby, Ruby!
Ruby, Ruby, Ruby!Ruby, Ruby, Ruby!
Ruby, Ruby, Ruby!
 
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
 
Android Development ...and the daily challenges
Android Development ...and the daily challengesAndroid Development ...and the daily challenges
Android Development ...and the daily challenges
 
Devs@Home - Einführung in Go
Devs@Home - Einführung in GoDevs@Home - Einführung in Go
Devs@Home - Einführung in Go
 
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
 
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 (8)

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
 
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?
 

entwickler.de 05/2023: Go über den Wolken

  • 4. Go App Engine Cold Start Up and running in 500ms! 🚀
  • 6. Hello Gopher package main import "fmt" func main() { fmt.Println("Hello Gopher!") } Ausführen go build hellogopher.go // 1. Code kompilieren ./hellogopher // 2. Binary ausführen go run hellogopher.go // Code kompilieren und ausführen
  • 8. 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, wasm, ...
  • 10. Cloud Native Technologien befähigen skalierbare Anwendungen zu entwickeln und betreiben, in modernen, dynamischen Umgebungen. Cloud Native Computing Foundation
  • 11. Skalierbarheisst ... Last wird auf 2, 3, ... Instanzen verteilt eine Instanz kann mehrCPU und Speicher bekommen ist nichts los, läuft auch nichts # # #
  • 12. Moderne, dynamische Umgebung heißt ... Instanzen sind flüchtig Adressen sind flüchtig Hostnamen sind flüchtig kein Dateisystem # # # #
  • 13. 12 FactorApp 1. Codebasis 2. Abhängigkeiten 3. Konfiguration 4. Unterstützende Dienste 5. Build, Release, Run 6. Prozesse 7. Bindung an Ports 8. Nebenläufigkeit und Skalierung 9. Einweggebrauch 10. Dev-Prod-Vergleichbarkeit 11. Logs 12. Admin-Prozesse ✅ ✅ ✅
  • 15. TarifrechnerDogOp v0.1 TarifrechnerfürHunde OPVersicherung # REST API die einen Tarif berechnet # Bereitstellung als Container # Konfiguration überUmgebungsvariablen #
  • 16. TarifrechnerDogOp v0.2 Angebote in PostgreSQL DB speichern # Migration DatenbankSchema # Bereitstellung als Container # CRUD Operationen in REST API #
  • 17. Fallschirm und Rettungsgurt Problem Details # Health Check # Logging und Tracing #
  • 19. 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
  • 20. 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
  • 21. Bauen und Ausführen // 1. Compilieren in Binary go build -o build/dogop . // 2. Binary ausführen ./build/dogop
  • 23. 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 } ] }
  • 24. 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! 💣
  • 26. Struct statt Klasse // Structs definieren type Tariff struct { Name string `json:"name"` Rate float64 `json:"rate"` } 1 2 3 4 5 6 7 type Quote struct { 8 Age int `json:"age"` 9 Breed string `json:"breed"` 10 Tariffs []Tariff `json:"tariffs"` 11 } 12 type Quote struct { Age int `json:"age"` Breed string `json:"breed"` Tariffs []Tariff `json:"tariffs"` } // Structs definieren 1 2 type Tariff struct { 3 Name string `json:"name"` 4 Rate float64 `json:"rate"` 5 } 6 7 8 9 10 11 12 // Structs definieren 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 11 12 // Struct erzeugen tariff := Tariff{Name: "Dog OP _ Basic", Rate: 12.4}
  • 27. 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
  • 29. Fehler💣 Go kennt kein spezielles Konstrukt zur Fehlerbehandlung # Fehlersind normale Rückgabewerte #
  • 30. 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
  • 31. HTTPHandlertesten Unit Tests in Standardlib enthalten # Tests in Datei main_test.go #
  • 32. 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
  • 33. HTTPHandlertesten go test -v ./... === RUN TestHandleQuote --- PASS: TestHandleQuote (0.00s) PASS ok crossnative/dogop 0.228s
  • 35. Port perUmgebungsvariable konfigurieren // Einfach per Standardlib function main() { port := os.Getenv("DOGOP_PORT") http.ListenAndServe(fmt.Sprintf(":%v", config.Port), r) }
  • 36. Port perUmgebungsvariable konfigurieren // Oder mit envconfig von Kelsey Hightower type Config struct { Port string `default:"8080"` } function main() { var config Config err := envconfig.Process("dogop", &config) if err != nil { log.Fatal(err.Error()) } http.ListenAndServe(fmt.Sprintf(":%v", config.Port), r) }
  • 37. 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
  • 39. Zusammenfassung DogOPv0.1 1. Go Projekt aufsetzen 2. einfache Rest API Handler 3. Konfiguration perUmgebungsvariable 4. Structs 5. Fehlerbehandeln 6. Unit Tests 7. DockerContainerbauen
  • 40. 12 FactorApp DogOPv0.1 1. Codebasis 2. Abhängigkeiten 3. Konfiguration 4. Unterstützende Dienste 5. Build, Release, Run 6. Prozesse 7. Bindung an Ports 8. Nebenläufigkeit und Skalierung 9. Einweggebrauch 10. Dev-Prod-Vergleichbarkeit 11. Logs 12. Admin-Prozesse ✅ ✅ ✅ ✅ ✅
  • 42. 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": "e8b948c6-28f6-40b5-9d51-dc14a6c1 "customer": "jan", "age": 8, "breed": "chow", "name": "Rollo" }
  • 43. CRUD API fürAngebote r.Route("/api/offer", func(sr chi.Router) { sr.Post("/", HandleCreateOffer(offerRepository)) sr.Get("/{ID}", HandleReadOffer(offerRepository)) })
  • 44. 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
  • 45. Angebote speichern in PostgreSQL pgx als PostgreSQL Driverund Toolkit # Interface database/sql in Go Standardlib #
  • 46. Angebote speichern in PostgreSQL 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
  • 47. Angebote speichern in PostgreSQL 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
  • 48. 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
  • 49. Connection Pool nutzen connPool, err := pgxpool.New(context.Background(), config.Db) offerRepository := &OfferRepository{connPool: connPool} type Config struct { 1 Port string `default:"8080"` 2 Db string `default:"postgres://postgres:postgres@localhost:5432/dogop"` 3 } 4 5 func main() { 6 7 8 9 10 // ... 11 } 12 type Config struct { Port string `default:"8080"` Db string `default:"postgres://postgres:postgres@localhost:5432/dogop"` } 1 2 3 4 5 func main() { 6 connPool, err := pgxpool.New(context.Background(), config.Db) 7 8 offerRepository := &OfferRepository{connPool: connPool} 9 10 // ... 11 } 12 type Config struct { Port string `default:"8080"` Db string `default:"postgres://postgres:postgres@localhost:5432/dogop"` } func main() { connPool, err := pgxpool.New(context.Background(), config.Db) offerRepository := &OfferRepository{connPool: connPool} // ... } 1 2 3 4 5 6 7 8 9 10 11 12
  • 50. DatenbankSchema migrieren DB Schema Update beim Start derAnwendung # DB Schema migrieren mit golang-migrate # SQL Migrations Skripte Teil des Binary #
  • 51. DatenbankSchema migrieren //go:embed migrations var migrations embed.FS 1 2 3 func main() { 4 // IO Filesystem erstellen 5 source, err := iofs.New(migrations, "migrations") 6 7 // Connection String für Migration anpassen 8 dbMigrateURL := strings.Replace(config.Db, "postgres://", "pgx://", 1) 9 10 // Golang Migrate intialisieren 11 m, _ := migrate.NewWithSourceInstance("iofs", source, dbMigrateURL) 12 13 // Datenbank Schema aktualisieren 14 m.Up() 15 16 // ... 17 } 18 // IO Filesystem erstellen source, err := iofs.New(migrations, "migrations") //go:embed migrations 1 var migrations embed.FS 2 3 func main() { 4 5 6 7 // Connection String für Migration anpassen 8 dbMigrateURL := strings.Replace(config.Db, "postgres://", "pgx://", 1) 9 10 // Golang Migrate intialisieren 11 m, _ := migrate.NewWithSourceInstance("iofs", source, dbMigrateURL) 12 13 // Datenbank Schema aktualisieren 14 m.Up() 15 16 // ... 17 } 18 // Connection String für Migration anpassen dbMigrateURL := strings.Replace(config.Db, "postgres://", "pgx://", 1) //go:embed migrations 1 var migrations embed.FS 2 3 func main() { 4 // IO Filesystem erstellen 5 source, err := iofs.New(migrations, "migrations") 6 7 8 9 10 // Golang Migrate intialisieren 11 m, _ := migrate.NewWithSourceInstance("iofs", source, dbMigrateURL) 12 13 // Datenbank Schema aktualisieren 14 m.Up() 15 16 // ... 17 } 18 // Golang Migrate intialisieren m, _ := migrate.NewWithSourceInstance("iofs", source, dbMigrateURL) //go:embed migrations 1 var migrations embed.FS 2 3 func main() { 4 // IO Filesystem erstellen 5 source, err := iofs.New(migrations, "migrations") 6 7 // Connection String für Migration anpassen 8 dbMigrateURL := strings.Replace(config.Db, "postgres://", "pgx://", 1) 9 10 11 12 13 // Datenbank Schema aktualisieren 14 m.Up() 15 16 // ... 17 } 18 // Datenbank Schema aktualisieren m.Up() //go:embed migrations 1 var migrations embed.FS 2 3 func main() { 4 // IO Filesystem erstellen 5 source, err := iofs.New(migrations, "migrations") 6 7 // Connection String für Migration anpassen 8 dbMigrateURL := strings.Replace(config.Db, "postgres://", "pgx://", 1) 9 10 // Golang Migrate intialisieren 11 m, _ := migrate.NewWithSourceInstance("iofs", source, dbMigrateURL) 12 13 14 15 16 // ... 17 } 18 //go:embed migrations var migrations embed.FS func main() { // IO Filesystem erstellen source, err := iofs.New(migrations, "migrations") // Connection String für Migration anpassen dbMigrateURL := strings.Replace(config.Db, "postgres://", "pgx://", 1) // Golang Migrate intialisieren m, _ := migrate.NewWithSourceInstance("iofs", source, dbMigrateURL) // Datenbank Schema aktualisieren m.Up() // ... } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  • 53. Zusammenfassung DogOPv0.2 1. CRUD API fürAngebote 2. Angebote in PostgreSQL speichern 3. Context 4. Connection Pool nutzen 5. DatenbankSchema migrieren
  • 54. 12 FactorApp DogOPv0.2 1. Codebasis 2. Abhängigkeiten 3. Konfiguration 4. Unterstützende Dienste 5. Build, Release, Run 6. Prozesse 7. Bindung an Ports 8. Nebenläufigkeit und Skalierung 9. Einweggebrauch 10. Dev-Prod-Vergleichbarkeit 11. Logs 12. Admin-Prozesse ✅ ✅ ✅ ✅ ✅ ✅
  • 56. Fallschirm und Rettungsgurt Problem Details Health Check Logging und Tracing # # #
  • 57. Health Check Nutzung von health-go (hellofresh) Integration des Checks fürpgx # #
  • 58. 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
  • 61. 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" }
  • 62. Problem Details id, err := uuid.Parse(chi.URLParam(r, "ID")) return func(w http.ResponseWriter, r *http.Request) { 1 // 1. Offer lesen 2 3 if err != nil { 4 problem.New( 5 problem.Title("invalid request"), 6 problem.Wrap(err), 7 problem.Status(http.StatusBadRequest), 8 ).WriteTo(w) 9 return 10 } 11 } 12 id, err := uuid.Parse(chi.URLParam(r, "ID")) if err != nil { problem.New( problem.Title("invalid request"), problem.Wrap(err), problem.Status(http.StatusBadRequest), ).WriteTo(w) return return func(w http.ResponseWriter, r *http.Request) { 1 // 1. Offer lesen 2 3 4 5 6 7 8 9 10 } 11 } 12 id, err := uuid.Parse(chi.URLParam(r, "ID")) if err != nil { problem.New( problem.Title("invalid request"), problem.Wrap(err), problem.Status(http.StatusBadRequest), ).WriteTo(w) return return func(w http.ResponseWriter, r *http.Request) { 1 // 1. Offer lesen 2 3 4 5 6 7 8 9 10 } 11 } 12
  • 63. 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 #
  • 64. 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
  • 65. Middlewares nutzen // Basis Middlewares 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 Middlewares 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 Middlewares 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
  • 66. 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 PostgreSQL speichern 3. Context 4. Connection Pool nutzen 5. DatenbankSchema migrieren
  • 67. 3 Gründe fürGo 1. Einfach 2. Mächtig 3. Langweilig