I'm working on a Go application that utilizes gqlgen (GraphQL code gen) to communicate with the client, and MongoDB as the backend database. For our GraphQL schema, we have several types of games (GameA, GameB, GameC, etc.) which all implement a base Game type. The client requests a game lobby (which has a Game property) and in return gets whatever game is currently being played as part of the query as a whole. gqlgen translates this schema into a Game interface which has a single required function: IsGame() to associate the various types of games with the single interface.
//models.go
/* ... */
// Define the Game interface
type Game interface {
IsGame()
}
// Define GameA
type GameA struct {
ID primitive.ObjectID `json:"id" bson:"_id"`
Score []int `json:"score"`
Players []Player `json:"players"`
}
// GameA implements the Game interface
func (g GameA) IsGame() {}
/* ... */
The above code works as intended as far as the GraphQL server and client communication go. The problem comes when trying to read or write a Game to Mongo, since we have no way of knowing the type of game ahead of time:
//db.go
/* ... */
// SetGame takes a game model, game ID, and an insert switch to update a game in the db.
// If insert is true and a game with a matching ID does not already exist, a new
// game will be created.
func (c *Client) SetGame(game *models.Game, id primitive.ObjectID, insert bool) error {
d, err := bson.Marshal(game)
if err != nil {
return err
}
res, err := c.GameCollection.ReplaceOne(
context.TODO(), bson.M{"_id": id}, d,
options.Replace().SetUpsert(insert),
)
if err != nil {
return err
}
if res.UpsertedCount == 0 && res.MatchedCount == 0 {
return fmt.Errorf("no games with ID %s found.", id.Hex())
}
return nil
}
// GetGame accepts an ID (primitive.ObjectID) and
// returns the game it discovers.
func (c *Client) GetGame(id primitive.ObjectID) (*models.Game, error) {
var result *models.Game // This doesn't work obviously, since we don't know what type of game it is
err := c.GameCollection.FindOne(
context.TODO(), bson.M{"_id": id},
).Decode(&result)
return result, err
}
/* ... */
In theory, SetGame(...) should work as intended I believe, however GetGame(...) will not since .Decode() requires a struct to empty the results into. Without knowing the game type ahead of time, there's no way to properly decode the result of the FindOne(...) function. This brings me to my question:
Short of making several GetGameX(...) functions for each type of game (which will increase over time, so trying to reduce the amount of coding overhead), is there any way to have functions that support the polymorphic properties of both GraphQL and Mongo? Or if not, are there any better solutions that don't require making lots of unnecessary "duplicate" code?