-
Notifications
You must be signed in to change notification settings - Fork 742
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Storage #6
Comments
Do we have a server for this already? Furthermore, we should consider creating a "library" of games people have made. |
All the server code lives in src/server. Games aren't persisted yet, but I plan to add support for various backends. |
I'll link to any games people make on the main README page once that happens. We haven't reached 1.0 yet so I would imagine that people will start using this more widely once the API is stable. |
I think writing a connector class for the reducer would be a better idea, so people could add their own |
Yes, this is the idea I had too, to make it extensible so that we're not limited to just the backends that come supported out of the box. |
If you have the |
I was just thinking of adding a DB section to the server constructor:
Provided connectors can be used like:
We could probably first just wrap the existing in-memory object into this signature and build up from there. |
There is a use case which data could be on multiple stores like |
Let's make
where the onAction handlers are called in the order they are defined. |
This is cool and has more benefits |
Is it possible to use |
We could definitely look into redux-saga, but I would prefer to start simple and only use it if necessary. I've created a simple interface for storage with just |
I think Mongo will be simple for start, as Redis is a key-value and working with arrays and objects needs more handlings |
I do think that Mongo would be a bit better for board games as each individual player's "possessions" aren't always similar. |
It doesn't need any database for test. just should be added an interface to let users of framework pass their own async actions in order to fetch state from db. There are challenges like:
|
@saeidalidadi Would you be interested in adding Mongo support? It should be as simple as just implementing another db class that talks to Mongo instead of the in-memory Map. Since this code runs only on the server, there shouldn't be a concern that the reducers are the same on the client and server. |
It would be a pleasure for me to run this task. 👍 |
Any updates on the progress of this. It is a bit disconcerting because at the moment if we redeploy our app we lose game state. |
@jcgertig I haven't heard back from Saeid in a while. Perhaps he's busy. Would you like to take this over? Feel free to add support for your favorite storage system. |
I've added support for overriding the |
Hi @nicolodavis, as you said I am busy, but I find a solution based on this answer from Dan Abramov for persisting the state on Server({
games,
db : {
set: async setById (gameId, gameState) {
// user query
return result;
},
get: async getById (gameId) {
// user query
return gameState;
}
}) |
@saeidalidadi You mean a set / get that talks to Mongo, or are you referring to something else? Not sure I follow. |
They could talk to all the remote stores and the developers would be able to add their own implementation for the |
That's what I thought I implemented in 2721ad4. Is your proposal different? |
They are about the same, so I will add a working code for this feature. |
Ok, I'll take a look when you send a PR. I'm still not exactly sure what your proposal is, but I'm sure it will make more sense when I look at the code :) |
I have implemented the functionalities but have issues with tests in |
Can you throw up a branch? I'd love to check it out and see this progress. Aside: How do y'all feel about Firebase for this? |
Firebase sounds good to me! |
I have crated a simple implementation to persist |
* persist game state inside db * undo some unnecessary changes * fix merge * fix tests * no babel on src/server * reset package-lock.json * remove stage-0 preset
The MongoDB connector is implemented in 003fe46. Need to add some error handling. |
@nicolodavis any plan /ETA on Firebase? |
Nobody is working on Firebase at the moment. Looking for contributors. |
@nicolodavis I would like to add my contribution by working on Firebase. I created a similar class to Mongo for Firebase/Firestore. Is this something you would like to add to the project? /**
* Firebase RDT/Firestore connector.
*/
export class Firebase {
/**
* Creates a new Firebase connector object.
*/
constructor({ config, dbname, cacheSize, mockFirebase }) {
if (cacheSize === undefined) cacheSize = 1000;
if (dbname === undefined) dbname = 'bgio';
// TODO: better handling for possible errors
if (config === undefined) config = {};
this.client = firebase;
// Default engine is Firestore
this.engine = config.engine === 'RTD' ? config.engine : 'Firestore';
this.apiKey = config.apiKey;
this.authDomain = config.authDomain;
this.databaseURL = config.databaseURL;
this.projectId = config.projectId;
this.dbname = dbname;
this.cache = new LRU({ max: cacheSize });
}
/**
* Connect to the instance.
*/
async connect() {
var config = {
apiKey: this.apiKey,
authDomain: this.authDomain,
databaseURL: this.databaseURL,
projectId: this.projectId,
};
this.client.initializeApp(config);
this.db =
this.engine === 'Firestore'
? this.client.firestore()
: this.client.database();
return;
}
/**
* Write the game state.
* @param {string} id - The game id.
* @param {object} store - A game state to persist.
*/
async set(id, state) {
const cacheValue = this.cache.get(id);
if (cacheValue && cacheValue._stateID >= state._stateID) {
return;
}
this.cache.set(id, state);
const col =
this.engine === 'RTD'
? this.db.ref(id)
: this.db.collection(this.dbname).doc(id);
delete state._id;
await col.set(state);
return;
}
/**
* Read the game state.
* @param {string} id - The game id.
* @returns {object} - A game state, or undefined
* if no game is found with this id.
*/
async get(id) {
let cacheValue = this.cache.get(id);
if (cacheValue !== undefined) {
return cacheValue;
}
let col, doc, data;
if (this.engine === 'RTD') {
col = this.db.ref(id);
data = await col.once('value');
doc = data.val();
} else {
col = this.db.collection(this.dbname).doc(id);
data = await col.get();
doc = data.data();
}
let oldStateID = 0;
cacheValue = this.cache.get(id);
/* istanbul ignore next line */
if (cacheValue !== undefined) {
/* istanbul ignore next line */
oldStateID = cacheValue._stateID;
}
let newStateID = -1;
if (doc) {
newStateID = doc._stateID;
}
// Update the cache, but only if the read
// value is newer than the value already in it.
// A race condition might overwrite the
// cache with an older value, so we need this.
if (newStateID >= oldStateID) {
this.cache.set(id, doc);
}
return doc;
}
/**
* Check if a particular game exists.
* @param {string} id - The game id.
* @returns {boolean} - True if a game with this id exists.
*/
async has(id) {
const cacheValue = this.cache.get(id);
if (cacheValue !== undefined) {
return true;
}
let col, data, exists;
if (this.engine === 'RTD') {
col = this.db.ref(id);
data = await col.once('value');
exists = data.exists();
} else {
col = this.db.collection(this.dbname).doc(id);
data = await col.get();
exists = data.exists;
}
return exists;
}
} |
@bennygenel that would be great! |
@nicolodavis I'm planing for SQL integration as we talked but I have one question in mind. Package dependencies are getting pretty big in my opinion. For someone just going to use mongodb having all other db packages downloaded is really unnecessary. After sequelize integration there is going to be 6 packages in total added to the project just for databases. Is there something we can do about this? |
@bennygenel Yes, we can just follow the same package splitting approach that we do for the other stuff. We can split the server side stuff into:
package.json will still contain all the dependencies, but the user will not pull in the firebase dependency into their code if they just use mongo (for example). I think this will involve getting rid of the environment variable approach of initializing the backends, though. |
@nicolodavis i just tried mongo integration and i see it creates separate collection per each single game instance. Also i have no control on names of those collections. Is there some setup i missed or its intended to be like this? |
One collection per game instance was how it was originally designed. We can change it to a single collection (with an additional column for the game instance) and also add some customization over collection name. The Mongo connector is a very small file that you can modify locally and use as a custom DB connector for now: https://github.com/nicolodavis/boardgame.io/blob/master/src/server/db/mongo.js If you have a good experience using a single collection, I'll be happy to accept a PR and merge that in to the current implementation. |
@nicolodavis i will think about collections structures and back to you. |
Guys, any updates on mongodb connector ? |
@skdhayal Nobody is working on it at the moment. |
@skdhayal In case you’re interested in helping out on a Mongo connector, here are some links.
At the moment we’re encouraging community maintainers to create connectors as separate packages as we don’t have the bandwidth to test and maintain connectors for every DB and I’m happy to provide guidance if you think this is something you’d like to work on. Some of the other community connectors might be a useful reference here, e.g. connectors for Firestore or Postgres. |
@skdhayal i wanted to work on it soon. Let me know please if you are going to implement it, or maybe there is some work we can share. |
Chris asked to share my work on MongoDB Storage, so basically I just used the postgresql library code and edited it to work with Mongo.
|
Closing this is as storage is handled by third-party adapters. |
Games are currently held in an in-memory object, but should be persisted in either a MongoDB or Firebase instance. Options for other backends should also be possible.
TODO:
The text was updated successfully, but these errors were encountered: