This project is a demonstration of best practices while building software.
From product side application is a simple app that supports creating account, logging in with existing accounts, logging in with google, browsing polls with pagination, creating polls, and voting on polls.
It utilizes some of the popular technologies like golang, REST api, grpc, echo, golangci-lint, postgre, atlas db migrations, terraform, docker, helm, make, openapi, typescript, nextjs, react, redux, tailwind, google login.
To run this project locally:
- Rename /.envrc.example to .
/.envrc
- Rename ./front/.envrc.example to .
./front/.envrc
- Run
direnv allow
- Run it using
docker compose up
- Apply db schema migration using atlas
make db_migrate
- If you don't have atlas installed you can install it by running
make dep
- If you don't have atlas installed you can install it by running
- visit
http://localhost:1314
To run this project in cloud:
- Install yq, terraform, helm, atlas, aws cli
- run
make infra_deploy
- it will provision your eks cluster, vpc, and ecr's, and create unique secrets
- it will push front image and api image to appropriate ecrs
- it will use helm to deploy 3 services (front, db/postgre, api) and a single ingress
- it will connect to db service in order to migrate database schemas
Api is build using common web library called echo.
There is also a [grpc implementation][pkg/server/grpc].
It contains unit test which provide a decent coverage and enable efficient code scaling.
It follows common best practice of abstracting database layer allowing underlying db to be easily replaced.
It also exposes public schemas and errors allowing other software to reuse it.
API configuration is done trough most common library called viper.
It is documented trough openapi (ex Swagger) and proto service definition
Code is structured in a way that enables adding different communication mechanisms such as grpc.
Front is built using next.js and statically compiled.
It uses redux for keeping the state, as well as typescript in order to make the code more scalable.
Some containers and pages use local state to simplify the logic.
Styles are done trough extensible tailwind tailwind classes.
Infrastructure is setup using terraform in order to make it repeatable, collaborative and extensible.
- It can easily support multiple different environments such as development, staging, production
Helm is used to produce kubernetes manifest files encouraging code reuse.
- Postgre
db
service is meant to scale vertically whilefront
andapi
services can scale horizontally (that's why there are 2 node groups) db
pod uses ebs in order to make database persistent trough pod upgrades and restarts- Eks cluster is setup in a way that ingress provisions it's own public elb allowing multiple different ingresses to exist
Docker image tagging uses md5 hash of commit instead of latest
to increase transparency of what code is running in cluster.
This project is organized in the manner of monorepo You can find below explanations and overview of project directory structure.
-
- there is a single golang command and it is to start a server
-
- contains atlas.hcl database schema
-
./front contains frontend nextjs project in the manner of monorepo
- ./front/src/components
- contains all the react components that don't utilize state
- ./front/src/containers
- contains all the react components that are using state
- ./front/src/lib
- contains frontend helpers
- ./front/src/pages
- contains nextjs pages
- ./front/src/store
- contains redux store and typings support
- ./front/src/store/reducers
- contains implementation reducers
- ./front/src/style - contains simple global styles
- ./front/src/components
-
./infrastructure contains infrastucture files needed to deploy to cloud
- ./infrastructure/terraform
- contains terraform project used to provision aws cloud infrastructure
- ./infrastructure/helm
- contains helm templates used to deploy services on kubernetes cluster
- ./infrastructure/terraform
-
./pkg contains golang rest api app
-
- used to perform app initalization
-
- utlizes viper to perform config initalization
-
./pkg/domain domain bound code
- ./pkg/domain/db
- postgre database access layer, containing all the queries
- ./pkg/domain/model
- domain bound business logic
- ./pkg/domain/util
- utilities used for api
- ./pkg/domain/db
-
./pkg/server/grpc contains grpc server logic
- ./pkg/server/grpc/mapper
- maps model types to proto generated types
- ./pkg/server/grpc/proto
- contains proto file definitions, as well as generated code
- ./pkg/server/grpc/sever
- contains grpc concrete implementations
- ./pkg/server/grpc/sever/interceptor
- contains interceptors for authentication and logging errors
- ./pkg/server/grpc/mapper
-
./pkg/server/http contain http server logic
- ./pkg/server/http/api
- contains all api controllers
- ./pkg/server/http/middleware
- contains api middleware such as error handler or auth logic
- ./pkg/server/http/router
- contains route mapping
- ./pkg/server/http/schema
- contains public schemas for api input and outputs as well as possible public errors
- ./pkg/server/http/api
-
The reason why I haven't setup ci/cd for the project is that it would require always running cloud infrastructure (which would cost a bit of $$).
Since this is a demo project I've decided not to follow trunk based development, nor git-flow.
So performing testing and linting doesn't make sense.
However....
API code quality is ensured through running make lint
.
API code corectness is ensured through running make test
.
I've tried to follow git semantic commits while developing this project.
Thank you for having a look at this project, I hope that you've enjoyed it.