- Controller: Contains RESTful controller classes. These classes handle incoming HTTP requests and define the API endpoints.
- Entity: Stores data models that are mapped to database tables and define the properties and relationships of the application data.
- Enums: Used to represent pre-defined
Countryvalues. - Exception: Contains custom exception classes for managing errors and exceptional conditions. These classes ensure consistent error handling and meaningful error messages throughout the application.
- Repo: Contains repository classes that deal with data access using
JPA (Java Persistence API)to interact with the database. - Service: Contains service classes that implement business logic. Controllers use these services to perform operations on data.
I implemented locking mechanisms on userId and country. When a user updating their level or claiming reward, the operations are synchronized on the userId. This prevents multiple threads from modifying the same user's data simultaneously, ensuring data integrity. When a user enters a tournament, they are synced on the country, preventing multiple users from the same country from joining the same group.
I cached the current tournament in a variable instead of constantly querying the database. I also used a ConcurrentHashMap for country scores, allowing fast, thread-safe updates. This in-memory storage reduces latency compared to frequent database writes. I simply saved the HashMap to a countryLeaderboard.dat file at shutdowns. Then, I load HashMap from that file at application starts. A cron job saves Country Leaderboard to database at 20:00 UTC.
TournamentServiceenter tournament operations and country leaderboard managment for a tournament.TournamentParticipantServicefor participant operations such as claiming rewards, updating scores, learning their rankings.UserServicefor user actions such as creating user and updating level.TournamentGroupServicefor group leaderboard management.
This separation of concerns makes the code easier to read and maintain.
Custom exceptions like NoActiveTournamentException and UserAlreadyParticipantException help handle errors clearly and provide precise feedback to users.
When assigning tournament participants to their groups, I proceed by assigning them to the group closest to the start, that is, the fullest.
TournamentParticipant createdAt attribute used in claimReward in order to get last participation.
- Clone the repository
- Navigate to the project directory
- Run
docker compose up - Send Request to endpoints (postman collection provided)
- Spring Data JPA
- Lombok
- H2 (for testing)
- Mockito
