Skip to content

Commit

Permalink
refactor: major refactor which introduces new practices (#5)
Browse files Browse the repository at this point in the history
[major] v1.0
1. Removed `vendor` directory for Go 
2. Introduced interface in API layer for different cmd implementations (server, subscriber, cli etc.)
3. Using OTEL for instrumentation
4. Upgraded all dependencies, including Go version
5. Updated docs to use gonew instead of custom bash script for reusing goapp to start a new project
6. Updated overall README to possibly make things easier to understand and clearer
  • Loading branch information
bnkamalesh authored Apr 28, 2024
1 parent 17d30c0 commit af12a99
Show file tree
Hide file tree
Showing 1,255 changed files with 1,880 additions and 460,636 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/golangci-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
steps:
- uses: actions/setup-go@v3
with:
go-version: 1.18
go-version: 1.22
- uses: actions/checkout@v3
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ on:
- master
jobs:
test:
name: "Test with Go 1.19"
name: "Test with Go 1.22"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Set up Go 1.19
- name: Set up Go 1.22
uses: actions/setup-go@v3
with:
go-version: 1.19
go-version: 1.22

- name: Test
run: go test -coverprofile=coverage.txt -covermode=atomic $(go list ./... | grep -v /cmd)
257 changes: 108 additions & 149 deletions README.md

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions cmd/server/grpc/grpc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package grpc

import "github.com/bnkamalesh/goapp/internal/api"

type GRPC struct {
apis api.Server
}

func New(apis api.Server) *GRPC {
return &GRPC{
apis: apis,
}
}
14 changes: 8 additions & 6 deletions internal/server/http/handlers.go → cmd/server/http/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ import (
"runtime/debug"

"github.com/bnkamalesh/errors"
"github.com/bnkamalesh/webgo/v6"
"github.com/bnkamalesh/webgo/v7"

"github.com/bnkamalesh/goapp/internal/api"
"github.com/bnkamalesh/goapp/internal/pkg/logger"
)

// Handlers struct has all the dependencies required for HTTP handlers
type Handlers struct {
api *api.API
apis api.Server
home *template.Template
}

Expand Down Expand Up @@ -56,7 +56,7 @@ func (h *Handlers) routes() []*webgo.Route {
// Health is the HTTP handler to return the status of the app including the version, and other details
// This handler uses webgo to respond to the http request
func (h *Handlers) Health(w http.ResponseWriter, r *http.Request) error {
out, err := h.api.Health()
out, err := h.apis.ServerHealth()
if err != nil {
return err
}
Expand All @@ -80,7 +80,7 @@ func (h *Handlers) HelloWorld(w http.ResponseWriter, r *http.Request) error {
struct {
Message string
}{
Message: "welcome to the home page!",
Message: "Welcome to the Home Page!",
},
)
if err != nil {
Expand All @@ -106,7 +106,9 @@ func errWrapper(h func(w http.ResponseWriter, r *http.Request) error) http.Handl

status, msg, _ := errors.HTTPStatusCodeMessage(err)
webgo.SendError(w, msg, status)
_ = logger.Error(errors.Stacktrace(err))
if status > 499 {
logger.Error(r.Context(), errors.Stacktrace(err))
}
}
}

Expand All @@ -118,7 +120,7 @@ func panicRecoverer(w http.ResponseWriter, r *http.Request, next http.HandlerFun
}
webgo.R500(w, errors.DefaultMessage)

_ = logger.Error(fmt.Sprintf("%+v", p))
logger.Error(r.Context(), fmt.Sprintf("%+v", p))
fmt.Println(string(debug.Stack()))
}()

Expand Down
27 changes: 27 additions & 0 deletions cmd/server/http/handlers_usernotes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package http

import (
"encoding/json"
"net/http"

"github.com/bnkamalesh/errors"
"github.com/bnkamalesh/goapp/internal/usernotes"
"github.com/bnkamalesh/webgo/v7"
)

func (h *Handlers) CreateUserNote(w http.ResponseWriter, r *http.Request) error {
unote := new(usernotes.Note)
err := json.NewDecoder(r.Body).Decode(unote)
if err != nil {
return errors.InputBodyErr(err, "invalid JSON provided")
}

un, err := h.apis.CreateUserNote(r.Context(), unote)
if err != nil {
return err
}

webgo.R200(w, un)

return nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"net/http"

"github.com/bnkamalesh/errors"
"github.com/bnkamalesh/webgo/v6"
"github.com/bnkamalesh/webgo/v7"

"github.com/bnkamalesh/goapp/internal/users"
)
Expand All @@ -19,21 +19,13 @@ func (h *Handlers) CreateUser(w http.ResponseWriter, r *http.Request) error {
return errors.InputBodyErr(err, "invalid JSON provided")
}

createdUser, err := h.api.CreateUser(r.Context(), u)
createdUser, err := h.apis.CreateUser(r.Context(), u)
if err != nil {
return err
}

b, err := json.Marshal(createdUser)
if err != nil {
return errors.InputBodyErr(err, "invalid input body provided")
}
webgo.R200(w, createdUser)

w.Header().Set("Content-Type", "application/json")
_, err = w.Write(b)
if err != nil {
return errors.Wrap(err, "failed to respond")
}
return nil
}

Expand All @@ -42,11 +34,12 @@ func (h *Handlers) ReadUserByEmail(w http.ResponseWriter, r *http.Request) error
wctx := webgo.Context(r)
email := wctx.Params()["email"]

out, err := h.api.ReadUserByEmail(r.Context(), email)
out, err := h.apis.ReadUserByEmail(r.Context(), email)
if err != nil {
return err
}

webgo.R200(w, out)

return nil
}
106 changes: 106 additions & 0 deletions cmd/server/http/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package http

import (
"context"
"fmt"
"net/http"
"strconv"
"strings"
"time"

"github.com/bnkamalesh/errors"
"github.com/bnkamalesh/goapp/internal/api"
"github.com/bnkamalesh/goapp/internal/pkg/apm"
"github.com/bnkamalesh/webgo/v7"
"github.com/bnkamalesh/webgo/v7/middleware/accesslog"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)

// Config holds all the configuration required to start the HTTP server
type Config struct {
Host string
Port uint16

ReadTimeout time.Duration
WriteTimeout time.Duration
DialTimeout time.Duration

TemplatesBasePath string
EnableAccessLog bool
}

type HTTP struct {
listener string
server *webgo.Router
}

// Start starts the HTTP server
func (h *HTTP) Start() error {
h.server.Start()
return nil
}

func (h *HTTP) Shutdown(ctx context.Context) error {
err := h.server.Shutdown()
if err != nil {
return errors.Wrap(err, "failed shutting down HTTP server")
}

return nil
}

// NewService returns an instance of HTTP with all its dependencies set
func NewService(cfg *Config, apis api.Server) (*HTTP, error) {
home, err := loadHomeTemplate(cfg.TemplatesBasePath)
if err != nil {
return nil, err
}

handlers := &Handlers{
apis: apis,
home: home,
}

router := webgo.NewRouter(
&webgo.Config{
Host: cfg.Host,
Port: strconv.Itoa(int(cfg.Port)),
ReadTimeout: cfg.ReadTimeout,
WriteTimeout: cfg.WriteTimeout,
ShutdownTimeout: cfg.WriteTimeout * 2,
},
handlers.routes()...,
)

if cfg.EnableAccessLog {
router.Use(accesslog.AccessLog)
router.UseOnSpecialHandlers(accesslog.AccessLog)
}
router.Use(panicRecoverer)

otelopts := []otelhttp.Option{
// in this app, /-/ prefixed routes are used for healthchecks, readiness checks etc.
otelhttp.WithFilter(func(req *http.Request) bool {
return !strings.HasPrefix(req.URL.Path, "/-/")
}),
// the span name formatter is used to reduce the cardinality of metrics generated
// when using URIs with variables in it
otelhttp.WithSpanNameFormatter(func(operation string, r *http.Request) string {
wctx := webgo.Context(r)
if wctx == nil {
return r.URL.Path
}
return wctx.Route.Pattern
}),
}

apmMw := apm.NewHTTPMiddleware(otelopts...)
router.Use(func(w http.ResponseWriter, r *http.Request, hf http.HandlerFunc) {
apmMw(hf).ServeHTTP(w, r)
})

return &HTTP{
server: router,
listener: fmt.Sprintf("%s:%d", cfg.Host, cfg.Port),
}, nil
}
41 changes: 41 additions & 0 deletions cmd/server/http/web/templates/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<title>Hello world</title>
<style>
html,
body {
font-size: 16px;
line-height: 1.5em;
min-width: 100vw;
min-height: 100vh;
}
body {
font-family: sans-serif;
background-color: #efefef;
color: #222;
height: 100vh;
width: 100vw;
overflow: hidden;
}
main {
margin: 40vh auto 0;
width: 35rem;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: "Roboto", sans-serif;
font-weight: 400;
}
</style>
</head>
<body>
<main>
<h3 id="colorize" style="text-align: center">{{.Message}}</h3>
</main>
</body>
</html>
14 changes: 14 additions & 0 deletions cmd/subscribers/kafka/kafka.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Package kafka implements the Kafka subscription functionality
package kafka

import "github.com/bnkamalesh/goapp/internal/api"

type Kafka struct {
apis api.Subscriber
}

func New(apis api.Subscriber) *Kafka {
return &Kafka{
apis: apis,
}
}
10 changes: 5 additions & 5 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM golang:1.20 AS builder
FROM golang:1.22 AS builder

COPY ../ /app
WORKDIR /app
Expand All @@ -13,10 +13,10 @@ LABEL MAINTAINER Author <[email protected]>

# Following commands are for installing CA certs (for proper functioning of HTTPS and other TLS)
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
netbase \
&& rm -rf /var/lib/apt/lists/ \
&& apt-get autoremove -y && apt-get autoclean -y
ca-certificates \
netbase \
&& rm -rf /var/lib/apt/lists/ \
&& apt-get autoremove -y && apt-get autoclean -y

# Add new user 'appuser'. App should be run without root privileges as a security measure
RUN adduser --home "/appuser" --disabled-password appuser \
Expand Down
17 changes: 13 additions & 4 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,26 +1,35 @@
version: "3.9"
services:
redis:
image: "redis:alpine"
image: "redis:7"
networks:
- goapp_network

postgres:
image: "postgres"
image: "postgres:16"
environment:
POSTGRES_PASSWORD: gauserpassword
POSTGRES_USER: gauser
POSTGRES_DB: goapp
ports:
- "5432:5432"
networks:
- goapp_network

goapp:
image: golang:1.20
image: golang:1.22
volumes:
- ${PWD}:/app
working_dir: /app
# command: go run main.go
tty: true
environment:
TEMPLATES_BASEPATH: /app/cmd/server/http/web/templates
POSTGRES_HOST: postgres
POSTGRES_PORT: 5432
POSTGRES_STORENAME: "goapp"
POSTGRES_USERNAME: "gauser"
POSTGRES_PASSWORD: "gauserpassword"
command: go run main.go
ports:
- "8080:8080"
depends_on:
Expand Down
Loading

0 comments on commit af12a99

Please sign in to comment.