-
-
Notifications
You must be signed in to change notification settings - Fork 11
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
Lazy loading after draft 15 #108
Comments
This is tough one and I definitely think worth discussing about it. Because of Vue's reactivity nature, lazy loading will cause quite a huge n+1 problem in the app. For example, if you call If you have only few items, I think performance wouldn't get affected. But it could lead to issues which is also very tough to debug. One thing I can think of is to create composable function that handles this situation. Let's say you have a component that requires passed in users to always have import { computed } from 'vue'
import { useStore } from 'vuex'
import User from '@/models/User'
export function useUsersWithPosts(users) {
const store = useStore()
const userRepo = store.$repo(User)
// We should load data newly since `load` method
// is going to mutate the given objects.
const usersWithPosts = computed(() => {
const items = userRepo.revive(users)
userRepo.with('posts').load(items)
return items
})
return {
users: usersWithPosts
}
} Then inside your component, you could this function like: export default {
props: {
users: { type: Array, required: true }
},
setup(props) {
const { users } = useUsersWithPosts(computed(() => props.users))
// Now `users` have posts relation.
return {
users
}
}
} It doesn't look that intuitive, but this is what it should happen. We should always load relationship as minimal as possible. |
Thank you for the reaction. This would be a solution that could lazy load relations, however I feel this should be functionality that belongs to the model and should not be placed in a separate structure using the composition API. Thinking more deeply about our use-case for lazy loading relationships, while prototyping an app that uses models with relations nesting deep until 10 levels down, we pass on those models and relations to underlying Vue components. While prototyping a few levels deep, you keep switching to the root where all relations should be loaded and the components where you are trying out and using the relations, resulting in errors when the relations is not loaded. At this time all (or most) relationships in our project seem to be preloaded in any case due to performance necessities. I will start a refactor in my project today to see whether the last use-cases should/can be preloaded as well. For the prototyping phase of a project feedback on unloaded relations would be very helpful. Perhaps Vuex-orm could give a warning in the console whenever a relation is being called that has not yet been loaded? |
I feel the biggest concern for most ORM-esque libraries is that lazy-loading depends entirely on the design at the domain model level. That is to say, if Active Record (AR) is a design pattern at its core (which Vuex ORM no longer is) then all your data source logic is driven at the model level too – making it possible to communicate directly with data sources. AR also has its pitfalls, particularly when it comes to dependency injection on SSR. We found the biggest suffering to be at the hands of Nuxt users. Thus, the Repository pattern fast became a likely candidate to adopt for the official v1 release. The additional drawback of implementing lazy-loading at the library level is memory consumption. With the Repository pattern, your data source logic is decoupled from models and models are essentially data carriers, so any reference to relations will be held in memory since the pattern requires Repo-to-Model data allocation/mapping.
If you're not concerned about SSR compatibility, you can still instantiate repositories from models and provide local functionality to communicate with them for data mapping. For example: // @/models/Model.js
import { Model as BaseModel } from '@vuex-orm/core'
import { store } from 'path/to/store'
export class Model extends BaseModel {
$repo(model) {
return store.$repo(model ?? this.$self())
}
} // @/models/User.js
import { Model } from './Model'
export class User extends Model {
get lazyPosts() {
if (this.posts === undefined) {
return this.$repo().with('posts').load([this])
}
return this.posts
}
} While this is an over-simplistic workaround, I would recommend @kiaking's approach, which supports the purpose of separating, clearly and in one direction, the dependency between the work domain and the data allocation or mapping.
See #96 |
Thank you both Kia and Cue for your answers and insights. I have refeactored our project to not use lazy-loading at all but to preload where needed. While refactoring I found 1 or 2 use-cases where relations were not yet pre-loaded, wich (after refactoring) caused an improvement in performance. For now I could not find a reason to need support for lazy-loading. #96 however would be a very nice addition! (Thanks for the refferal!) |
For some use-cases in our apps we need to use a lot of relations. For some of those cases we'd like the guarantee of having the relations loaded and made use of a way of lazy loading those relations.
We are aware that lazy loading brings a bit of overhead and should be avoided by lots of data, but we do have some usecases were we'd still like the guarantee of having the relation loaded or else loading.
How should we deal with these types of situations?
Will this be suported (in the future)?
The text was updated successfully, but these errors were encountered: