This is a series describing various architectures I have worked with in the last two decades and some of their benefits and issues. The table of contents of the series is here.
Winster was a cooperative social gaming web site that enabled players to win real-world prizes. It predated Zynga and Facebook, but both of those companies ‘rose’ during the time I worked for Winster.
Major System Aspects
Winster had a fairly standard Java backend that dealt with managing player information, talking with PayPal, and keeping track of prizes, advertisements, and promotions. This is pretty independent of what Winster was: almost any commercial consumer site might have these capabilities.
What made Winster interesting is that the client was in Adobe Flash/Flex, it was realtime multi-player, and the rules of the games were all stored on the server. This created a pretty compelling environment for players to interact: players could swap pieces and both be in better shape vs. “the house”. And this interaction supported a real-time chat system. So very much like a card-game table without any competitiveness between players.
The client-server interaction was a combination of HTTP calls and socket-based bidirectional updates.
AA-1 : No important logic on the client
At Winster players could ‘win prizes’ based on playing the games. A lot of basic games out there put the actual functionality into the game client (Flash, JavaScript, and even compiled desktop clients). This is fine if there is nothing at stake. Someone hacks the client and they get to play a ‘different’ game. Many games even have available ‘cheat modes’ that make a different game easy to enable.
But if the client can actually impact the business, it has to be securely and correctly implementing business rules. To enable this, you can try to make sure the client is an unhacked/unaltered version of the correct client. Or more simply, you can treat the client as untrusted: it makes request, and the server decides whether they are reasonable.
For Winster, we chose to not trust the client and so every action done on the client that affects the state of the game went through a ‘game server’ that knew the rules of the game. There are a lot of wins for this:
- Servers tend to be easier to verify
- You control the hardware completely, and at least at that time, there were significantly more testing frameworks
- Server failure is ‘unlikely’ and should be totally visible if you have a problem
- You are already writing the server in a particular language, so may more easily be able to augment its capabilities (although some clients have very nice game/event-oriented languages)
There are some losses:
- Latency is guaranteed to be higher, and potentially has to be masked
- For games like first-person shooters, you need to see the bullet fly even though the server determines the hit
- For things like field-validation, you commonly have to repeat yourself on both the client and the server
- Clients sometimes have really nice game language
- If there are delays in answers, you somehow have to get them to the client asynchronously
- As clients scale ‘game servers’ scale.
The last loss can badly affect your operational profits, especially ‘pre-cloud’ which is the timeframe that Winster was. We had to have servers big enough to deal with our peak loads and smart enough not to overload themselves.
The server-side game can be much simpler than the visual appearance on the client side (e.g. think the rules of chess vs. a pretty chess board), but the server-side game has to protect the business rules of the game so people can’t game the game.
AA-2 : Socket based client-server connection
Winster existed before Websocket, Comet, and other specifications and approaches. To communicate what other players did within your room / table, the server sent updates through a direct socket. Making sure customers could connect with a straight socket was painful for customer support (if a customer was behind a very restrictive firewall) and required augmentations to deal with ‘Flash Policies’ and other aspects. The advantage of the Game Server approach was that the socket notifications were just that: notifications that the world was in a new state. If clients missed them, they could get updated on a subsequent notification. Or catch up if initially stalled for some reason.
AA-3 : Protocol versioning
On top of the socket communication was a ‘V1’ and ‘V2’ version of a custom communication protocol. A great rule to any protocol:
- Version it!
You may not think it will change, but by simply versioning the protocol with a ‘V1’ or ‘{ “version”:”v1”, …’ or similar you have enabled easily migrating forward with backward compatibility. In many cases you can never be sure when or if a client will be updated, so you need to enable continued support of old clients until they are commercially non-viable.
AA-4 : AJAX or Send-Data vs. rendering
Because Flash/Flex is a very high-level UI language, the Java server had absolutely no ability to ‘render’ for the client, so there was a very strong client/UI vs. Server/Data & Rules separation. You make a request of the server and you get data back via HTTP / XML or via the socket connection. This enables the client to swap out and enables the server to have easier automated testing.
AA-5 : Logging and Telemetry
Logging has a number of different purposes. Three very different ones include:
- To see if the software has issues / defects
- To have a record if a customer asks for ‘proof’
- To see what a customer and the systems are doing compared to the business benefit
Winster had a lot of logging and telemetry because it (a) needed to work, (b) needed to deal with grouchy customers, and (c) needed to be very optimized to be profitable.
Logging frameworks and infrastructure improve every year, and it is important to put in the best structure you can for the purposes you have.