Simple fizzbuzz microservice
Table of contents
General info
The original fizzbuzz consists in writing all numbers from 1 to 100, and just replacing all multiples of 3 by "fizz", all multiples of 5 by "buzz", and all multiples of 15 by "fizzbuzz". The output would look like this:
"1,2,fizz,4,buzz,fizz,7,8,fizz,buzz,11,fizz,13,14,fizzbuzz,16,..."
The goal is to implement a web server that will expose a REST API endpoint that:
- Accepts five parameters : three integers
int1
,int2
andlimit
, and two stringsstr1
andstr2
. - Returns a list of strings with numbers from 1 to
limit
, where: all multiples ofint1
are replaced bystr1
, all multiples ofint2
are replaced bystr2
, all multiples ofint1
andint2
are replaced bystr1str2
.
The server needs to be:
- Ready for production
- Easy to maintain by other developers
- Add a statistics endpoint allowing users to know what the most frequent request has been. This endpoint should:
- Accept no parameter
- Return the parameters corresponding to the most used request, as well as the number of hits for this request
The svc-fizzbuzz microservice exposes a REST API with the following endpoints:
-
/
- Forwards to /version endpoint.
-
/version
- Returns the service version.
-
/status
- Returns the service status.
-
/metrics
- Returns the prometheus metrics.
-
/swagger.json
- Returns the swagger service description file.
-
- Returns a list of strings with numbers from 1 to
limit
, where: all multiples ofint1
are replaced bystr1
, all multiples ofint2
are replaced bystr2
, all multiples ofint1
andint2
are replaced bystr1str2
. - Query String (or POST body parameters):
linit
(positive integer) max value100.000
int1
(positive integer) default value3
int2
(positive integer) default value5
str1
(string) default valuefizz
str2
(string) default valuebuzz
- Returns a list of strings with numbers from 1 to
-
/api/v1/fizzbuzz/top
- Returns usage statistics of the /api/v1/fizzbuzz endpoint. It allows the users to know what the number of hits of that endpoint. And returns the parameters corresponding to it.
Technologies
Project is created with:
- go: 1.16
- redis: 6.0.9
- docker: 20.10.6
Up and Running
--database-connect
flag see Usage for more information.
Baremetal
Install localy (baremetal) (needs a redis server) :
$ go get github.com/hugdubois/svc-fizzbuzz
Docker
Install via Docker (needs a redis server) :
$ docker pull hugdubois/svc-fizzbuzz:1.0.0
$ docker run -d --name=svc-fizzbuzz --net=host -it hugdubois/svc-fizzbuzz:1.0.0 serve --database-connect localhost:6379
NOTE : docker images can be found on dockerhub.
Docker compose
Install via docker-compose (without git clone) (needs curl) :
$ curl https://raw.githubusercontent.com/hugdubois/svc-fizzbuzz/master/hack/remote-docker-compose.sh | sh
NOTE : that script will create a svc-fizzbuzz-compose with all required files
With git clone
simply do :
$ git clone https://github.com/hugdubois/svc-fizzbuzz
$ cd svc-fizzbuzz
$ make compose-up
Some services are exposed:
- svc-fizzbuzz on http://localhost:8080/
- prometheus on http://localhost:9090/
- grafana on http://localhost:3000/
- some dashboards can be found here
Kubernetes
Install via kubernetes (needs kubectl):
$ kubectl apply -f https://raw.githubusercontent.com/hugdubois/svc-fizzbuzz/master/k8s-deployment.yaml
NOTE: if you use minikube do minikube service svc-fizzbuzz
to expose and get the service ip or simply use kubectl port-forward deployment/svc-fizzbuzz 8080:8080
.
Usage
svc-fizzbuzz is a sipmle fizzbuzz microservice.
Basic usage:
$ svc-fizzbuzz [command]
Available Commands:
- completion generates the autocompletion script for the specified shell
- help help about any command
- serve launches the svc-fizzbuzz service webserver
- version returns service version
To get help simply run svc-fizzbuzz help
.
To launch the API webserver run: svc-fizzbuzz serve
-
Some flags are available :
- --address (string) (short -a)
- Must be used to set the HTTP server address.
- ex:
127.0.0.1:13000
- default:
:8080
- --cors-origin (string) (short -c)
- Must be used to set the Cross Origin Resource Sharing AllowedOrigins. It's a string separed by
|
. - ex:
http://*domain1.com|http://*domain2.com
- default:
*
- Must be used to set the Cross Origin Resource Sharing AllowedOrigins. It's a string separed by
- --database-connect (string)
- Must be used to set the redis server connection informations. [[db:]password@]host:port.
- ex:
1:passW0rd@redis-server:6379
- default:
localhost:6379
- --debug (boolean)
- Must be used to force debug mode.
- ex:
1:passW0rd@redis-server:6379
- default:
localhost:6379
- --help (string)
- Must be used to get help.
- --read-timeout (duration)
- Must be used to set the server read timeout (5s,5m,5h) before connection is cancelled.
- ex:
10s
- default:
5s
- --shutdown-timeout (duration)
- Must be used to set the server shutdown timeout (5s,5m,5h) graceful shutdown.
- ex:
15s
- default:
10s
- --write-timeout (duration)
- Must be used to set the server write timeout (5s,5m,5h) before connection is cancelled.
- ex:
15s
- default:
10s
- --address (string) (short -a)
-
The svc-fizzbuzz microservice exposes a REST API with the following endpoints:
- /
- Forwards to /version endpoint.
- /version
- Returns the service version.
- /status
- Returns the service status.
- /metrics
- Returns the prometheus metrics.
- /swagger.json
- Returns the swagger service description.
- /api/v1/fizzbuzz
- Returns a list of strings with numbers from 1 to
limit
, where: all multiples ofint1
are replaced bystr1
, all multiples ofint2
are replaced bystr2
, all multiples ofint1
andint2
are replaced bystr1str2
. - Query String (or POST body parameters):
linit
(positive integer) max value100.000
int1
(positive integer) default value3
int2
(positive integer) default value5
str1
(string) default valuefizz
str2
(string) default valuebuzz
- Returns a list of strings with numbers from 1 to
- /api/v1/fizzbuzz/top
- Returns usage statistics of the /api/v1/fizzbuzz endpoint. It allows the users to know what the number of hits of that endpoint. And returns the parameters corresponding to it.
- /
Examples
/
$ curl "localhost:8080/"
Should return
{"name":"svc-fizzbuzz","version":"v1.0.0"}
/status
$ curl "localhost:8080/status"
Should return
{"svc-alive":true,"store-alive":true}
/api/v1/fizzbuzz
This is the core API endpoint. This endpoint returns a list of strings with numbers from 1 to limit
, where: all multiples of int1
are replaced by str1
, all multiples of int2
are replaced by str2
, all multiples of int1
and int2
are replaced by str1str2
.
$ curl "localhost:8080/api/v1/fizzbuzz"
Should return a original fizzbuzz
{"fizzbuzz":["1","2","fizz","4","buzz","fizz","7","8","fizz","buzz","11","fizz","13","14","fizzbuzz","16","17","fizz","19","buzz","fizz","22","23","fizz","buzz","26","fizz","28","29","fizzbuzz","31","32","fizz","34","buzz","fizz","37","38","fizz","buzz","41","fizz","43","44","fizzbuzz","46","47","fizz","49","buzz","fizz","52","53","fizz","buzz","56","fizz","58","59","fizzbuzz","61","62","fizz","64","buzz","fizz","67","68","fizz","buzz","71","fizz","73","74","fizzbuzz","76","77","fizz","79","buzz","fizz","82","83","fizz","buzz","86","fizz","88","89","fizzbuzz","91","92","fizz","94","buzz","fizz","97","98","fizz","buzz"]}
The query string (or POST body parameters):
linit
(positive integer) max value100 000
int1
(positive integer) default value3
int2
(positive integer) default value5
str1
(string) default valuefizz
str2
(string) default valuebuzz
$ curl "localhost:8080/api/v1/fizzbuzz?limit=10"
Should return only ten values of the original fizzbuzz.
{"fizzbuzz":["1","2","fizz","4","buzz","fizz","7","8","fizz","buzz"]}
More complex query:
$ curl "localhost:8080/api/v1/fizzbuzz?limit=10&int1=2&int3=4&str1=bon&str2=coin"
Should return only ten values of a custom fizzbuzz.
{"fizzbuzz":["1","bon","3","bon","coin","bon","7","bon","9","boncoin"]}
So all of these calls
$ curl -XPOST "localhost:8080/api/v1/fizzbuzz?limit=10&int1=2&int3=4&str1=bon&str2=coin"
$ curl -XPUT "localhost:8080/api/v1/fizzbuzz?limit=10&int1=2&int3=4&str1=bon&str2=coin"
$ curl -XPATCH "localhost:8080/api/v1/fizzbuzz?limit=10&int1=2&int3=4&str1=bon&str2=coin"
$ curl -XPOST -d "limit=10&int1=2&int3=4&str1=bon&str2=coin" "localhost:8080/api/v1/fizzbuzz"
$ curl -XPUT -d "limit=10&int1=2&int3=4&str1=bon&str2=coin" "localhost:8080/api/v1/fizzbuzz"
$ curl -XPATCH -d "limit=10&int1=2&int3=4&str1=bon&str2=coin" "localhost:8080/api/v1/fizzbuzz"
Should return only ten values of a custom fizzbuzz.
{"fizzbuzz":["1","bon","3","bon","coin","bon","7","bon","9","boncoin"]}
422 Unprocessable Entity
with the reason of the error.
So :
$ curl -v "localhost:8080/api/v1/fizzbuzz?limit=infini"
Should return an error.
...
< HTTP/1.1 422 Unprocessable Entity
...
{"code":422,"message":"Bad parameter: 'limit' must be a positive number - got (infini)"}%
And
$ curl "localhost:8080/api/v1/fizzbuzz?int1=bon"
Should return also an error.
{"code":422,"message":"Bad parameter: 'int1' must be a positive number - got (bon)"}%
So :
curl "localhost:8080/api/v1/fizzbuzz?int1=bad&int2=bad"
Should return only one error.
{"code":422,"message":"Bad parameter: 'int1' must be a positive number - got (bad)"}%
/api/v1/fizzbuzz/top
This endpoint returns the usage statistics of the /api/v1/fizzbuzz endpoint. It allows the users to know what the number of hits of that endpoint. And returns the parameters corresponding to it.
$ curl "localhost:8080/api/v1/fizzbuzz/top"
Should return only one error.
{"data":{"params":{"limit":10,"int1":2,"str1":"bon","int2":5,"str2":"coin"},"count_request":10}}%
/metrics
This endpoint exposes the prometheus metrics.
$ curl "localhost:8080/metrics"
Should return.
...
# HELP fizzbuzz_http_requests_duration_millisecond How long it took to process the request, partitioned by status code, method and HTTP path.
# TYPE fizzbuzz_http_requests_duration_millisecond summary
fizzbuzz_http_requests_duration_millisecond_sum{code="OK",method="GET",path="/"} 0
fizzbuzz_http_requests_duration_millisecond_count{code="OK",method="GET",path="/"} 1
fizzbuzz_http_requests_duration_millisecond_sum{code="OK",method="GET",path="/api/v1/fizzbuzz"} 0
fizzbuzz_http_requests_duration_millisecond_count{code="OK",method="GET",path="/api/v1/fizzbuzz"} 3
fizzbuzz_http_requests_duration_millisecond_sum{code="OK",method="GET",path="/api/v1/fizzbuzz/top"} 1
fizzbuzz_http_requests_duration_millisecond_count{code="OK",method="GET",path="/api/v1/fizzbuzz/top"} 1
fizzbuzz_http_requests_duration_millisecond_sum{code="OK",method="GET",path="/status"} 0
fizzbuzz_http_requests_duration_millisecond_count{code="OK",method="GET",path="/status"} 1
fizzbuzz_http_requests_duration_millisecond_sum{code="OK",method="HEAD",path="/api/v1/fizzbuzz"} 0
fizzbuzz_http_requests_duration_millisecond_count{code="OK",method="HEAD",path="/api/v1/fizzbuzz"} 1
fizzbuzz_http_requests_duration_millisecond_sum{code="OK",method="OPTIONS",path="/api/v1/fizzbuzz"} 0
fizzbuzz_http_requests_duration_millisecond_count{code="OK",method="OPTIONS",path="/api/v1/fizzbuzz"} 1
fizzbuzz_http_requests_duration_millisecond_sum{code="OK",method="PATCH",path="/api/v1/fizzbuzz"} 4
fizzbuzz_http_requests_duration_millisecond_count{code="OK",method="PATCH",path="/api/v1/fizzbuzz"} 2
fizzbuzz_http_requests_duration_millisecond_sum{code="OK",method="POST",path="/api/v1/fizzbuzz"} 0
fizzbuzz_http_requests_duration_millisecond_count{code="OK",method="POST",path="/api/v1/fizzbuzz"} 2
fizzbuzz_http_requests_duration_millisecond_sum{code="OK",method="POST",path="/api/v1/fizzbuzz/top"} 0
fizzbuzz_http_requests_duration_millisecond_count{code="OK",method="POST",path="/api/v1/fizzbuzz/top"} 1
fizzbuzz_http_requests_duration_millisecond_sum{code="OK",method="PUT",path="/api/v1/fizzbuzz"} 4
fizzbuzz_http_requests_duration_millisecond_count{code="OK",method="PUT",path="/api/v1/fizzbuzz"} 5
fizzbuzz_http_requests_duration_millisecond_sum{code="Unprocessable Entity",method="GET",path="/api/v1/fizzbuzz"} 0
fizzbuzz_http_requests_duration_millisecond_count{code="Unprocessable Entity",method="GET",path="/api/v1/fizzbuzz"} 6
fizzbuzz_http_requests_duration_millisecond_sum{code="Unprocessable Entity",method="PATCH",path="/api/v1/fizzbuzz"} 0
fizzbuzz_http_requests_duration_millisecond_count{code="Unprocessable Entity",method="PATCH",path="/api/v1/fizzbuzz"} 1
...
/swagger.json
This endpoint exposes the swagger service description.
$ curl "localhost:8080/metrics"
Should return.
{
...
"swagger": "2.0",
...
}
Not found
All others calls return an 404 Not Found
error.
$ curl -v "localhost:8080/does_not_exists"
Should return.
...
< HTTP/1.1 404 Not Found
...
{"code":404,"message":"Not Found"}
Contribute
This repository follows the Conventional Commits and standard git flow.
See package documentation on pkg.go.dev.
Code coverage can be found on codecov.
CI is on travis and github actions.
Make directives
make build
- (default) build the service and inject the version (-ldflags
).make version
- Display current version (VERSION file).make tools
- Buildtools
that are in_tools
directory.make test
- Run test.make test-v
- Run test on verbose mode.make test-live
- Run test on infinite shell loop.make test-cover
- Run test with coverage.make test-cover-profile
- Run test with coverage and generate a profile coverage file.make test-cover-report
- Run test with coverage UI on a browser (go tool cover -html=...
).make test-cover-func
- Run test with total coverage computation (go tool cover -func=...
).make gen-swagger
- Generate swagger service declaration from project annotation.make serve
- Build and launch the server API with the debug mode activate.make clean
- Removing all generated files (compiled files, code coverage, ...).make docker-tag
- Generate.env
file to docker-compose.make docker
- Generate docker image.make docker-push
- Push the docker image to the repository (useDOCKER_REGISTRY
andDOCKER_IMAGE_NAME
like thisDOCKER_REGISTRY={{hostname}}:{{port}} make docker-push
)make docker-run
- Run service with docker.make docker-rm
- Remove service from docker.make compose-up
- Rundocker-compose up -d
.make compose-down
- Rundocker-compose up
.make compose-ps
- Rundocker-compose ps
.make k8s-deploy
- Deploy kubernetes deployment on the current cluster viakubectl
.make k8s-delete
- Delete kubernetes deployment from the current cluster viakubectl
.make update-pkg-cache
- Performs ago get
via https://proxy.golang.org asGOPROXY
.
Directories structure
_tools
- The directory contains some used tools (cf.make tools
)cmd
- The directory contains thecli
commands.core
- The directory is the core domain layer.docs
- The directory is dedicated to swagger it contains generated files by swag tool.hack
- The directory contains some shell scripts.helpers
- The directory contains a helpers package.infra
- The directory contains some infrastructure code to docker-compose.middlewares
- The directory contains all HTTP middlewares.service
- The directory is the service layer.store
- The directory is persistence layer.vendor
- The directory isgo mod
vendoring.
Notes
The statistics to /api/v1/fizzbuzz/top endpoint are stored in a redis sorted sets.
To retrieve the most used request a simple ZREVRANGE k 0 0 WITHSCORES is good enough.
Roadmap
- separate core domain (without dependencies)
- nice cli with usage and help
- light simple http service (with graceful shutdown and errors recovering)
- nice requests log
- allows CORS
- endpoint to expose prometheus metrics
- CI
- test coverage >~ 80% (core 100%)
- docker / docker-compose
- simple k8s deployment
- use kustomize to bump TAG in k8s-deployment.yaml
- add swagger file and expose it on /swagger.json endpoint
- add a cache
- TLS support
Authors
Support
mail: hugdubois@gmail.com