Cloud Native Technologien befähigen
skalierbare Anwendungen
zu entwickeln und betreiben,
in modernen, dynamischen Umgebungen.
Cloud Native Computing Foundation
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
package main
import (
"net/http"
"github.com/go-chi/chi/v5"
)
func main() {
r := chi.NewRouter()
r.HandleFunc("GET /", func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("Hello DogOp!"))
})
http.ListenAndServe(":8080", r)
}
// 1. Kompilieren in Binary
go build -o build/dogop .
// 2. Binary ausführen
./build/dogop
POST /api/quote
content-type: application/json
{
"age": 8,
"breed": "chow"
}
HTTP/1.1 200 OK
Content-Type: application/json
{
"age": 8,
"breed": "chow",
"tariffs": [
{
"name": "Dog OP _ Basic",
"rate": 12.4
}
]
}
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.HandleFunc("POST /api/quote", HandleQuote)
r.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello DogOp!"))
})
http.ListenAndServe(":8080", r)
}
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"`
}
// Struct erzeugen
tariff := Tariff{Name: "Dog OP _ Basic", Rate: 12.4}
func HandleQuote(w http.ResponseWriter, r *http.Request) {
// 1. JSON Request lesen
var q Quote
json.NewDecoder(r.Body).Decode(&q) // 💣 Fehler möglich!
// 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) // 💣 Fehler möglich!
}
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
}
// ...
}
main_test.go
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)
}
}
go test -v ./...
=== RUN TestHandleQuote
--- PASS: TestHandleQuote (0.00s)
PASS
ok crossnative/dogop 0.228s
// Einfach per Standardlib
function main() {
port := os.Getenv("DOGOP_PORT")
http.ListenAndServe(fmt.Sprintf(":%v", config.Port), r)
}
// 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)
}
# 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. Docker Image bauen
>_ pack build dogop-cnb
--buildpack paketo-buildpacks/go
--builder paketobuildpacks/builder-jammy-base
// 2. Docker Image ausühren
>_ docker run dogop-cnb
POST /api/offer
content-type: application/json
{
"name": "Rollo",
"age": 8,
"breed": "chow",
"customer": "jan"
}
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": "427ed4de",
"customer": "jan",
"age": 8,
"breed": "chow",
"name": "Rollo"
}
GET /api/offer/427ed4de
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": "427ed4de",
"customer": "jan",
"age": 8,
"breed": "chow",
"name": "Rollo"
}
r.HandleFunc("POST /api/offer", HandleCreateOffer(offerRepository))
r.HandleFunc("GET /api/offer/{ID}", HandleReadOffer(offerRepository))
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)
}
}
pgx
als Postgres Driver und Toolkitdatabase/sql
in Go Standardlib
type OfferRepository struct {
connPool *pgxpool.Pool
}
func (r *OfferRepository) Insert(ctx context.Context, offer *Offer) (*Offer, error) {
// ...
}
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
}
return func(w http.ResponseWriter, req *http.Request) {
// ...
// 2. Offer speichern
createdOffer, _ := offerRepository.Insert(req.Context(), &offer)
// ...
}
func (r *OfferRepository) Insert(ctx context.Context, offer *Offer) (*Offer, error) {
// ..
// 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,
)
// ...
// 4. Transaktion commiten
tx.Commit(ctx)
// ...
}
health-go
(hellofresh)pgx
// 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.HandleFunc("GET /health", h.HandlerFunc)
GET /api/offer/-invalid-
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json
{
"reason": "invalid UUID format",
"status": 400,
"title": "invalid request"
}
if err != nil {
problem.New(
problem.Title("invalid request"),
problem.Wrap(err),
problem.Status(http.StatusBadRequest),
).WriteTo(responseWriter)
return
}
if err != nil {
http.Error(responseWriter, "invalid request", http.StatusBadRequest)
return
}
http.Handler
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)
}
func main() {
r := chi.NewRouter()
// 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))
http.ListenAndServe(":8080", r)
}