This application implements Scopone, a traditional italian card game. It allows 4 players to play the game. It supports running more games at the same time, in other words "tables" of four players can be active at the same time.
- Steps for a player to play the game
- Project structure
- Server
- Gorilla WebSocket server
- WebSocket server with AWS Lambda function
- Test WebSocket server APIs
- Client architecture and logical structure
- Angular Client
- React Client
- CI/CD scripts
A player needs to open the web application to start playing. The specific steps are
- Launch the application from a browser
- Enter the name of the player
- Choose the game that the player wants to play or create a new game
- Once 4 players join the game, the game a button to start the game appears on the screen of each player and the first player who clicks on that button will actually start the game and will be the first player to play a card
- The server shuffles a deck of cards and distribute 10 cards to each player to start a new hand
- Players play in sequence untill all cards are played
- When all cards of an hand are played, the hand is closed and the result is shown to each player
- Player can continue the game with the next hand or stop playing
- If a player exit the game at any time, maybe by just closing the browser, the game is interrupted
- The game restart from where it was left once all players are connected again
This project is composed by a server and a client.
The Server is a Go application with the server code contained in the server
folder. The server can be connected to a mongodb to store the state of the games.
The client code is in 2 versions
- Angular (currently at version 9) contained in the
client-ng
folder - React (currently at version 17) contained in the
client-react
folder
Client and Server use WebSocket protocol to communicate.
There are also other folders at the top of the workspace.
scopone-rx-service
: contains the code which implements a service which is shared by both clients, the Angular and the React oneserverless-cd
: contains the scripts that implement the CD logic, i.e. the script that allow to build and deploy the entire app, both client and server
The server is implemented in Go.
Server code is in the server/src
folder.
The server implements the WebSocket server which clients connect to.
The server can be deployed as either a standard WebSocket server implemented using the Gorilla library or as an AWS Lambda function. This article describes how WebSockets can work with Lambda.
When using the server as a standard Gorilla WebSockets server (opposed to a WebSocket server delpoyed as Lambda), it can be launched with and without the support of a Mongo database where details of the games are registered.
If Mongo db is used, the server can be stopped and, when it restarts, all games information will be retrieved and the game can continue from where it was left when the server was stopped.
If Mongo db is not used, then if the server stops, all information will be lost and when it restarts all games and players will have been lost.
The commands to launch the standard Gorilla WebSocket server are the main packages found as subfolders in server/src/cmd
.
The Gorilla WebSocket server can be launched in debug mode from within VSCode.
Launch VSCode from within the workspace folder scopone-ng-react
and open in VSCode one of the commands that launch the server (the commands can be found in server/src/cmd
).
Then go to the Run and Debug
view of VSCode and launch the configuration named Launch (scopone-ng-react)
. This command launches the server in debug mode. In this mode it is possible to add breackpoints in the server code. Such breakpoints will interrupt the execution of the server commands which are triggered by the clients which are connected.
In other words, once the server is run in debug mode, any client can connect to the server (on 8080 port as default) and then start interacting with it as normal. Once any command issued by any client hits a breackpoint, the server execution is interrupted and we can step through the code execution.
The configuration of the debug mode can be found in scopone-ng-react/launch.json
.
In case we deploy the WebSocket server as AWS Lambda function, the entry point of the server application is the main
function in the main.go
file in src/server/srvlambda
folder.
To unit test the server move to the server
folder and run the command go test ./...
To build the server app, move to the folder server
and launch the command go build -o scopone-app ./src/cmd/scopone-mongo
(or go build -o scopone-app ./src/cmd/scopone-in-memory-only
for the version which does not use Mongo db).
This command creates the executable scopone-app
in the server
folder.
Once built, the server can be launched with the command MONGO_CONNECTION="mongoConnectionUrl" ./scopone-app
(MONGO_CONNECTION
is not required in case the package built is the one that does not use Mongo db).
To build a Docker image for the Scopone server
- Edit the file
server/Dockerfile-minimal-mongo
to set the environmnet variableMONGO_CONNECTION
with the value of the mongo connection url - run the following command
docker build -t scopone-server -f Dockerfile-minimal-mongo .
To start the WebSocket server with Docker run the command
docker run -p 8080:8080 scopone-server
It is possible to deploy the WebSocket server on GAE. Follow the instructions described in the file command-gcloud.md
.
At the time of writing, GAE supports WebSockets only with its "flexible" offering which does not have any free-plan, so the server, any time is active, is going to cost money. So remember to STOP the WebSocket server if not used.
Once the server is launched, it is possible to ping it from a browser invoking the url https://myhost/
(in case of local installation of the server use the url http://localhost:8080/
).
If the server is up and running, it responds with a simple "Home page" that should appear on the browser.
To build the package ready to be deployed as AWS Lambda function, from within the folder server
run the command env GOOS=linux go build -ldflags="-s -w" -o ./bin/handleRequest ./src/server/srvlambda
.
This command builds an executable named ./bin/handleRequest
.
To deploy the package as AWS Lambda function, the Serveless Framework is used.
Install the Serverless Framework locally using the command npm install
. Note that the file package.json
is present in the server
forder just to allow install the Serveless Framework.
The url of the mongo database to be used has to be set in the server/.env
. Copy the file server/.env-sample
to server/.env
file and change the value of the MONGO_CONNECTION
variable to the connection string pointing to the mongo db instance to be used (the form of the connection string should be in this format mongodb+srv://user:[email protected]/scopone?retryWrites=true&w=majority
).
To deploy the package as AWS Lambda run the command npx sls deploy
.
The serverless.yml
can be found in the server
folder.
If the deployment succeeds, it prints on the console the url for the endpoint of the WebSocket server (in a format similar to wss://an-id-of-endpoint.execute-api.an-aws-region.amazonaws.com/dev
). Take note of it since it will be used to configure the client.
It is possible to test the server APIs running some integration tests which use exclusively the ScoponeService code, e.g. with the code in the scopone-rx-service
folder
Start the server in one of the possible ways as specified here:
- as local server: go to the server folder with
cd server
and launch the server with./scopone-app
- launch with Docker with the command
docker run -p 8080:8080 scopone-server
- deploying the server as a Lambda web socket server
If mongodb is used, then make sure the database is clean. i.e. there are no collection related to Scopone app. All tests can be repeated without having to cleanup the DB, but it may happen that for uncontrolled reasons the DB is left in a state which does not allow to complete the tests, so it is convenient to clean up the db to run the first set of tests.
Set the WebSocket server url as value of the property serverAddress
in the file scopone-rx-service/src/environments/environment.ts
. If the WebSocket server has been launched locally, then the url is probably something like this ws://localhost:8080/osteria
. Otherwise specify the url to be used (e.g. in case of Lambda server, the url would look something like this wss://my-end-point.execute-api.us-east-1.amazonaws.com/dev
).
Run the test launching the command npm run test
from the folder scopone-rx-service
.
Test the server from VSCode playing an entire game with standalone service (e.g. with the service in "scopone-rx-service" folder)
Start the server as described here.
Go to the scopone-rx-service (cd scopone-rx-service
from the workspace folder. Open VSCode from there with the command code .
and from within VSCode, open the file src/test/service.mocha-play-game.spec-1.ts
. Go to the VSCode Debug window, select "Current TS Tests File" in the Run drop down list and launch.
This test creates a new game and plays an entire hand (i.e. plays all 40 cards) and checks that everything works from a WebSockets API point of view.
Do the same steps described in the "Test the server from VSCode" section, but launch the file src/test/scopone-server.service.play-36-cards.spec-1.ts
. This test creates a new game and plays the first hand for 36 cards.
This allows a tester to log in the server from the front end and test the end of an hand without having to play all cards.
There are 2 different implementations of the client, one built with Angular and one built with React. They are implmemented in the client-ng
and client-react
folders respectively.
While Angular and React are different in many ways, they share one important idea: both are component based and the views built with them are, or shuold be, built using the composition pattern. In other words the final UI is the result of composing different simpler smaller components and components can be reused throughout different pages of the UI as long as they provide the right level of customization.
The clients are based on the following basic principles:
- The logic is divided into
- View layer, implemented via Components (Angular or React components)
- Service layer, implemented by the ScoponeServerService whose code can be found in the
scopone-rx-sevice
folder
- Components are light, in other words they are responsible only for
- Intercept UI generated events and tranform them into commands to be sent to an external service
- Subscribe to the streams of events, relevant for the specific Component, which are published by the external service
- The service ScoponeServerService is the core of the UI logic. It is implemented as pure TypeScript logic, with no dependencies on either Angular or React. The service layer exposes 2 types of APIs
- Standard APIs, implemented as methods of the service, which can be called by components when the components what to send a command. Usually (* see below for further explanation of "ususally") the service takes the command invocation parameters and turn it into a command to be sent to the back end server using the web socket channel.
- Stream APIs, implemented as RxJs Observables. These are streams of events that the service makes available to components for subscription. Components which are interested in specific events can subscribe to the relevant Observable and implement, as side effect of notifications, updates in the UI as results of the notifications received.
- The net division between UI components layers and service layer and the clear responsibility set on the service layer allow to have light components and have the vast majority of the logic implemented in the service layer, which being standard TypeScript code with no dependenciy on either Angular or React becomes much easier to test.
(*) We say "usually sends the command to the remote service" since this is not mandatory. The key concern of the ScoponeServerService is to turn a command received into notifications on the various streams of events that it publishes, and this can be performed also without sending a command to the back end server.
Both clients, the Angular and the React one, share the same service, ScoponeServerService implemented in the scopone-rx-sevice
folder
ScoponeServerService is implemented in the folder scopone-rx-sevice
folder and is imported in both the Angular and the React clients.
The Client is an Angular application, currently using version 9 of the framework.
The code is stored in the client-ng
folder,
The connection with the server is configured using the files contained in the folder client/src/environments
.
The file client/src/environments/environment.ts
is used during development.
The file client/src/environments/environment.prod.ts
is used when building the app for production.
The connection url for the WebSocket server has to be set in the property serverAddress
of either client/src/environments/environment.ts
or client/src/environments/environment.prod.ts
. For instance, in case of a WebSocket server running locally, the configuration would be serverAddress: 'ws://localhost:8080/osteria'
.
To launch a development web server, with hot reload, run the following command
ng serve
To build the application for deployment to production, run the command
ng build --prod
The Client is a React application, currently using version 17 of the library.
The code is store in the client-react
folder.
The project was bootstrapped with Create React App. Read more details in the readme.md
file in the folder of the React client.
The connection with the server is configured using the mechanism provided by Create React App to set environment variables.
The connection string needs to be set using the REACT_APP_SERVER_ADDRESS
variable defined in the following files
- .env.development for the development environment
- .env.production for the production environment
These files override what is defined in the .env
file
For instance, in case a local installation of the server is used, the configuration looks like
REACT_APP_SERVER_ADDRESS=ws://localhost:8080/osteria
To launch a development web server, run the following command
npm run start
To build the application for deployment to production, run the command
npm run build
The scripts to automatically build and deploy the client and the server parts of the app are contained in the serverless-cd
folder.
Check that the file server/.env
contains the right connection string for the MongoDB instance to be used.
To deploy the server as a Lambda function move to the serverless-cd/server-lambda
folder and run the command
bash deploy-server-to-lambda.sh
At the end of the execution, if successful, the script prints the address of the Lambda server to be used in the configuration of the clients with a message similar to this
endpoints: wss://abcdef.execute-api.us-east-1.amazonaws.com/dev
The address wss://abcdef.execute-api.us-east-1.amazonaws.com/dev
has to be used as the url of the remote server in the configuration of the Angular and React clients (see above instruction to know how to set this value for the different clients).
To build the Angular client for production and deploy it on an S3 bucket configure to host websites, move to the serverless-cd/client-ng-s3
folder and run the command
bash build-deploy-ng-front-end.sh
To build the React client for production and deploy it on an S3 bucket configure to host websites, move to the serverless-cd/client-react-s3
folder and run the command
bash build-deploy-react-front-end.sh