-
Notifications
You must be signed in to change notification settings - Fork 222
feat: DataStore and API Lazy Loading with Custom Selection Set #2583
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
feat: DataStore and API Lazy Loading with Custom Selection Set #2583
Conversation
4c16841
to
24e7e32
Compare
AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/AWSDataStorePlugin.swift
Outdated
Show resolved
Hide resolved
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.
Some initial comments, I'm still going through it so... more to come!
AmplifyPlugins/API/Sources/AWSAPIPlugin/Core/AppSyncListProvider.swift
Outdated
Show resolved
Hide resolved
AmplifyPlugins/API/Sources/AWSAPIPlugin/Core/AppSyncModelDecoder.swift
Outdated
Show resolved
Hide resolved
AmplifyPlugins/API/Sources/AWSAPIPlugin/Core/AppSyncModelDecoder.swift
Outdated
Show resolved
Hide resolved
AmplifyPlugins/API/Sources/AWSAPIPlugin/Core/AppSyncModelProvider.swift
Outdated
Show resolved
Hide resolved
AmplifyPlugins/API/Sources/AWSAPIPlugin/Support/Utils/GraphQLRequest+toListQuery.swift
Outdated
Show resolved
Hide resolved
AmplifyPlugins/API/Sources/AWSAPIPlugin/Support/Utils/GraphQLRequest+toListQuery.swift
Outdated
Show resolved
Hide resolved
Amplify/Categories/DataStore/Model/Internal/ModelProvider.swift
Outdated
Show resolved
Hide resolved
|
||
/// A Codable struct to hold key value pairs representing the identifier's field name and value. | ||
/// Useful for maintaining order for key-value pairs when used as an Array type. | ||
public struct LazyReferenceIdentifier: Codable { |
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 custom key/value pair required? Is [String: String]
not compatible with Codable
?
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.
I don't believe the swift dictionary of String is codable, but I can check again to see if there are alternatives to this implementation
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.
[String: String]
does indeed conform to Codable
. Are we doing key based lookups on an [LazyReferenceIdentifier]
anywhere? Do we need to support (or explicitly disallow) duplicate keys?
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.
ah, the reason was the need for an ordered dictionary, that's why it's array of codable structs right now. We are not doing key based lookups anywhere since
- datastore model provider extracts a single item from the array and uses that as the identifier
- api model provider requires the ordered list of identifiers, it translates it to the GraphQL request in
ModelIdDecorator
AmplifyPlugins/API/Sources/AWSAPIPlugin/Core/AppSyncModelDecoder.swift
Outdated
Show resolved
Hide resolved
Amplify/Categories/DataStore/Model/Internal/Schema/ModelField+Association.swift
Outdated
Show resolved
Hide resolved
Amplify/Categories/DataStore/Model/Lazy/DefaultModelProvider.swift
Outdated
Show resolved
Hide resolved
|
||
/// A Codable struct to hold key value pairs representing the identifier's field name and value. | ||
/// Useful for maintaining order for key-value pairs when used as an Array type. | ||
public struct LazyReferenceIdentifier: Codable { |
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.
[String: String]
does indeed conform to Codable
. Are we doing key based lookups on an [LazyReferenceIdentifier]
anywhere? Do we need to support (or explicitly disallow) duplicate keys?
AmplifyPlugins/API/Sources/AWSAPIPlugin/Core/AppSyncListProvider.swift
Outdated
Show resolved
Hide resolved
Error HandlingLazy
With the launch of Amplify
The Async API removed the requirement to explicitly define the
For a major version bump, we can change the behavior of the plugin providers
Ref Issue: #2654 |
…ren, more API CommentPost schema integ tests, fix AWSPluginsCore unit tests
- remove DataError - return API or DataStore error from respective plugin model providers - return CoreError from core implementation
- rename shouldDecode to decode - use _ prefix for public but intended internal
5ad7375
to
50d2981
Compare
/// A related operation performed on `List` resulted in an error. | ||
case listOperation(ErrorDescription, RecoverySuggestion, Error? = nil) | ||
|
||
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.
formatting nit: leading whitespace
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.
Thanks for reviewing again, as this PR is getting pretty large, i'll address this in the next PR with a couple of other things as well
I'm not sure if I can respond on this thread but is there any update to this in regards to the main. Currently this branch is the only one that I can get @auth owner to work on both physical device and ios simulator (M1). When trying to use amplify 2.0.0, updates do not work. |
Sorry it worked for a few times then stopped. It does work when I put @auth private for both the physical device and ios simulator (M1). |
Hi @lostdawg3, thanks for trying out DataStore! Feel free to provide us more information in a GitHub Issue . Your schema will be helpful and steps you took to reproduce the issue. That way we'll have a dedicated thread to dig deeper into issues you are experiencing. If there's something that's working on this branch but not on the current |
* adds selection set customization to api category * fix selection set indent logic * lazy load all commits * Add PrimaryKeyOnly flag to GraphQLRequestBuilders * API - Add initial PostComment4V2 tests * API progress on PostComment4V2 tests * API update and delete tests for PostComment4V2 tests * cancel subscription in subscription tests * Adding copy of DataStore tests and make buildable * updated codegen for postcomment and minor testSave tests * Setting up ProjectTeam codegen schemas and running initial test * API LL3 LL4 testing * API LL5 LL6 testing * API LL2 testing * disable unfinished tests * Update DataStore integ models with modePath * fix unit tests in API and DataStore * test DS integration tests * migrate LazyModel to LazyReference * code clean up * code clean up * Update Amplify-Package.xcscheme * add geo scheme * code clean up * address PR comments 1 - remove duplicate state enum, move primaryKeysOnly check within decorator * fix primaryKey parameter * add L12 codegenerated files * API testing LL12 * DS LL12 test placeholder * enable reduced selection set for DataStore for all operations * disable reduced selection set for DataStore mutations * disable reduced selection set for DataStore mutations 2 * remove print statments * add typename from client side for DataStore MutationSync<AnyModel> * add _deleted to new subscription selection sets only for nested children, more API CommentPost schema integ tests, fix AWSPluginsCore unit tests * fix API integ tests for ProjectTeam 5/6 * optimize list and lazy reference decoding logic * remove public loadingStrategy configuration * fix race case for plugin resets wrt the ModelRegistry being reset * update models and fix tests * test stability - set up datastore only for selection set tests * reduce logging in Decorator * test stability - reduce syncMaxRecords to 100 * test stability - clearOnTearDown false for all tests * API integ tests - subscribe team project LL8/9 * API integ tests - refactor LL112 into 3 separate test classes * PhoneCall testing * fix error handling - remove DataError - return API or DataStore error from respective plugin model providers - return CoreError from core implementation * code clean up - rename shouldDecode to decode - use _ prefix for public but intended internal Co-authored-by: Daniel Rochetti <[email protected]>
* adds selection set customization to api category * fix selection set indent logic * lazy load all commits * Add PrimaryKeyOnly flag to GraphQLRequestBuilders * API - Add initial PostComment4V2 tests * API progress on PostComment4V2 tests * API update and delete tests for PostComment4V2 tests * cancel subscription in subscription tests * Adding copy of DataStore tests and make buildable * updated codegen for postcomment and minor testSave tests * Setting up ProjectTeam codegen schemas and running initial test * API LL3 LL4 testing * API LL5 LL6 testing * API LL2 testing * disable unfinished tests * Update DataStore integ models with modePath * fix unit tests in API and DataStore * test DS integration tests * migrate LazyModel to LazyReference * code clean up * code clean up * Update Amplify-Package.xcscheme * add geo scheme * code clean up * address PR comments 1 - remove duplicate state enum, move primaryKeysOnly check within decorator * fix primaryKey parameter * add L12 codegenerated files * API testing LL12 * DS LL12 test placeholder * enable reduced selection set for DataStore for all operations * disable reduced selection set for DataStore mutations * disable reduced selection set for DataStore mutations 2 * remove print statments * add typename from client side for DataStore MutationSync<AnyModel> * add _deleted to new subscription selection sets only for nested children, more API CommentPost schema integ tests, fix AWSPluginsCore unit tests * fix API integ tests for ProjectTeam 5/6 * optimize list and lazy reference decoding logic * remove public loadingStrategy configuration * fix race case for plugin resets wrt the ModelRegistry being reset * update models and fix tests * test stability - set up datastore only for selection set tests * reduce logging in Decorator * test stability - reduce syncMaxRecords to 100 * test stability - clearOnTearDown false for all tests * API integ tests - subscribe team project LL8/9 * API integ tests - refactor LL112 into 3 separate test classes * PhoneCall testing * fix error handling - remove DataError - return API or DataStore error from respective plugin model providers - return CoreError from core implementation * code clean up - rename shouldDecode to decode - use _ prefix for public but intended internal Co-authored-by: Daniel Rochetti <[email protected]>
* adds selection set customization to api category * fix selection set indent logic * lazy load all commits * Add PrimaryKeyOnly flag to GraphQLRequestBuilders * API - Add initial PostComment4V2 tests * API progress on PostComment4V2 tests * API update and delete tests for PostComment4V2 tests * cancel subscription in subscription tests * Adding copy of DataStore tests and make buildable * updated codegen for postcomment and minor testSave tests * Setting up ProjectTeam codegen schemas and running initial test * API LL3 LL4 testing * API LL5 LL6 testing * API LL2 testing * disable unfinished tests * Update DataStore integ models with modePath * fix unit tests in API and DataStore * test DS integration tests * migrate LazyModel to LazyReference * code clean up * code clean up * Update Amplify-Package.xcscheme * add geo scheme * code clean up * address PR comments 1 - remove duplicate state enum, move primaryKeysOnly check within decorator * fix primaryKey parameter * add L12 codegenerated files * API testing LL12 * DS LL12 test placeholder * enable reduced selection set for DataStore for all operations * disable reduced selection set for DataStore mutations * disable reduced selection set for DataStore mutations 2 * remove print statments * add typename from client side for DataStore MutationSync<AnyModel> * add _deleted to new subscription selection sets only for nested children, more API CommentPost schema integ tests, fix AWSPluginsCore unit tests * fix API integ tests for ProjectTeam 5/6 * optimize list and lazy reference decoding logic * remove public loadingStrategy configuration * fix race case for plugin resets wrt the ModelRegistry being reset * update models and fix tests * test stability - set up datastore only for selection set tests * reduce logging in Decorator * test stability - reduce syncMaxRecords to 100 * test stability - clearOnTearDown false for all tests * API integ tests - subscribe team project LL8/9 * API integ tests - refactor LL112 into 3 separate test classes * PhoneCall testing * fix error handling - remove DataError - return API or DataStore error from respective plugin model providers - return CoreError from core implementation * code clean up - rename shouldDecode to decode - use _ prefix for public but intended internal Co-authored-by: Daniel Rochetti <[email protected]>
Issue #, if available:
Table of Contents
Instructions for installing this PR
amplify-dev
to generate the models used by this PRdata-dev-preview
Description of changes
This PR introduces the LazyReference type and plugin implementations to extend scenarios in API and DataStore Model decoding. Without this PR and related codegen changes, a belongs-to/has-one association of a model must exist when it is a required association and must be in the data payload for it to be decoded successfully into the Model with that association. For example, a Comment belongs-to a Post, when retrieving the Comment, the data payload being decoded must include the Post because the model type defintion of the Comment includes the Post as a reference:
The data payload must include post for it to decode, (shown as json below):
This is known as eager loading the associated data. All retrievals of the Comment requires that the post data is retrieved at the same time as well- in DataStore, this is done using a SQL join statement. In API plugin, this is specified in a nested selection set containing the post fields.
This PR introduces the LazyReference type. The post reference in the Comment type is replaced with an instance of
LazyReference<Post>
.The computed property
post
is backed by the internal_post
of type LazyReference. The computed property allows the developer to make an async call to lazy load the post. Alternative solutions to having the computed property backed by the internal property is to have a property wrapper instead. The main disadvantage of the property wrapper is the inability to perform an async operations, like how we are achieving it with the async throws getter of the computed property.With the computed property and internal property, the developer call pattern becomes:
When the association is required, the underlying API used is
_post.require()
, if the association is optional, then_post.get()
._post.require()
will throw if the model cannot be loaded, whileget()
will returnnil
. The LazyReference type allows the data payloads to include all fields (eager loaded) or include only the primary keys of post (lazy loaded), used for decoding to the type into one of the two states.When a LazyReference instance is created during data decoding, a model provider is created using the registered plugin's model provider registry.
When using API plugin:
When using DataStore plugin:
The LazyReference instance delegates its lazy loading capabilities to the underlying plugin's model provider, which performs requests to its data source (either SQLite or AppSync directly)
For example, API plugin will make a GraphQL get request using the model schema and identifiers of the model to retrieve the entire model and transition the LazyReference to a loaded state.
The selection set created above includes only the primary keys of the post when using the latest codegen that includes the
rootPath
, to control the selection set customizations from @drochetti's PR #2526. This is a pre-requiste PR to allow selection set customizations to create "not loaded" LazyReference instances.DataStore will perform a SQL select operation.
The plugin will manipulate the data payload to always add the model's identifier as metadata to the last level of the model graph. When dealing with has-many associations, it will add the target's associated identifier (comments hold the post identifier as metadata). When dealing with the belongs-to, it will add ithe target's identifier (post will hold the post's identifier). The added metadata gets decoded to a LazyReference and List types to hold the metadata in its model provider. This allows the developer to tranverse the model graph indefinitely using the existing lazy List implementation and the new LazyReference implementation, regardless of the initial loaded state of the model.
Blog-Post-Comment example
AppSync LazyReference metadata
AppSyncModelProvider's idenifier data is an ordered dictionary of its identifiers, the ordering matters to specify the
ID!
type andString!
types when making the AppSync get request. For default identifier, this is["id":"idValue"]
. For a composite key, with a renamed primary key for example, will be["postId":"postIdValue", "title":"titleValue"]
DataStore LazyReference metadata
DataStoreModelProvider's identifier is the parent object's foreign key value. The column name is either the single column name like "id" or "postId" or for composite keys, it is called
@@primaryKey
.Codegen Changes
PR with related codegen changes aws-amplify/amplify-codegen#504
Selection Set changes
__typename
is injected at runtime from the request to the response that allows response payloads missing the __typename to be decoded successfully.Developer Experience / Migration Guide
aws-amplify/docs#4934
Issues
Use case: Has-One Bidirectional belongs to
See #504 (comment)
See #505 (comment)
Use case: Cross platform DataStore events (Android)
DataStore on Android syncs data to AppSync using a selection set in the mutation to AppSync differently from iOS. For the existing issue, see x. The problem is that there is not enough data in the subscription event, due to Android's selection set missing fields that are required for iOS DataStore. For example, in the Comment Post example, Android may sync a comment with the selection set
This will fail to decode to iOS without the LazyReference codegen changes because the swift model type Post that is a field on the Comment may have required fields. With the LazyReference encapsulation, the post field on the Comment will be decoded as a "not loaded" Post with the identifiers. In DataStore's reconciliation logic, it only needs to extract the identifiers of the Post from the comment and store that as the FK values in the Comment table. It doesn't need the entire Post object.
See #1753 (comment)
Use case: Cross platform DataStore events (Studio/JS)
Studio/JS sends a mutation events with a selection set of this structure:
__typename
_deleted
,_lastChangedAt
,_version
id
_deleted
These events are successfully reconciled by the iOS client using this implementation because
__typename
is injected at runtime by DataStore to decode successfully to the AnyModel type used by DataStore. The author and book in this example is codegenerated to be encapsulated by the LazyReference type and decodes to the "not loaded" states. Their identifiers are retrieved as the FK for saving the bookAuthor join modelSee #1601 (comment)
Check points: (check or cross out if not relevant)
DataStore checkpoints (check when completed)
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.