-
Notifications
You must be signed in to change notification settings - Fork 721
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
SQLite persistent normalized cache #79
SQLite persistent normalized cache #79
Conversation
@kompfner: Thank you for submitting a pull request! Before we can merge it, you'll need to sign the Meteor Contributor Agreement here: https://contribute.meteor.com/ |
fc82f3e
to
961477e
Compare
…ill depend on Sqlite pod)
…framework directory by the time StarWarsAPI generate API build phase is run
…l] instead of []. This is an odd behavior, but is what is currently required by ApolloStore. This should probably be revisited.
…ume a specific type of cache
…is cached in a persistent way
…ached by the time the fetch’s completion block was called
But SQLite isn't lightweight. It requires a new dependency, new xcodeproj, and adds more overhead if you're not a CocoaPods user. Lunar doesn't. It literally just links CoreData. Not to mention it pretty much is this SQLiteNormalizedCache, just with DB transactions expressed in CoreData logic, not SQLite. If you want to use Apollo and Lunar, you add this to your
It will clone Lunar, clone Apollo, and compile both to (Side note: I initially brought this up and started development on Lunar because I wanted to use our already existing model framework. After exploring the code shared here, I realized that I don't need that dependency anymore, because Lunar now has a single entity, LunarRecord, which provides two properties: "key" and "record".) I'm not happy, as a contributor, a maintainer of my own cache (that literally does the same thing without the dependency), and more specifically, as a Carthage user, that we're trending towards including something because it was PR'd first and is easy to maintain if you use CocoaPods. My dependency build time is going to go up because I now have to build Apollo, Lunar, and SQLite (which I don't use), even though I cut my local dependencies out (no more model or sync frameworks). I share the general sentiment here that we should be able to get people up and running with persistence quickly, and that's why I'm still proposing that we just provide install instructions with how to include whatever specific community-maintained cache. It's really not beyond our audience to have to make some choices on their own backend and maybe, if there's no community support, help support the community and write some code. |
Apollo's beauty isn't even in it's caching, nor should it concern itself with persistence. Apollo's beauty is that it was a lightweight interface that could live on top of your network and persistence layer without being opinionated about either. This takes a step away from that, and starts to add weight and overhead, absolutely. |
At this point, I've said all there is to say. If you guys are set on merging this in and forcing SQLite on Carthage users, than there's nothing I can do. I'm just trying to share what I've learned in working with Moya and what I'm trying to use Apollo for. SQLite has nothing to do with Apollo, IMO, and should not be included in the repository. |
…ping JSONSerializationFormat is meant to convert from/to GraphQL types, but the fields in a record already contain JSON values or Reference.
@kompfner: I cleaned up some code related to promises and the fields serialization. We no longer need to special case |
reject(error) | ||
} | ||
} | ||
return Promise { try mergeRecords(records: records) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, sweet, your Promise
implementation automatically turns thrown errors into rejections. 👍
@@ -117,14 +117,13 @@ extension Dictionary: JSONEncodable { | |||
public var jsonObject: JSONObject { | |||
var jsonObject = JSONObject(minimumCapacity: count) | |||
for (key, value) in self { | |||
print("key: \(key), \(type(of: key))") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should these be removed?
let recordJSON = try SQLiteSerialization.deserialize(data: recordData) | ||
return Record(key: row[key], recordJSON) | ||
let fields = try SQLiteSerialization.deserialize(data: recordData) | ||
return Record(key: row[key], fields) | ||
} | ||
} | ||
|
||
private let serializedReferenceKey = "reference" | ||
|
||
final class SQLiteSerialization { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, I think this should probably be private.
} | ||
|
||
private static func deserialize(valueJSON: Any) throws -> Any { | ||
switch valueJSON { | ||
private static func deserialize(jsonValue: JSONValue) throws -> Record.Value { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about this for the naming of the serialize
and deserialize
method parameters, for even greater clarity?
serialize(fieldValue:)
serialize(fieldJSON:)
var objectToSerialize = JSONObject() | ||
for (key, value) in fields { | ||
objectToSerialize[key] = try serialize(value: value) | ||
} | ||
return try JSONSerializationFormat.serialize(value: objectToSerialize) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The cleanup in this commit is great! Perhaps JSONSerializationFormat
should have a more descriptive name, to make its purpose more distinct than the system-provided JSONSerialization
?
@@ -88,7 +88,7 @@ public final class SQLiteNormalizedCache: NormalizedCache { | |||
} | |||
} | |||
|
|||
private let serializedReferenceKey = "reference" | |||
private let serializedReferenceKey = "$reference" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this really necessary? What distinguishes a reference field from any other field is that it's a dictionary (of the form [reference: <key>]
), whereas other fields are primitive types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, you're right, I wasn't thinking clearly, there are no nested objects in records of course. Although we may want to leave open the possibility of storing JSON in a field if something like that becomes part of the GraphQL spec? May be too forward looking.
@martijnwalraven I'm going to do a tiny bit more cleanup based on the comments I left on your last few commits. Please let me know if you agree with those. Those commits were really nice improvements, btw! |
f70b390
to
9f9d17f
Compare
… debuggin CI-only crash
I think the reason the ApolloSQLite unit tests were failing was due to race conditions around multiple tests attempting to open the same file on disk. My fix was to have each test open a SQLite file at a different file URL. |
…rpose of debuggin CI-only crash" This reverts commit 28feedf.
…eem to be available on Travis
Disabling iOS 9.3 unit tests, since that simulator doesn't seem to be an available option on Travis right now. |
This PR does the following:
SqliteNormalizedCache
, which persists records in a SQLite database.SqliteNormalizedCache
subspec, which is necessary for two reasons:SQLite.swift
.SqliteNormalizedCache
usage on tvOS projects, which don’t have access to the file system.InMemoryNormalizedCache
andSqliteNormalizedCache
.SqliteNormalizedCache
.Known remaining work before this PR can be merged:
loadRecords(forKeys:)
, implement correct expected behavior of returningnil
for keys that don't have corresponding records (instead of the misunderstood expected behavior described in the comment)Next steps: