2

I'm making a game I have a Replay object that represents all the inputs done by a player to finish a level. It looks like that:

class Replay {
    int replayId;
    int playerId;
    int levelId;
    int length;
    List<Input> inputs;
}

I want to be able to search and access replays really fast so I'm storing them in a database. However, I've been told I should not use database to store large binary files. So I'm storing the replayId (primary key), the playerId, the levelId and the length on the database and the full object is serialized in a file named using the primary key. This is all good and I can fully wrap those hacky things into an object that will abstract the loading and saving of Replay objects.

However, designing that object, I'm coming accross case where I do not need to fully load the Replay but simply need the id of player who did it or the time. So I'm ending up making methods that look a bit like this:

interface ReplayService {
    //query to Database and then loading from disk
    Replay getWorldRecord(int levelId) throws LevelNotFoundException, RecordNotSetException; 

    //query from database only and return length
    int getWorldRecordTime(int levelId) throws LevelNotFoundException, RecordNotSetException;

    //query from database only and return playerId
    int getWorldRecordHolder(int levelId) throws LevelNotFoundException, RecordNotSetException;
}

This is the cleanest solution I've found but the problem is the record's time and holder are found together with a single SQL query. That makes the implementation very stupid for the 2 methods returning a single value of that same query. When a user that will need to display the time and holder he will pay the price of 2 queries instead of 1 that returns all the data from database anyway. So I would need a way to group those 2 (or more) fields I want to retrieve at once.

interface ReplayService {
    //query to Database and then loading from disk
    Replay getWorldRecord(int levelId) throws LevelNotFoundException, RecordNotSetException; 

    //query from database only and return all infos without the data
    SomeKindOfType getWorldRecordInfo(int levelId) throws LevelNotFoundException, RecordNotSetException;
}

I've been looking for Pair objects in Java and came accross people explaining why It is bad pratice and any object should have a significant name. I agree with this as even if I don't want to pay for 2 queries but I don't want to obfuscate my code either.

I also really hate Info objects such as ReplayInfo since a valid Replay object should contain his informations and the word Info adds nothing in terms of meaning. It is a bit the same for Metadata. But I'm not sure if I have other better options. Should I create a new type to store the Database part of the object? I hope not since the Database/File hacking thing is part of the implementation and not design. I would like to be able to make a different implementation that stores the data completely differently without the API to feel awkward.

Any idea on how to design a system using incomplete objects for technical reasons but still want to remain clean and easy to understand? Should I just dodge the Info/Metadata object with a generic Pair or Tuple?

1

I may still not understand your issues, but I agree with the desire to avoid FooInfo classes. Would it work for you if:

  1. At construction, Replay reads all "infoish" fields from the database, but leaves the inputs null or empty. Use a standard DB abstraction/library to do the SELECT so that this part of your code is unlikely to change. If you add a new "date" field, or change playerID to a String, it should be simple to update Replay.

  2. Only when the client requests the inputs do you read them from the file. This part of your code is the "custom" part, and your decision to lazy load might change, so this is the one major section of code likely to change. e.g., in some future they might be compressed (or compressed differently), or stored in the cloud instead of local files.

This is the point of using classes as abstractions. The hard decisions are encapsulated in the Replay class, but the API looks simple and the client doesn't know or really care how it works, just that it does. You are following the spirit of the (horribly misnamed and misunderstood) SRP.

Note your getInputs() method probably needs to throw an IOException. This is arguably an example of a "leaky" abstraction, but not all code can be perfect.

0

Assuming these data are stored and never deleted or changed during runtime, you can have the best of both worlds with the following design:

  1. Keep the interface
  2. When writing the concrete implementation, add a private method, perhaps named getWorldRecordInfo, that gets both fields and stores them in a cache. If the cache is already populated, skip the DB call.
  3. Program getWorldRecordTime and getWorldRecordHolder so that they call getWorldRecordInfo.

This way you will only need one database call but you preserve the integrity of your interfaces. It will also improve performance if these data points have to be retrieved repeatedly.

  • Cache is not an option since the database might be accessed by multiple Servers (currently making the server). – Winter Aug 8 '17 at 23:40
  • 1
    Cache is still fine from multiple servers (database clients) if the data is write-once. – John Wu Aug 8 '17 at 23:51
  • Downloading the full replay is unlikely for most players while looking at the time and record holder is very likely and I don't want to store in server's ram all the replay content since the last restart of the server. And I also don't want to design a smart caching system. – Winter Aug 9 '17 at 0:01
  • Under my design, getWorldRecordInfo would get the two fields you want, nothing else. So you would not be caching the replay data, which I agree would be a bad idea. – John Wu Aug 9 '17 at 0:03
  • but what would getWorldRecordInfo return? I call already return all the infos from a single method with a single query. What I want is a way to do it clean, without a silly Info object, if it's possible. The current setup I have is calling the same query in 2 different methods but only returning one field at the time. – Winter Aug 9 '17 at 0:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.