Skip to content

Split ObjectState into utilities and storage implementations #164

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

Merged
merged 10 commits into from
Jan 13, 2016

Conversation

andrewimm
Copy link
Contributor

ObjectState used to both store object data and the perform the functionality to mutate it.
Now, to facilitate different behavior between single-instance mode and unique-instance mode, ObjectState simply contains the mutation behavior, independent of how it's stored.
This PR introduces SingleInstanceState and UniqueInstanceState, storage layers that are directly accessed by ParseObject, and use ObjectState as a utility.

This change is chiefly to prevent the memory leaks reported in #111

Essentially, there are 4 pieces:

  • Isolation of ObjectState into utility functions that have their data sources passed in
  • Implementation of SingleInstanceState and UniqueInstanceState
  • Reimplementation of tests for both of these storage systems
  • Branching of ParseObject logic to interact with one or the other

I'll be adding more tests, but things look good for now.

@andrewimm
Copy link
Contributor Author

Final code piece for #136

@andrewimm
Copy link
Contributor Author

This change also brings ObjectState and the two State implementations to complete code coverage in unit tests.

@andrewimm
Copy link
Contributor Author

Just tried allocating 10MM objects with UniqueInstanceState while profiling node, watched it continually GC and recover memory :D

@roddylindsay
Copy link

woohoo

@TylerBrock
Copy link
Contributor

w00t
On Mon, Jan 11, 2016 at 2:40 PM Roddy Lindsay [email protected]
wrote:

[image: woohoo]
https://cloud.githubusercontent.com/assets/1270126/12248899/47cd82a2-b871-11e5-835b-82a4776697f4.png


Reply to this email directly or view it on GitHub
#164 (comment)
.

@andrewimm
Copy link
Contributor Author

cc @hallucinogen and @wangmengyan95 for review as well

return Object.freeze(
ObjectState.estimateAttributes(this.className, this._getStateIdentifier())
);
let attributes = singleInstance ?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better if we abstract this into InstanceStateController that can be configured by CoreManager.js

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hallucinogen I thought about that too, but they take completely different arguments -- one uses a set of strings as identifiers, the other uses an object. We could pass the Object and introspect the identifiers in most, but not all of the cases

@andrewimm
Copy link
Contributor Author

So, we could probably abstract everything away and make this a controller, but one of the core reasons that ObjectStore exists, and why single-instance mode is addressable by a className + ID mapping, is to eventually publicize APIs that provide functional access over Parse Objects (a la Parse+React) without requiring any new code. Whether we consider the idea of SDK modularization or not, I like to think that it would be possible to build a reasonable lightweight application with just a Store, ParseOp, and RESTController.

I also recognize the value of shared interfaces, so here's what I think we should do. Rather than make the first argument a ParseObject (pulling in unnecessary dependencies), it should be an object that implements the interface { className: string, id: string }. Since a Parse Object implements this interface, we can always pass it as the first argument.

EDIT: actually we can't. Somehow I logic'd that incorrectly on my walk to the bus -- I guess it's actually an example of why I don't necessarily want a constant interface. Need to think about this more.

@andrewimm
Copy link
Contributor Author

Continuing... another possible option is to replace the way we use _getStateIdentifier. Right now it returns the object ID; what if we used it to return the object I mention above instead?

In single instance mode, it returns an object of the shape { className: string, id: string }. In unique instance mode, it returns the ParseObject itself.

This is my favorite approach thusfar

@grantland
Copy link

This is going to be confusing, since the JS SDK is one level deeper than the other SDKs:

  • JS ParseObject
  • JS ObjectState == Android/iOS/.NET ParseObject
  • JS UniqueInstanceState/SingleInstanceState == Android/iOS/.NET ParseObject.State

Does ParseReact publicize ObjectState directly? If not, it'd be great if we can rename things to make it less confusing moving between codebases.

@andrewimm
Copy link
Contributor Author

ObjectState is not documented, so we can rename things. I'd support renaming it, since it's really a set of state modifiers, not the state itself anymore.

@hallucinogen
Copy link

So, we could probably abstract everything away and make this a controller, but one of the core reasons that ObjectStore exists, and why single-instance mode is addressable by a className + ID mapping, is to eventually publicize APIs that provide functional access over Parse Objects (a la Parse+React) without requiring any new code.

It's still possible to eventually publicize APIs that provide functional access over ParseObjects (a.k.a publicize our current ObjectState) while still abstracting the State store. IMO, it's even more powerful if we abstract it as controller (and the API will allow people to build localStorage based State store! #randomidea #dontmindthis).

I agree with passing _getStateIdentifier.

@grantland
Copy link

Maybe ReactObject instead and ObjectState and Single(Instance)ObjectState?

@andrewimm
Copy link
Contributor Author

Object State is now controlled by ObjectStateController, and swappable like any other controller. This should be good for expandability.

Also renamed ObjectState to ObjectStateMutations, since it's now a set of standardized mutation functions, and does not affect any state.

@andrewimm andrewimm mentioned this pull request Jan 12, 2016
5 tasks
export function getServerData(obj: ObjectIdentifier): AttributeMap {
let state = getState(obj);
if (state) {
return state.serverData;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to be consistent in the default return value for all these functions?
return state.serverData || {};
like we did in getState?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain further? Each function's "default state" has a pretty specific meaning that is leveraged in the code. In general, I don't think that's the bit that should be up for review now; those are the things that are remaining the same before & after this diff.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, for your specific example, it's an invariant that state.serverData exists if state exists, check out the typedef at https://github.com/ParsePlatform/Parse-SDK-JS/pull/164/files#diff-5c0499aa286ed5bb8b6f6ad90b87830cR26

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah i missed that - looks like it's all covered then.

@peterdotjs
Copy link
Contributor

Really like the extensibility now with ObjectStateController.

setPendingOp: (obj: ParseObject, attr: string, op: ?Op) => void;
pushPendingState: (obj: ParseObject) => void;
popPendingState: (obj: ParseObject) => OpsMap;
mergeFirstPendingState: (obj: ParseObject) => void;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, you sure this is correct? mergeFirstPendingState of ObjectStateController accepts ParseObject.....

}

_handleSaveError() {
var pending = this._getPendingOps();
ObjectState.mergeFirstPendingState(this.className, this._getStateIdentifier());
let stateController = CoreManager.getObjectStateController();
stateController.mergeFirstPendingState(this._getStateIdentifier());

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... meanwhile in practice, it accepts stateIdentifier....

@hallucinogen
Copy link

Loving it! One question though

@andrewimm
Copy link
Contributor Author

I'll let @hallucinogen give the green light on this one.

@@ -34,10 +33,12 @@ import {
import ParsePromise from './ParsePromise';
import ParseQuery from './ParseQuery';
import ParseRelation from './ParseRelation';
import * as SingleInstanceState from './SingleInstanceState';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope you won't hate me for this. But this should be SingleInstanceStateController. I feel strongly about this since SingleInstanceState sounds like model, while SingleInstanceStateController clearly... a controller.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nah, makes sense, though with every extra word I worry about things getting too Java-y. I'll go find-replace.

AbstractInstanceStateControllerFactory, here we come!

@andrewimm
Copy link
Contributor Author

Boomdone. Really missing phab macros right about now

@@ -207,6 +218,107 @@ module.exports = {
return config['ObjectController'];
},

setObjectStateController(controller: ObjectStateController) {
if (typeof controller.getState !== 'function') {
console.log(controller);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

productioncleanup

@hallucinogen
Copy link

LGTM. One last nits, but accepting anyway.

@andrewimm
Copy link
Contributor Author

tyvm

andrewimm added a commit that referenced this pull request Jan 13, 2016
Split ObjectState into utilities and storage implementations
@andrewimm andrewimm merged commit 26c016d into release-1.7.0 Jan 13, 2016
@andrewimm andrewimm deleted the singleinstancestore branch January 25, 2016 18:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants