Folders and files
| Name | Name | Last commit date | ||
|---|---|---|---|---|
Repository files navigation
Task Implement client and server in Java. Description Server offers service for player wallet (balance). Wallet state (balance) should be managed in memory (3rd party solution may not be used). Balance is backed up in database (hsql). When balance is not in memory, it is loaded from database and any changes are done in memory. Player record in database is created on demand. There is a periodical background process to write changes from memory to database. Constraints on balance changes: 1. Balance cannot be less than 0. 2. If transaction exists (is duplicate), then previous response is returned. Check may take into consideration only 1000 latest transactions. 3. If balance change is bigger than configured limit, then change is denied (explained further below). 4. If player is in blacklist, then change is denied (explained further below). Configuration (balance change limit and player blacklist) must be taken from external source. This can be file, database, external component etc. Client itself is a server that offers gameplay logic. Specific gameplay will not be implemented, client can just generate random balance updates. Specific communication protocol between client and server is not specified (custom protocol can be invented). Server must write proper log information, where at least IN/OUT per player must be grep’able. Commands between servers: client->server: username, transaction id, balance change server->client: transaction id, error code, balance version, balance change, balance after change Database structure PLAYER(USERNAME, BALANCE_VERSION, BALANCE) Additional requirements Number of players is small enough to fit their data in RAM. when a node crashes, the balance update can be lost. But the clients must have a way to eventually recover from this situation Several concurrent clients should be supported Implementation aspects Number of players is small enough to fit their data in RAM. java.util.concurrent.ConcurrentMap in DataPool class being used for wallet state management in memory. DataPool is a golden source of balances. Current state is drained to hsqldb periodically, see DataPool.writeToDb(). Spring schedulers and task executors exploited by the application configured at ExecutorsConfig. Constraints on balance changes and actual balance changes are implemented in BalanceService. Syncronized LinkedHashMap exploited in BalanceService to keep track of latest 1000 transactions. So, request handling implementation on server is thread-safe but it uses global synchronization, on the other hand Balance change limit can be configured at application.properties, blacklist is a table at hsql. BalanceUpdatesProducer in balance-client module generates random balance updates. Custom socket based protocol with serialization implemented between client and server. Several clients can connect to the same server concurrently. So, several instances of ClientApp can be run freely. In case when server crashes balance updates are not lost but resent by the client on server start. Updates may be lost in case when client's 'balanceUpdatesDeque' is at capacity. Usually, it happens when the server is down for significant time. Consumer keeps order of messages being resent (see BalanceUpdatesConsumer: balanceUpdatesDeque.addFirst(request);). Hsql configured as in-memory db. Not implemented features: When balance is not in memory, it is loaded from database. Number of players is small enough to fit their data in RAM So, there is no need to load balance from database on-demand. A socket opens and closes per each request. This fact causes overhead on both client and server side. Lack of time to do it more efficiently. A library https://github.com/ben-manes/concurrentlinkedhashmap by google guy were initially used to keep track of latest 1000 transactions. When ConcurrentLinkedHashMap is at capacity, it evicts oldest records according to insertion order but it this asynchronously, not immediatelly. So, I was unable to use it due to strict constraint 'Check may take into consideration only 1000 latest transactions'. Guava cache was considered as not an option here as well as circular buffer. Blacklist could be implemented using com.google.common.hash.BloomFilter to archieve less memory usage, but in this case contains() time complexity less predictable due to possible database call due to false positiveness.