Skip to content

learning-cloud-native-go/myapp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

120 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

buymeacoffee

Learning Cloud Native Go - myapp

πŸ”‹ Batteries Included

  • Use of Go linters, Docker, Docker Compose, Alpine development images, and Distroless production images.
  • Use of GORM CLI to generate Go generics-based database repositories.
  • Use of Goose to build a DB migration CLI with embedded migrations.
  • Use of Validator.v10 to validate forms via Go generics-based, fail-fast validation middleware.
  • Use of Zerolog to generate request logs and centralized Syslog logging.
  • Use of Swag.v2 to generate OpenAPI v3 specifications.
  • Use of GitHub Actions to run linters and tests, and to build and push production images to the registry.
  • Use of GitOps with ArgoCD to automate declarative environment orchestration and application lifecycle management.
Environment Go 1.26 Image Size Postgres v18 Image Size
Development 800 MB 300MB
Production 30 MB

πŸ“Ÿ Available Commands

$ just
πŸš€MYAPP
    help                    # List available commands
    install                 # Install development tools
    go-run cmd="app"        # Run a specific cmd (defaults to app)
    go-run-migrate cmd="up" # Run database migrations (defaults to up)
    build                   # Run docker compose build
    up cmd=""               # Run docker compose up
    down                    # Run docker compose down
    lint                    # Run lints using gofumpt, go vet, staticcheck and govulncheck
    test                    # Run tests
    gen                     # Run go generate for all packages
    gen-openapi             # Generate openapi v3 specification using swag v2
    gen-gorm-repos          # Generate gorm repositories using gorm cli

πŸ›¬ Endpoints

Name HTTP Method Route
Health GET /livez
List Books GET /v1/books
Create Book POST /v1/books
Read Book GET /v1/books/{id}
Update Book PUT /v1/books/{id}
Delete Book DELETE /v1/books/{id}

πŸ—„οΈ Database Design

Column Name Datatype Not Null Primary Key
id UUID βœ… βœ…
title TEXT βœ…
author TEXT βœ…
published_date DATE βœ…
image_url TEXT
description TEXT
created_at TIMESTAMP βœ…
updated_at TIMESTAMP βœ…

⛔️ Form Validation

{
  "errors": {
    "title": "This is a required field",
    "author": "This can only contain alphabetic and space characters",
    "published_date": "This must be a valid date",
    "image_url": "This must be a valid URL"
  }
}

πŸ“ Request Logs and Centralized Syslog Logging

db-1  | 2018-01-10 01:00:00.000 +08 [1] LOG:  database system is ready to accept connections
Container myapp-db-1 Healthy
app-1  | 2018/01/10 01:00:00 OK   00001_create_books_table.sql (2.21ms)
app-1  | 2018/01/10 01:00:00 goose: successfully migrated database to version: 1
app-1  |
app-1  | {"level":"info","time":"2018-01-10T02:00:00+08:00","message":"Starting server :8080"}
app-1  |
app-1  | [7.218ms] [rows:1] INSERT INTO books (id, created_at, updated_at, title, author, published_date, image_url, description) VALUES ('38ba23d1-9565-40ed-b781-aacd2f84018d', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'Death Note', 'Light Yagami', '2006-10-04 00:00:00', 'https://static.wikia.nocookie.net/deathnote/images/9/94/A_Death_Note.jpg', 'A supernatural volume dropped into the human world by the Shinigami Ryuk') RETURNING *
app-1  | {"level":"info","request_id":"d5mq7oi6hkls7397s43g","received_time":"2018-01-10T03:00:00+08:00","method":"POST","url":"/v1/books","header_size":135,"body_size":0,"agent":"yaak","referer":"","proto":"HTTP/1.1","remote_ip":"192.168.65.1","server_ip":"172.19.0.3","status":201,"resp_header_size":47,"resp_body_size":296,"latency":8.307,"time":"2018-01-10T03:00:00+08:00"}
app-1  | {"level":"info","request_id":"d5mq7oi6hkls7397s43g","id":"38ba23d1-9565-40ed-b781-aacd2f84018d","time":"2018-01-10T03:00:00+08:00","message":"new book created"}
app-1  |
app-1  | [2.541ms] [rows:1] SELECT * FROM books WHERE id = '38ba23d1-9565-40ed-b781-aacd2f84018d'
app-1  | {"level":"info","request_id":"d5mqa6a6hkls7397s44g","received_time":"2018-01-10T04:00:00+08:00","method":"GET","url":"/v1/books/38ba23d1-9565-40ed-b781-aacd2f84018d","header_size":82,"body_size":0,"agent":"yaak","referer":"","proto":"HTTP/1.1","remote_ip":"192.168.65.1","server_ip":"172.19.0.3","status":200,"resp_header_size":47,"resp_body_size":296,"latency":2.674625,"time":"2018-01-10T04:00:00+08:00"}
app-1  |
app-1  | [3.744ms] [rows:1] UPDATE books SET updated_at=CURRENT_TIMESTAMP, title='Death Note', author='Misa Amane', published_date='2004-11-04 00:00:00', image_url='https://static.wikia.nocookie.net/deathnote/images/9/94/A_Death_Note.jpg', description='Light Yagami''s buried notebook' WHERE id = '38ba23d1-9565-40ed-b781-aacd2f84018d' RETURNING *
app-1  | {"level":"info","request_id":"d5mqesa6hkls7397s45g","id":"38ba23d1-9565-40ed-b781-aacd2f84018d","time":"2018-01-10T05:00:00+08:00","message":"book updated"}
app-1  | {"level":"info","request_id":"d5mqesa6hkls7397s45g","received_time":"2018-01-10T05:00:00+08:00","method":"PUT","url":"/v1/books/38ba23d1-9565-40ed-b781-aacd2f84018d","header_size":135,"body_size":0,"agent":"yaak","referer":"","proto":"HTTP/1.1","remote_ip":"192.168.65.1","server_ip":"172.19.0.3","status":200,"resp_header_size":47,"resp_body_size":252,"latency":4.018875,"time":"2018-01-10T05:00:00+08:00"}
app-1  |
app-1  | [3.035ms] [rows:1] DELETE FROM books WHERE id = '38ba23d1-9565-40ed-b781-aacd2f84018d' RETURNING true
app-1  | {"level":"info","request_id":"d5mqfgi6hkls7397s460","received_time":"2018-01-10T06:00:00+08:00","method":"DELETE","url":"/v1/books/38ba23d1-9565-40ed-b781-aacd2f84018d","header_size":82,"body_size":0,"agent":"yaak","referer":"","proto":"HTTP/1.1","remote_ip":"192.168.65.1","server_ip":"172.19.0.3","status":200,"resp_header_size":47,"resp_body_size":0,"latency":3.265,"time":"2018-01-10T06:00:00+08:00"}
app-1  | {"level":"info","request_id":"d5mqfgi6hkls7397s460","id":"38ba23d1-9565-40ed-b781-aacd2f84018d","time":"2018-01-10T06:00:00+08:00","message":"book deleted"}
app-1  |
app-1  | [2.573ms] [rows:1] SELECT * FROM books LIMIT 10 OFFSET 0
app-1  | {"level":"info","request_id":"d5mq9gi6hkls7397s440","received_time":"2018-01-10T07:00:00+08:00","method":"GET","url":"/v1/books","header_size":82,"body_size":0,"agent":"yaak","referer":"","proto":"HTTP/1.1","remote_ip":"192.168.65.1","server_ip":"172.19.0.3","status":200,"resp_header_size":47,"resp_body_size":298,"latency":2.926916,"time":"2018-01-10T07:00:00+08:00"}
app-1  |
app-1  |
app-1  | [1.661ms] [rows:0] DELETE FROM books WHERE id = '38ba23d1-9565-40ed-b781-aacd2f84018d' RETURNING true
app-1  | {"level":"info","request_id":"d5mrj8ppsdvs73dkfct0","received_time":"2018-02-01T01:00:00+08:00","method":"DELETE","url":"/v1/books/38ba23d1-9565-40ed-b781-aacd2f84018d","header_size":82,"body_size":0,"agent":"yaak","referer":"","proto":"HTTP/1.1","remote_ip":"192.168.65.1","server_ip":"172.19.0.3","status":404,"resp_header_size":47,"resp_body_size":0,"latency":1.80125,"time":"2018-02-01T01:00:00+08:00"}
app-1  | [1.384ms] [rows:0] UPDATE books SET updated_at=CURRENT_TIMESTAMP, title='Death Note', author='Misa Amane', published_date='2004-11-04 00:00:00', image_url='https://static.wikia.nocookie.net/deathnote/images/9/94/A_Death_Note.jpg', description='Light Yagami''s buried notebook' WHERE id = '38ba23d1-9565-40ed-b781-aacd2f84018d' RETURNING *
app-1  | {"level":"info","request_id":"d5mqjmhqvtmc73foh3dg","received_time":"2018-02-02T08:00:00:00","method":"PUT","url":"/v1/books/38ba23d1-9565-40ed-b781-aacd2f84018d","header_size":135,"body_size":0,"agent":"yaak","referer":"","proto":"HTTP/1.1","remote_ip":"192.168.65.1","server_ip":"172.19.0.3","status":404,"resp_header_size":47,"resp_body_size":0,"latency":1.576,"time":"2018-02-02T08:00:00+08:00"}

// πŸ’― Real logs collected locally but with few rearrangements to make it easier to read.

πŸ—‚οΈ Project Folder Structure

β”œβ”€β”€ compose.yml
β”œβ”€β”€ Dockerfile
β”‚
β”œβ”€β”€ openapi-v3.yaml
β”‚
β”œβ”€β”€ app
β”‚   β”œβ”€β”€ book
β”‚   β”‚   β”œβ”€β”€ bookrepo      # πŸ’‘generated with gorm-cli via the interface in book/repository.go
β”‚   β”‚   β”‚   └── repository.go
β”‚   β”‚   β”œβ”€β”€ form_util.go
β”‚   β”‚   β”œβ”€β”€ handler.go
β”‚   β”‚   └── repository.go
β”‚   └── router
β”‚       └── router.go
β”œβ”€β”€ form    # πŸ’‘Form validation middleware rely on this and pkg folder only
β”‚   └── book.go
β”œβ”€β”€ model
β”‚   └── book.go
β”‚
β”œβ”€β”€ config
β”‚   └── config.go
β”‚
β”œβ”€β”€ cmd   # πŸ’‘Entrypoint for app and migrate executables
β”‚   β”œβ”€β”€ app
β”‚   β”‚   └── main.go
β”‚   └── migrate
β”‚       β”œβ”€β”€ main.go
β”‚       └── migrations
β”‚           └── 00001_create_books_table.sql
β”‚
└── pkg (middleware, logger, validator, ctxutil, paramsutil, errors)

πŸ—οΈ ArgoCD and Kustomize

ArgoCD and Kustomize based cloud native IaC & GitOps setup.

πŸ’‘ Consider moving to a Hub-and-Spoke architecture for Argo CD, combined with a separate repository strategy.

└── k8s
    β”‚
    β”œβ”€β”€ bootstrap
    β”‚   β”œβ”€β”€ argocd
    β”‚   └── argocd-config
    β”‚       β”œβ”€β”€ clusters
    β”‚       β”œβ”€β”€ projects
    β”‚       └── applications
    β”‚
    β”œβ”€β”€ platform
    β”‚   β”œβ”€β”€ metrics-server
    β”‚   β”œβ”€β”€ gateway-api
    β”‚   β”œβ”€β”€ istio-ambient
    β”‚   └── cloudnative-pg
    β”‚
    β”œβ”€β”€ components
    β”‚   └── myapp-db
    β”œβ”€β”€ services
    β”‚   β”œβ”€β”€ base
    β”‚   β”‚   └── myapp
    β”‚   └── overlays
    β”‚       β”œβ”€β”€ dev
    β”‚       β”œβ”€β”€ prod
    β”‚       └── stage
    β”‚
    └── gateways

πŸ’‘ Sample Kind Dev Cluster

kind create cluster --name dev
kubectl apply -k k8s/bootstrap/argocd
kubectl apply -k k8s/bootstrap/argocd-config
kubectl apply -k k8s/platform/istio-ambient
kubectl apply -k k8s/platform/gateway-api
kubectl apply -k k8s/gateways
kubectl port-forward svc/shared-gateway-dev-istio -n istio-ingress 8081:8081 # πŸ’‘ Shared Dev Gateway
curl -X GET 'localhost:8081/myapp/v1/books' --header 'Accept: application/json'

kubectl port-forward svc/argocd-server -n argocd 8080:443 # πŸ’‘ ArgoCD Dashboard(admin/password)

About

πŸš€ Dockerized Go API application with DB migrations, GitOps and Kubernetes Kustomize based CD

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project

  •  

Packages

 
 
 

Contributors