/beers-srv

Enjoy serving 🍻 through micro-services using gRPC

Primary LanguageGo

beers

Enjoy serving 🍻 through micro-services using gRPC


asciicast

Stack

  • CI/CD: Github Actions (e.g Review, Release automation goreleaser, etc)
  • Testing: Go testing package
  • Serialization: Protocol Buffers protobuf
  • OCI orchestration: Kubernetes, Docker

Architecture

Following DDD principles I've separated bounded contextes and behaviors (e.g. adding, removing, serializing, grpc) into multiple packages using Service Objects, Database Layer Abstraction, Repository pattern, CLI interafces so that decopling behaviors lead to easier testing and shipping new features.

Installation

Installing beers-srv is easy, using go get you can install the cmd line app beers-cli to interact with gRPC server. First you'll need Google's Protocol Buffers installed.

$ brew install protobuf
$ go get -u github.com/azbshiri/beers-srv/...

Docker

If you have Docker already installed then you don't need Go or Protocol Buffers you just need to run below command:

$ docker build -t beers-srv:0.1.0 .
Sending build context to Docker daemon  360.4kB
Step 1/11 : FROM golang:1.13-alpine as builder
 ---> 4acab7f5278b
Step 2/11 : COPY . /go/src/github.com/azbshiri/beers
...

$ docker run -d --rm --name=beers-srv beers-srv:0.1.0
$ docker exec -i beers-srv grpc-health-probe -addr=:8000
status: SERVING

Testing

I haven't used any external libraries for testing/diffing so if you already have latest version Go installed, just simply run go test -v ./... :

ok      github.com/azbshiri/beers-srv/pkg/adding    (cached)
=== RUN   Test_Add
=== RUN   Test_Add/adds_beer
=== RUN   Test_Add/checks_against_blank_name
=== RUN   Test_Add/fails_due_to_service
--- PASS: Test_Add (0.00s)
    --- PASS: Test_Add/adds_beer (0.00s)
    --- PASS: Test_Add/checks_against_blank_name (0.00s)
    --- PASS: Test_Add/fails_due_to_service (0.00s)
PASS
ok 

Kubernetes

Deploying gRPC applications to K8s and the best way to configure health checks is using grpc-health-probe and GRPC Health Checking Protocol https://kubernetes.io/blog/2018/10/01/health-checking-grpc-servers-on-kubernetes/

$ kubectl apply -f deployment.yml
$ kubectl get svc beers-srv
NAME        TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
beers-srv   NodePort   10.110.71.154   <none>        8000:30768/TCP   15m

executing “grpc_health_probe” will call our gRPC server over localhost (Cluster IP):

$ minikube status
kubectl: Correctly Configured: pointing to minikube-vm at 192.168.122.118

$ grpc-health-probe --addr=192.168.122.118:30768
status: SERVING

kaboom!

Event sorucing

I'd go with Google Pub/Sub API to keep track of events on cluster in an event store and https://watermill.io/ API to make it easier to deal with events/subscribers.

  • BeerRemoved
  • BeerAdded
  • BeerNameChanged etc

Cloud naviteness

Using Kubernetes and Docker you can easily run this applicaiton on cloud with ease scaling up/down, add database integeration and etc.

The Twelve-Factor App

I. Codebase One codebase tracked in revision control, many deploys II. Dependencies Explicitly declare and isolate dependencies

Using Go modules I've isolated dependencies as well as moved out shared code into separate codebase with semantic versioning also since this is an small micro-service I decided to use one branch master for deployment and build and adding features using separate branches using PRs.

III. Config Store config in the environment

There's not much of a configuration in this micro-service but I've used env variables to setup the minimum.

IV. Backing services Treat backing services as attached resources

There's no backing services (database, etc) but otherwise still using env vars and service discovery tools one can easily decople attached services and their communication.

V. Build, release, run Strictly separate build and run stages

master is now reilable stage for building and release also using GitHub actions I run linting, testing and building for each PR.

VI. Processes Execute the app as one or more stateless processes

Since I'm using in-memory allocation and no database is envovled so this app is not stateless but because I've already implemented database abstraction layer and repository pattern it's just so easy to configure databases and other backing services using env vars / service discovery to make this app stateless. (e.g. store removing, adding into separate process pg, etc)

VII. Port binding Export services via port binding

By setting PORT you can easily change port of services (in this case only gRPC server)

VIII. Concurrency Scale out via the process model

OCI orchestration is a factor here as you can scale down/up easily on clusters (GCP, AWS) using Docker and K8s.

IX. Disposability Maximize robustness with fast startup and graceful shutdown

Using K8s and Docker I've implemented gRPC health protocol so K8s can easily restart/replace service instances when needed.

X. Dev/prod parity Keep development, staging, and production as similar as possible

As I have only one master branch and many sub-branches this is also true

XI. Logs Treat logs as event streams

Unfortunately I haven't had time to make this consitent but I guess in some cases I'm treating logs like streams?!

XII. Admin processes Run admin/management tasks as one-off processes

When run using Docker it's simple as running docker exec to do admin tasks also when developing using beers-cli and grpc-health-probe to make sure everything is working fine.

Things that are missed

  • Move protocol buffers generation/linting to Github actions
  • Fix GoReleaser on Github actions (checkout action doesn't sync tags)
  • Do dependency injection for logging and make it consistent
  • More human-friendly errors when dealing with
  • Add GCP/AWS integration using Terraform
  • Manage protocol buffers using buf or gunk
  • Service discovery using etcd, consul or etc
  • Implement fault-tolerant mechanisms https://github.com/afex/hystrix-go
  • Move data storage to a stateful service