Building a 1v1 Game Matchmaking Service in Go

This article is Part 1 of a series.

Part 2 – Turning the Domain Model into Code

A 1v1 Game Matchmaking Service is a service that pairs up opponents for a 2 player competitive game, like Chess. Let’s build one!

Context

First let’s talk about where this component fits into a broader context. A 1v1 Game Matchmaking Service is one component of a 1v1 Game Playing Ecosystem. In this ecosystem, there will be other components responsible for keeping track of all the current games and their states, keeping track of game history, and keeping track of player statistics. This component has a single responsibility: match two players together.

Domain Modeling

Before we start writing any code, we need to understand the problem domain. We’re going to put ourselves in the User’s shoes and walk through our interactions and our expectations for the behavior of the system.

Here’s our story:

I want to play a game. So I open the game and click New Game. Ideally, an opponent is quickly found and it just starts up and I’m in it. If it takes a bit longer, I get feedback on my screen telling me that it’s searching for an opponent for me to play against. If I get tired of waiting, I can click Cancel to stop trying to start a new game.

That’s about it. The behavior of the system is simple. There are additional behaviors we could include, like displaying an estimated wait time while the system is searching for an opponent, or letting the user search for certain kinds of opponents. We’ll come back to those later. Right now, we want to get a basic, working system in place that we can easily modify.

We need to turn our story into something more structured that we can use to build our software. We extract the actions and the feedback.

# User Actions
- Join New Game
- Cancel Joining Game

# Feedback
- Game Started
- Searching for Opponent
- Cancelled Searching For Opponent

Actions will become requests, and feedback will become responses. We will need additional data to do anything useful. Most importantly, how do we know who wants to do any of these things? We need some kind of Id for the user. We also want the language to line up nicely in our system. From the User’s perspective, they are playing a game. From the system’s perspective, it is making matches between players. We also need feedback from the Service when it is unable to process an action. Let’s add a bit more to our model.

# User Actions
- Find Match
  + Player Id
- Cancel Finding Match
  + Player Id

# Feedback
- Match Made
  + Player 1 Id
  + Player 2 Id
- Searching for Opponent
- Cancelled Searching For Opponent

Before we move on, take note: when you are modeling, stay focused on the interactive experience. What can I do to the system, and what does the system tell me? Notice we didn’t create a User type or a Player type or a Match type or any other types to hold bags of data. We may create those structures later, but they have nothing to do with the high level system behavior from the User’s perspective.

So far we’ve captured a single User’s experience. But a 1v1 Matchmaking Service involves more than just a single User. It’s only interesting with more than one User. The Matchmaking Service performs a service for multiple parties. We need to model the interactions between multiple Users and the Service. We need to understand who talks to who and what they say to each other. In the process, we will start to uncover the business decisions we have to make in order to satisfy the required User experience.

First off, the interactions start from the User. In other words, the Matchmaking Service sits idly by, waiting for a User to ask it to do something. Let’s start with the simplest case, 3 participants. Player 1, Player 2, and the Matchmaking Service.

# Scenario 1
- Player 1 sends Find Match message to Matchmaking Service
- Matchmaking Service sends Searching For Opponent message to Player 1
- Player 2 sends Find Match message to Matchmaking Service
# Now what happens?

We’ve found our first important business decision. You only need 2 players to make a match. We have 2 players. So we’ve made a match right? That is one way we could do it. That would be using a first come, first served policy to make matches. And it would be fast. But that may not make good matches. You may get a fantastic player against a terrible player. You may get the same 2 players matched up repeatedly because they both queue up at the same time after finishing their game together. In our initial User story, we didn’t capture any of that. Is it important to the User that they be matched up against someone with similar skill? Or that they play a variety of opponents? It’s hard to say at this point, and different Users will likely have different preferences. So what should we do?

We procrastinate. Yes, really. We have a decision with a lot of uncertainty and no data to support any decisions. We’re going to note that this is an important point in our system. We’re going to choose to do the simplest thing to get the system running and collect data so that we can make informed decisions about how to change the behavior in the future. When we build this part of the code, we’re going to intentionally make it easy to change the behavior with dependency injection and parameterization.

So, yes, we’ve made a match. We’re going to go with a first come, first served policy for matchmaking, and revisit other policies in future iterations.

Again, we’ll note this and defer the decision. For now, we will assume the Service can send a message to the Player. Let’s revisit Scenario 1 now that we’ve made some decisions.

# Scenario 1
- Player 1 sends Find Match message to Matchmaking Service
- Matchmaking Service sends Searching For Opponent message to Player 1
- Player 2 sends Find Match message to Matchmaking Service
- Matchmaking Service sends Match Made message to Player 2
- Matchmaking Service sends Match Made message to Player 1

We can fill in some more scenarios:

# Scenario 2
- Player 1 sends Find Match message to Matchmaking Service
- Matchmaking Service sends Searching For Opponent message to Player 1
- Player 1 sends Cancel Finding Match message to Matchmaking Service
- Matchmaking Service sends Cancelled Searching for Opponent message to    
Player 1
- Player 2 sends Find Match message to Matchmaking Service
Matchmaking Service sends Searching For Opponent message to Player 2

And… that’s it, really. There are other non-interesting scenarios like…

# Player cancels Finding a Match before they try to Find a Match
- Player 1 sends Cancel Finding Match message to Matchmaking Service
- Matchmaking Service sends Cancelled Searching for Opponent message to Player 1

…but those are basically no-op calls. The Player wasn’t looking for a match, and they still aren’t.

Next

At this point we have enough to start building something. In the next part we’ll turn our model into code.

Onwards to Part 2.