ICAC Scoresheet
A realtime digital scoresheet experience for archery competitions.
About
The Imperial College Archery Club thrives on the annual influx of new students, most of whom never had any formal experience with the sport. Having spent the past 4 years as an active member of the club, I have witnessed the mess and shitstorm 💩 of manual labour that comes with running competitions on pen-on-paper. From rectifying honest mistakes and illegible handwriting to scores being recorded in the wrong order, the whole experience has always been stressful for everyone involved. ICAC Scoresheet was envisioned as a solution to these issues - to finally digitize the paper scoresheet into a realtime application optimized for mobile web browsers.
Key Features
ICAC Scoresheet was envisioned to be the first web application of its kind - delivering a truly seamless realtime experience across match participants. Unlike other logging services that merely save submitted scores to a database for future reference, ICAC Scoresheet runs matches live through Websocket connections implemented using the Socket.IO library. As a result, ICAC Scoresheet can implement additional match-time guardrails to further enhance competition integrity by:
- • always updating participants with the latest scoresheets
- • ensuring participants only score for their peers
- • requesting unanimous score confirmations with every end of arrows before locking-in the submission
Application Architecture
Given the time-constrained and fault-intolerant nature of running competitions, ICAC Scoresheet was built from the ground-up with redundancy and horizontal scalability in-mind. As such, the application's backend was designed as a collection of stateless containerized microservices. At present, the application runs on 4 independently scalable services.
- • a NextJS service for the frontend
- • an Express service serving REST APIs
- • a dedicated match hosting service running Socket.IO
- • an NGINX service running as a reverse proxy and load balancer
Considering the importance of data integrity, both Redis and PostgreSQL are hosted externally on Redis Cloud and Supabase as fully managed database services.
Main Challenges
Horizontally Scaling Websockets
The main challenge as with any realtime application relying on Websockets was the management of server-side client states. Being a stateful communication protocol by default, building a truly stateless, horizontally scalable Websocket server was without a doubt, a monumental challenge compared building to simple RESTful endpoints. While Socket.IO provides features such as session cookies and client connection recovery capabilities to facilitate sticky sessions at the load balancer level, it would still leave live matches at the mercy of a single server instance. The solution to this was to move all data pertaining to live match states to a Redis instance that serves as a single source of truth for all Socket.IO server instances. This simplifies the load balancing scenario whereby clients would now have access to their associated sessions irrespective of which server instance they were routed to.
Asynchronous Reads-and-Writes
To maximize the performance of a single server instance, the Socket.IO match hosting service adopts a fully asynchronous event-driven architecture to manage responses to client interactions. Considering that the event loop does not guarantee a deterministic execution order of database calls when managing multiple concurrently running asynchronous callbacks, potential race conditions had to be abated through the widespread use of Redis transactions and in some cases, custom locking mechanisms. The careful management of such operations was a big part of the system design in developing ICAC Scoresheet to ensure a consistent and seamless user experience.
Authentication Across Multiple Services
A modular containerized microservice architecture does indeed come with a unique set of requirements for managing user authentication. On the server-side, Supabase was used as a centralized authentication service. Request authentication could easily be performed by calling the Supabase Go-true APIs through application middlewares. However, browser restrictions on cross-domain cookies posed a significantly greater challenge for the management of client-side JWTs.
After several weeks of tinkering with potential solutions, I decided that the simplest solution for managing client-side JWTs was to completely forego the idea of managing cross-domain cookies and serve the application under a single domain via NGINX. This was deemed appropriate following reasons:
- • NGINX was fairly straightforward to set up as a reverse proxy
- • simplifies cookie management as all cookies can simply be set as secure HttpOnly, sameSite cookies which are going to be automatically sent with all requests to the backend
- • the unjustifiably high cost of wildcard SSL certificates for the current scale of the application
What Happened to the Project?
Ultimately, the success of any project comes down to funding and finances. In the end, the club was not interested in paying for AWS fees and so we decided to table development for the time being. Though the goal of this project for me personally, had always been to tackle some of the most difficult concepts in backend development. While yes, one could argue that large swathes of this project has been over-engineered, I still think that ICAC Scoresheet was a major success for the purposes of my learning. In fact, I believe this experience has led me to appreciate simplicity (KISS and YAGNI) in system design. Something that I still strongly carry as one of my core principles to this day.