Abstract
Applying some user sympathy to our over-engineering tic-tac-toe game, with a Java-backed full-stack framework using Quarkus and Remix.
This is the second of a series of expected interludes for the overall Multi-Part Series: Road to JDK 25 — Over-Engineering Tic-Tac-Toe. Last time we went into detail about the algorithms involved in an algorithmic interlude and experimented with creative audio overviews.
We’ve been progressively over-engineering tic-tac-toe (available at the overengineering-tictactoe GitHub repository: here) primarily focused on using finalized features from Java Enhancement Proposals). In this article, we take another break from the regularly scheduled JEP theme. JDK 24 features are in currently crossing ramp-down phases as of the time of writing, solidifying for GA in March 2025, so we’ll simply use JDK 23.
As last promised we’re going to explore system design and a minimum-viable-product as we go beyond the JDK: building modern cloud-native tic-tac-toe micro-services! (GitHub: link)
Over-Engineering Tic-Tac-Toe gets a new paint job! A micro-serivce MVP with Remix/React
NB: This article comes some time after the implementation was paused post-MVP since I’ve been otherwise occupied with life events. If there’s enough interest and I have the time, I’ll pick it back up beyond that with a deeper dive into the tooling/code and finish the pending TODOs and add a follow-up article.
If anything can be built in a single service it would be tic-tac-toe. However, we going to use the secret tool of the professional over-engineer: micro-services. Well…
For all we know, our game could be so successful that everyone in the world (currently estimated at 8.2 billion people) all decide to play it at the same time. We need to be able to scale to 4.1 billion simultaneous games!
Good engineers with user sympathy like to keep their users happy and mine (my daughter) was tired of picking her next move from a 0-indexed array on the command line! Rule number one of creating great products: delight your users!
However, building service-oriented architectures with just pure Java and the JDK wouldn’t be much fun. Therefore selecting a Java-backed stack/framework that maximizes productivity is par for the course when building scalable services.
I’ve selected what I call the Enterprise PERQ stack:
- Prisma/Postgres: Database / persistence layer
- Express.Js: Web servers for web services
- Remix/React: Javascript/Typescript server-side rendered web interfaces with Tailwind CSS integration by default, and intelligent routing.
- Quarkus: Java-based backend services supporting gRPC, REST, GraphQL, Websockets, SSE, OpenAPI, and more.
It’s a solid enterprise-grade stack that is cloud-friendly, reactive, micro-profile, AOT compilation-ready, and utilizes modern server-side React. In addition, these frameworks provide great, modern developer experiences with hot/live reload, great build tools, and good community support.
System Design
There are many ways to approach system design before starting kicking off an implementation. Here, we’ll try to take a relatively lightweight and simplified common-sense approach and follow the following basic playbook to gather, iterate on, and finalize requirements step-by-step.
Splitting requirements into functional and non-functional categories helps ensure both user-facing features and system qualities are thoroughly addressed. The iterative approach allows for incremental refinement, ensuring that the system evolves in response to feedback, resource availability, and changing requirements.
Step One: Functional Requirements
Before noting functional requirements it’s useful to create a survey to gather requirements and non-requirements from users. After surveying a few of our users, we’ve come up with the following functional requirements:
Step Two: Non-Functional Requirements
Some common-sense / ambitious NFRs below round out the requirements:
Step Three: System API Definition
By taking our functional requirements we can produce a sequence diagram that specifies our interactions with the overall system API. These interactions can be later broken info a software architecture diagram where our required services become clear.
NB: I recommend using a mermaid-compatible tool to create sequence diagrams so you can focus on interactions, rather than layout.
Step Four: System Architecture Diagram (Functional)
When we break down the functions identified in our sequence diagram, three main services stand out as being necessary in our architecture to support the functional requirements:
- Web Service: Presentation layer, supports the web pages, login pages
- Game Service: Supports the gameplay and game interactions
- User Service: Supports login/authentication for admin users
Step Five: System Architecture Diagram (Final)
Updating our system diagram to support the non-functional requirements requires further thought.
In order to scale out, we will need to support multiple web services, as well as multiple game services. Since we were matching players with other players or players with bots in the original game service, we will need to migrate that functionality to a game manager service which can direct game interactions to the corresponding game service.
Further, decoupling the web services from the backend services via an API gateway is both good design but also enables us to flexibly update backend APIs and interactions without impacting the web services.
MVP
With a rough system design put together building a minimum viable product (or, perhaps we should be calling it Minimum Valuable Experience) is simply a case of identifying (and simplifying) the path to our first objective “delight the primary users”. To do that we only really need three services to start with to support player v player or player v bot games: the web service (Remix: WebSockets/SSE), the API gateway (Quarkus: REST+gRPC), and the game service (Quarkus: gRPC).
Et Voila: See https://github.com/briancorbinxyz/overengineering-tictactoe-microservices. Feel free to fork!
Services
- tictactoe-web-service: Web service for game UI (Typescript/Remix)
- tictactoe-game-service: gRPC-based service for game logic and game management (Java/Quarkus)
- tictactoe-api-gateway: REST service for game api (Java/Quarkus)
Status
- Basic 3x3 Tic-Tac-Toe functionality supported: Player vs Player, Player vs Bot
- Advanced: Sound effects, music, persistent sound configuration *
- Pending: Tests, error handling, configuration, administration, timeouts, non-classic game setups, analytics, cloud configuration, network play etc.
* Note: None of the advanced features were in the system design. Whilst a good up-front system design is always useful, agility means adjusting to user needs and getting constant feedback to ensure the product meets their expectations—that’s the essence of user sympathy!
Disclaimer:
The views and opinions expressed in this blog are based on my personal experiences and knowledge acquired throughout my career. They do not necessarily reflect the views of or experiences at my current or past employers
Next Steps
- Are you a student or developer? Comment & share your own opinions, favorite learning and study resources, or book recommendations.
- Follow my blog (or digital garden) for future updates on my engineering exploits and professional / personal development tips.
- Connect with @briancorbinxyz on social media channels.
- Subscribe!
Comments
Reply on Bluesky here to join the conversation.