Game Development

Distributed Fizz Buzz: Passing the Microservices Interview

Distributed Fizz Buzz: Passing the Microservices Interview

by Nathan Ladd and Scott Bellware

The video titled 'Distributed Fizz Buzz: Passing the Microservices Interview' features Nathan Ladd and Scott Bellware discussing how the classic FizzBuzz coding challenge can be adapted to fit a microservices architecture context.

  • Introduction to FizzBuzz: The FizzBuzz challenge is introduced as a common programming task during job interviews, where participants count from 1 to 100, outputting 'Fizz' for numbers divisible by 3, 'Buzz' for numbers divisible by 5, and 'FizzBuzz' for numbers divisible by both 3 and 5. The origin of FizzBuzz is mentioned, transitioning from a drinking game to a children's game.

  • Distributed FizzBuzz Concept: The speakers propose a scenario where FizzBuzz is played in a microservices environment, simulating multiple clients submitting answers to a service. The service manages the game state and publishes events based on client submissions, introducing the complexities inherent in distributed systems such as message reliability and network issues.

  • Design Principles for Distributed Systems: Key principles are discussed, including the need for microservices to handle failures gracefully. Emphasis is placed on avoiding direct synchronous interactions and instead using command and event messages to manage game flow.

  • Code Demonstration: The presenters discuss the structure of command and event messages, game entities, and the processing of messages within a microservices architecture. They demonstrate how to implement core logic using an example while stressing the importance of understanding optimistic concurrency and event sourcing.

  • Challenges Addressed: The team identifies the potential pitfalls when scaling the FizzBuzz implementation due to issues like message ordering and idempotence. They introduce design patterns such as the Idempotence Key pattern, the Reservation pattern, and the Sequence Number pattern to ensure system reliability.

  • Interactive Conversation: Mid-presentation, Nathan engages Scott in a dialogue about the practical implications of microservices architecture, questioning the common overreliance on specific programming languages. They also cover the significance of understanding APIs and messaging in achieving service autonomy.

  • Conclusion and Workshop Promotion: The video concludes with a stress on the ongoing need for learning and collaboration in mastering distributed systems, along with a promotion for an upcoming workshop on scalable system development in Ruby, which offers attendees extensive insights into building robust microservices.

Overall, this presentation provides a comprehensive examination of implementing FizzBuzz within a microservices framework, discussing both practical coding strategies and design considerations to avoid common pitfalls in distributed systems.

00:00:09.320 It's pretty common during job interviews to be asked to program FizzBuzz. Now that microservices have become such a popular movement, I think it's time we equip ourselves for a distributed FizzBuzz in the microservices interview context.
00:00:14.219 Before we begin, I want to review the rules of FizzBuzz. In a normal interview where you're asked to write a FizzBuzz program, you count from 1 to 100. Each time a number is divisible by 3, you output 'Fizz'; each time it's divisible by 5, you output 'Buzz'. If a number is divisible by both, like 15, you output 'FizzBuzz'. For all other numbers, you output the number itself.
00:00:18.269 Now, one thing that often happens when you need to extend a presentation duration and need some filler is to talk about the origins and history of the topic at hand. So, I'm going to discuss the origin of FizzBuzz. About 20 years ago, it was popularized as a children's game, but before that, it was actually a drinking game.
00:00:27.840 You and your friends would gather in a tavern, taking turns to say the next number or 'FizzBuzz' or whatever, and of course, you would lose if you got it wrong. The lesson I derive from this is that any game suitable for adults while drinking is also suitable for children when sober.
00:00:39.870 With that in mind, let me describe a scenario where two people are playing FizzBuzz together: Player 1 and Player 2 are at the tavern, having a round of beers. Player 1 decides to throw down the gauntlet and challenges Player 2 to a duel of FizzBuzz. He starts with '1'.
00:00:52.079 Player 2 responds with '2' because she's always willing to take up a challenge, so the game is afoot. Player 1 continues with '3', and then they both realize how boring this game is. They decide to make some unfortunate decisions.
00:01:01.300 Player 2 resumes the challenge and musters her best effort. However, four rounds in, Player 1 fails miserably. This serves as a model for a FizzBuzz microservice experience. Instead of having one program count from 1 to 100, we simulate having different players or clients making submissions.
00:01:07.859 The clients of the microservice—whether that's another microservice or a web frontend—will submit their answers. The service keeps track of the game state, and whenever a client submits a correct answer, the service advances its counter and publishes an event message. Conversely, if a client submits an incorrect answer, the service rejects that turn.
00:01:24.889 The command and event messages we send constitute the API or interface of this service. We interact with it through these messages. However, while sending messages, we must acknowledge the reality of distributed systems, particularly that messages are sent over potentially unreliable networks.
00:01:41.610 We can count on messages failing to arrive at times, arriving multiple times, or arriving in a different order than they were sent. This is an undeniable reality due to the laws of physics. Because latency is not zero, services can only really operate with their own local data. They can't rely on querying other services as the information may become outdated by the time a response is received.
00:02:00.380 This informs a significant design principle in distributed systems: we need our microservices to handle failures gracefully and account for failure modes that don't exist in fully synchronous systems. This underlying reality is what complicates the FizzBuzz implementation. In traditional programming, FizzBuzz is straightforward—typically about 17 lines of code—however, with networking and messaging elements, we have to consider failures or face consequences.
00:02:15.960 Moreover, synchronous request/response patterns, such as HTTP GET calls, are no longer suitable due to the risks of out-of-date data. Setting this up leads to a structure where clients send command messages over a command queue to the FizzBuzz service, which will then publish events.
00:02:31.200 Let's delve into some code. We begin with our message objects: command messages and event messages. They function as schemas and contracts that define how services exchange information.
00:02:38.420 These command and event messages often share common attributes. For instance, the game ID, answer, and time are frequently copied from the command message into the event message. Additionally, our event messages contain two timestamps: one for when the client requested the turn and another for when the service processed that command.
00:02:56.559 It's essential to maintain awareness of these two distinct timestamps. So let’s examine the schemas for our command and event messages. The 'Take Turn' command allows you to start a new FizzBuzz game by taking the first turn. If the turn is accepted, the service publishes 'Turn Taken' for all to see. However, if an answer is wrong, the service will record and publish 'Turn Rejected'.
00:03:13.160 There are exit criteria for the game: either the counter reaches 100 successfully, or someone makes an incorrect move, resulting in the game ending abruptly. The next element to discuss is the entity, which I will demonstrate with some real code.
00:03:42.520 Here we have our entity—a simple data structure akin to a model in Rails, but without any persistence layer attached. To excel in an interview, separating your persistence layer from your entity layer can leave a positive impression on your interviewer. This entity features a command method, 'Take Turn,' and a predicate method, 'Correct,' which determines if a proposed answer is correct.
00:03:56.239 The core logic of FizzBuzz resides in the 'Correct' predicate method. This entity keeps track of the game state and can confirm whether someone provided a correct answer. That's sufficient for the entity. Next, I’ll show you the command handler, keeping in mind that clients are sending command messages over the command queue to the FizzBuzz service.
00:04:08.740 The command handler class is responsible for processing that message type. The first thing we notice is a lot of boilerplate code—demonstrating capability in writing comprehensive classes can impress interviewers as well.
00:04:23.780 Beneath the boilerplate, we have our business logic. Here, when we take the turn, we retrieve the game entity from a store or repository. Based on whether the submitted answer is correct, we publish 'Turn Taken' or 'Turn Rejected' events and write the respective event to the event stream. Don’t worry if it seems overly complex; greater complexity often correlates with impressing your audience.
00:04:39.139 Moreover, there’s a semblance of optimistic locking at play. When we fetch the entity from the store, we obtain a version and must account for it when putting it back in. In interviews, highlighting your understanding of mechanical details such as optimistic concurrency can be advantageous.
00:04:55.380 Having demonstrated an entity without a persistence layer and how it fetches from the store, we also need to address what happens with the events we publish. This is often achieved through event sourcing, where the state of our entities is tracked. We're working with something called a projection—let me explain how this works.
00:05:12.740 Imagine a projection being actuated by a newly formed game entity. It reads all published events pertaining to that game in the past and applies each event to the entity to maintain accurate state. For example, we set the game ID and retrieve timestamps, which are packaged as ISO 8601 strings. Knowledge of these standards can really impress during an interview.
00:05:23.700 As we take the turn—an event advancing our game state—we’ll set the timestamp when the game ends. Now let's touch on event handlers, which will closely resemble command handlers since both essentially manage messages.
00:05:37.420 In our event handler, we handle the 'Turn Taken' messages by querying the game entity for victory conditions. After verifying if a game has met its exit criteria—like reaching a hundred turns or the game already ending—we will write a 'Game Ended' event.
00:05:52.059 To summarize, we handle every 'Turn Taken' event, but we only publish the 'Game Ended' event when the criteria are met. On the other hand, if a 'Turn Rejected' event occurs, it will end the game abruptly per the established rules.
00:06:02.380 Now let’s proceed to a live coding demonstration, a potentially embarrassing experience, which I’m more than willing to embrace.
00:06:11.410 On the top pane, I have the FizzBuzz service running, while the bottom pane shows an interactive IRB console where I can submit commands using helper methods that will issue command messages for me. The game hasn’t started yet, so I’m going to provide the first value.
00:06:27.430 Does anyone want to wager what the next correct answer is? Is it 'Buzz'? No, it’s not; it’s 'Fizz'. There we go—that’s our demo.
00:06:33.310 Thank you, but wait—there's more! I've discussed the fallacies in distributed systems, emphasizing that messages may arrive out of order or multiple times. None of the code demonstrated is inherently equipped to handle these scenarios.
00:06:47.260 What this suggests is that I haven’t built idempotence into the turn-taking system. Thus, if we issue a 'Take Turn' command, it could be processed multiple times, presenting a significant issue.
00:06:59.310 We're now at a crucial point—having aced the interview, the discussion shifts to negotiating your terms where you can introduce important design patterns for system architecture. The first is the Idempotence Key pattern, which I'm very passionate about.
00:07:15.330 Next, we'll discuss the Reservation pattern, followed by the Sequence Number pattern. We'll seamlessly fold these into the previously established code, and it’s going to be simple and effective.
00:07:30.550 At this point, Nathan becomes involved in what could be an amusing and somewhat contentious exchange regarding the practicality and realities of microservices.
00:07:41.040 Nathan questions Scott, expressing skepticism about the validity of his statements regarding microservices architecture and language flexibility, noting how teams often fall into routines tied to their programming languages.
00:08:01.210 Their conversation continues, touching on why microservices shouldn’t be restricted to particular languages and the nuances of APIs versus messages in effective service autonomy.
00:08:15.850 Nathan raises a point about the common association of microservices with HTTP APIs, which Scott addresses by emphasizing autonomy and the importance of understanding the underlying mechanisms of distributed systems.
00:08:29.280 Scott explains that while microservices can have web APIs, a focus on understanding phenomena like idempotence is crucial for avoiding pitfalls leading to distributed monoliths—situations where the systems become overly intertwined, creating additional challenges.
00:08:59.180 The dialogue between Nathan and Scott outlines the complexity of distributed systems and their architecture across various programming environments, ultimately leading into a mention of an upcoming workshop on distributed systems theory and practice.
00:09:13.600 Scott shares details of the workshop, providing a discount code for attendees, emphasizing the extensive knowledge they'll gain regarding building scalable and autonomous systems with Ruby.
00:09:27.540 The session concludes with thanks to the audience and applause, laboring the importance of continuous learning and collaborative development practices in understanding microservices and distributed systems in depth.