built with concourse
OVERVIEW
Think of a group of people on a playground playing the game catch. There is one ball being thrown around randomly from person to person. People can come and go as they please. If there is one person left, they will toss the ball to themselves until another person joins the game.
Any person that joins must be introduced to the entire group via a friend. If a person has the ball and leave the game, another person will pick it up and continue playing catch.
PREREQUISITES
You will need the following go packages,
go get -u -v github.com/sirupsen/logrus
go get -u -v github.com/cweill/gotests/...
SOFTWARE STACK
- DEVELOPMENT
- go
- gotests
- OPERATIONS
- concourse/fly (optional)
- docker
- SERVICES
Where,
- GUI golang net/http package and ReactJS
- Routing & REST API framework golang gorilla/mux package
- Backend golang
- Database N/A
HOW IT WORKS
Each “person” is an instance of the catch-microservice DockerHub image.
Each instance (i.e. people) has the following features:
- Lightweight.
- Knows who has and had the ball (via
whohasball
state). - Knows who is playing catch (via
friendslist
state). - Can ‘catch’ the ball from any other person, including himself.
- Can ‘throw’ the ball to any other person, including himself.
- Has a unique ID (URI).
- Randomly picks which person to throw the ball to.
Each person has the following State Table:
friendslist
: List of all people playing, even himself (list of URIs)whohasball
: ???????? (his URI)
STARTING AND PLAYING THE GAME
To deploy the first person (lets call him Steve):
docker run jeffdecola/catch-microservice StevesID
Because Steve is the first kid and all by himself, his State Table shall look like:
friendslist
: StevesIDwhohasball
: StevesID
Steve will play catch with himself until another person joins the game.
To deploy another person (e.g. Julie), she must know another person (e.g. Steve):
docker run jeffdecola/catch-microservice larryID steveID
Hence, Julies’s State Table shall look like.
friendslist
: larryID, steveIDwhohasball
: unknown
Steve will immediately throw the ball to julie.
Steve will update Larry’s State Table with the current states (updatestate
).
Steve will also introduce Larry to all the other kids
via his friendslist
if other kids are present (updatestate
).
When a kid catches the ball (throw
) he tells everyone in his friendslist
that he has the ball (updatestate
). Everyone will update their whohasball
state.
RETSful API using JSON
To accomplish the above logic, a RESTful API with json shall be used.
In gom, the http package lets us map request paths to functions.
There are 4 basic commands:
- caniplay
- updatestate
- throw
- ihavetheball
- kick
CANIPLAY - PUT /state
When a new kid (Larry) wants to play, he must ask his friend (Steve) if he can play.
PUT uri/state
{
"cmd": "caniplay",
"uri": "larryURI"
}
Reponse:
{
"response": "success"
}
If Larry does not get a response from Steve, then he can’t play catch and will leave (i.e. exit).
If success Steve will updates his freindslist
and tells all of
the other kids about Larry so they can update their respective friendslist
.
If Steve does not get a response while updating the other kids, he issues a kick command.
Steve will also update Larry’s State Table with his states. Now Larry is up to date and in the game.
UPDATESTATE - PUT /state
When a kids wants ot update a friends state.
PUT uri/state
{
"cmd": "updatestate",
"friendslist": "URI",
"addtofriendslist": "URI",
"whohasball" : {"URI", "URI"}
}
Reponse:
{
"response": "success"
}
THROW BALL - PUT /state
When a kid has the ball and wants to throw it, he randomwly picks someone from
his friendslist
and throws it via:
PUT uri/state
{
"cmd": "throw"
}
Response:
{
"response": "success"
}
If he does not get a reposnse (fail), he first kicks the kid frmo the game and then throws the ball to another kid.
If success, the thrower updates his whohasball
state. The catcher
subsequently tells everyone in his friendslist
who has the ball as follow:
PUT uri/state
{
"cmd": "ihaveball",
"uri": "catcherURI"
}
Response:
{
"response": "success"
}
On success of updating all kids, the catchre is ready to throw the ball.
If the catcher does not get a response (fail) from a kid, he kicks that kid from the game.
KICK FROM GAME- PUT /state
When a kid does not respond, it is assumed he left the game. The kid who got the non-response tell all the other kids who it is so they can purge him from their state.
PUT uri/state
{
"cmd": "kick",
"uri": "kickURI"
}
Response:
{
"response": "success"
}
KID NOT RECEIVING ANY INFO - PUT /state
If a kid left and came back, and does not receive any info, he assumes he’s been kicked and starts to go through his friends list to ask if he can join the game as a new kid.
CREATE THE DOCKER IMAGE
How I created, tested, and deployed the docker image.
RUN
To run.sh,
cd catch-microservice-code
go run main.go
As a placeholder, every 2 seconds it will print,
INFO[0000] Let's Start this!
Hello everyone, count is: 1
Hello everyone, count is: 2
Hello everyone, count is: 3
etc...
CREATE BINARY
To create-binary.sh,
cd catch-microservice-code/bin
go build -o catch-microservice ../main.go
./catch-microservice
This binary will not be used during a docker build since it creates it’s own.
STEP 1 - TEST
To create unit _test
files,
cd catch-microservice-code
gotests -w -all main.go
To run unit-tests.sh,
go test -cover ./... | tee test/test_coverage.txt
cat test/test_coverage.txt
STEP 2 - BUILD (DOCKER IMAGE VIA DOCKERFILE)
To build.sh with a Dockerfile,
cd catch-microservice-code
docker build -f build/Dockerfile -t jeffdecola/catch-microservice .
You can check and test this docker image,
docker images jeffdecola/catch-microservice:latest
docker run --name catch-microservice -dit jeffdecola/catch-microservice
docker exec -i -t catch-microservice /bin/bash
docker logs catch-microservice
docker rm -f catch-microservice
In stage 1, rather than copy a binary into a docker image (because that can cause issues), the Dockerfile will build the binary in the docker image,
FROM golang:alpine AS builder
RUN go get -d -v
RUN go build -o /go/bin/catch-microservice main.go
In stage 2, the Dockerfile will copy the binary created in
stage 1 and place into a smaller docker base image based
on alpine
, which is around 13MB.
STEP 3 - PUSH (TO DOCKERHUB)
You must be logged in to DockerHub,
docker login
To push.sh,
docker push jeffdecola/catch-microservice
Check the catch-microservice docker image at DockerHub.
STEP 4 - DEPLOY (TO DOCKER)
To deploy.sh,
cd catch-microservice-code
docker run --name catch-microservice -dit jeffdecola/catch-microservice
docker exec -i -t catch-microservice /bin/bash
docker logs catch-microservice
docker rm -f catch-microservice
CONTINUOUS INTEGRATION & DEPLOYMENT
Refer to ci-README.md on how I automated the above steps.