As we know preload & postload for DataObject transforming is important, with this advantages i have implement data loader concept from graphQL. Batch Loader is a factory method for handle all of the keys, required attributes or querying and combine them all into one non-redundant query.
Related site:
Example we have 3 tables, posts, comments and users and we want to get data as below given.
posts: {
id: 1,
title: 'Cool post',
comments: [
{
user: {
id: 2,
username: 'kwtam',
display_name: 'kelvin',
},
comment: 'Wow cool post, thanks sharing',
},
{
user: {
id: 3,
username: 'tplim',
display_name: 'tienping',
},
comment: 'Wow post cool, sharing thanks',
},
{
user: {
id: 4,
username: 'wanglin',
display_name: '',
},
comment: 'post cool thanks, sharing wow',
}
],
postBy: {
id: 1,
username: 'oskang09',
display_name: 'Oska',
},
likedBy: [
{
id: 3,
username: 'tplim',
display_name: 'tienping',
},
{
id: 4,
username: 'wanglin',
display_name: '',
}
]
}
Normallay by populate, or any ORM usually they want to get data like this would used about 9 queries or more, But with BatchLoader we only used 3 queries instead of 9.
BatchLoader taking all of the required keys when preload like posts id 1
and postload taking user id [ 1,2,3,4,4,3 ]
so we must make it to unique keys.
After batch loader factory completely done will build up an action function so mean that developer can choose to run it or not to run it. So in my structure,
when users pass in query ela: ['comments']
will load all data required for comments.
Here an example of mine definition of loadable attributes using AOP (Aspect Oriented Programming) so they have many other checking in hooks like acl, setting exists or anything.
ela: {
before: (context) => {
context._loaders = {
merchant: undefined,
store: undefined
};
// relation many to one and auto build it
context._loaders.store = manyToOne(
context,
{
model: 'store',
pk: 'id'
}
);
context._loaders.merchant = manyToOne(
context,
{
model: 'merchant',
pk: 'id'
}
);
},
joins: {
// calculated fields
ordered: () => async (data) => {
data.ordered = {};
data.cart.forEach(
({ productId, count }) => {
if (!data.ordered[productId]) {
data.ordered[productId] = count;
} else {
data.ordered[productId] += count;
}
}
);
},
// relational fields
store: () => async (data, context) =>
data.store = await context._loaders.store(data.storeId),
merchant: () => async (data, context) =>
data.merchant = await context._loaders.merchant(data.merchantId),
}
},
const {
authorized,
apply_setting,
parameterize_query,
accessible,
apply_id,
access_query,
actions,
image_crud,
instance_limit,
readable,
writable,
ela
} = require('@hooks');
const { and, iff, hasSetting, hasAccount, hasParams, isOneOf } = require('@checkers');
const { disallow } = require('@utils');
module.exports = (extraHooks) => {
const defaultHook = {
before: {
all: [
authorized.before,
parameterize_query.before,
apply_setting.before,
iff(
hasAccount,
iff(
hasSetting('accessible', true),
accessible.before,
readable.before,
writable.before,
),
iff(
and(hasParams('action'), hasSetting('actions')),
actions.before,
),
iff(
and(hasParams('ela'), hasSetting('ela')),
ela.before
),
iff(
and(hasSetting('instance_limit', true), ({ method }) => isOneOf(method, 'create', 'remove', 'patch')),
instance_limit.before
),
),
],
create: [
iff(
hasAccount,
iff(
hasSetting('apply_id', true),
apply_id.beforeCreate
)
),
iff(
hasSetting('image_crud'),
image_crud.beforeCreate,
),
],
find: [
iff(
hasAccount,
iff(
hasSetting('access_query', true),
access_query.beforeFind,
),
),
],
get: [
iff(
hasAccount,
iff(
hasSetting('access_query', true),
access_query.beforeGet,
),
),
],
update: [
disallow(),
],
patch: [
iff(
hasSetting('image_crud'),
image_crud.beforeUpdate,
),
iff(
and(hasParams('action'), hasSetting('actions')),
actions.runAction
),
],
remove: [
],
},
after: {
all: [
iff(
and(hasParams('ela'), hasSetting('ela')),
ela.after
)
],
create: [
(context) => {
if (context.result.merchantId) {
context.service.emit('modified', {
id: context.result.merchantId,
target: 'MERCHANT'
});
}
}
],
find: [
iff(
hasSetting('image_crud'),
image_crud.afterFind,
),
iff(
and(hasAccount, hasSetting('accessible', true)),
readable.afterFind
),
],
get: [
iff(
hasSetting('image_crud'),
image_crud.afterGet,
),
iff(
and(hasAccount, hasSetting('accessible', true)),
readable.afterGet
),
],
patch: [
iff(
hasSetting('image_crud'),
image_crud.beforeUpdate,
),
(context) => {
if (context.result.merchantId) {
context.service.emit('modified', {
id: context.result.merchantId,
target: 'MERCHANT'
});
}
}
],
remove: [
iff(
hasSetting('image_crud'),
image_crud.afterRemove,
),
(context) => {
if (context.result.merchantId) {
context.service.emit('modified', {
id: context.result.merchantId,
target: 'MERCHANT'
});
}
}
],
},
error: {
all: [
],
create: [
iff(
hasSetting('image_crud'),
image_crud.errorCreate,
),
],
find: [
],
get: [
],
patch: [
iff(
hasSetting('image_crud'),
image_crud.errorUpdate,
),
],
remove: [
],
}
};
if (extraHooks) {
extraHooks(defaultHook);
}
return defaultHook;
};
You can also querying by noSQL by given with
{
ela: {
field: {
status: 'CONFIRM',
$limit: 10,
}
}
}
but i haven't implement this into mine app, i would implement when i done frontend sdk, frontend wont have experienced on these query even noSQL.