Skip to content

Commit

Permalink
Merge pull request #491 from leav-solutions/feature/XSTREAM-662_coreC…
Browse files Browse the repository at this point in the history
…aching

perf(core): add more caching
  • Loading branch information
Delmotte-Vincent authored Jun 13, 2024
2 parents ab2112e + 05976d7 commit 1197056
Show file tree
Hide file tree
Showing 14 changed files with 166 additions and 194 deletions.
23 changes: 13 additions & 10 deletions apps/core/src/domain/attribute/attributeDomain.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// Copyright LEAV Solutions 2017
// This file is released under LGPL V3
// License text available at https://www.gnu.org/licenses/lgpl-3.0.txt
import {ILibrary} from '_types/library';
import {IQueryInfos} from '_types/queryInfos';
import {IEventsManagerDomain} from 'domain/eventsManager/eventsManagerDomain';
import {IAdminPermissionDomain} from 'domain/permission/adminPermissionDomain';
import {IVersionProfileDomain} from 'domain/versionProfile/versionProfileDomain';
Expand All @@ -11,15 +9,17 @@ import {IFormRepo} from 'infra/form/formRepo';
import {ILibraryRepo} from 'infra/library/libraryRepo';
import {ITreeRepo} from 'infra/tree/treeRepo';
import {IUtils} from 'utils/utils';
import {mockAttrAdv, mockAttrAdvVersionable, mockAttrSimple, mockAttrTree} from '../../__tests__/mocks/attribute';
import {mockForm} from '../../__tests__/mocks/forms';
import {mockLibrary} from '../../__tests__/mocks/library';
import {ActionsListEvents, ActionsListIOTypes} from '../../_types/actionsList';
import {AttributeFormats, AttributeTypes, IAttribute} from '../../_types/attribute';
import {AdminPermissionsActions} from '../../_types/permissions';
import {ILibrary} from '_types/library';
import {IQueryInfos} from '_types/queryInfos';
import PermissionError from '../../errors/PermissionError';
import ValidationError from '../../errors/ValidationError';
import {ICacheService, ICachesService} from '../../infra/cache/cacheService';
import {ActionsListEvents, ActionsListIOTypes} from '../../_types/actionsList';
import {AttributeFormats, AttributeTypes, IAttribute} from '../../_types/attribute';
import {AdminPermissionsActions} from '../../_types/permissions';
import {mockAttrAdv, mockAttrAdvVersionable, mockAttrSimple, mockAttrTree} from '../../__tests__/mocks/attribute';
import {mockForm} from '../../__tests__/mocks/forms';
import {mockLibrary} from '../../__tests__/mocks/library';
import {IActionsListDomain} from '../actionsList/actionsListDomain';
import attributeDomain from './attributeDomain';

Expand All @@ -30,7 +30,8 @@ const mockCacheService: Mockify<ICacheService> = {
};

const mockCachesService: Mockify<ICachesService> = {
getCache: jest.fn().mockReturnValue(mockCacheService)
getCache: jest.fn().mockReturnValue(mockCacheService),
memoize: jest.fn().mockImplementation(({func}) => func())
};

const mockEventsManager: Mockify<IEventsManagerDomain> = {
Expand Down Expand Up @@ -146,7 +147,9 @@ describe('attributeDomain', () => {

const attrDomain = attributeDomain({
'core.infra.library': mockLibRepo as ILibraryRepo,
'core.infra.attribute': mockAttrRepo as IAttributeRepo
'core.infra.attribute': mockAttrRepo as IAttributeRepo,
'core.infra.cache.cacheService': mockCachesService as ICachesService,
'core.utils': mockUtils as IUtils
});
const libAttrs = await attrDomain.getLibraryAttributes('test', ctx);

Expand Down
24 changes: 16 additions & 8 deletions apps/core/src/domain/attribute/attributeDomain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ interface IDeps {
config?: any;
}

export default function ({
export default function({
'core.infra.attribute': attributeRepo = null,
'core.domain.actionsList': actionsListDomain = null,
'core.domain.permission.admin': adminPermissionDomain = null,
Expand All @@ -82,9 +82,9 @@ export default function ({
'core.infra.form': formRepo = null,
'core.infra.library': libraryRepo = null,
'core.infra.tree': treeRepo = null,
'core.infra.cache.cacheService': cacheService = null,
'core.utils': utils = null,
config = null,
'core.infra.cache.cacheService': cacheService = null
config = null
}: IDeps = {}): IAttributeDomain {
const _updateFormsUsingAttribute = async (attributeId: string, ctx: IQueryInfos): Promise<void> => {
const formsList = await formRepo.getForms({ctx});
Expand Down Expand Up @@ -113,13 +113,21 @@ export default function ({

return {
async getLibraryAttributes(libraryId: string, ctx): Promise<IAttribute[]> {
const libs = await libraryRepo.getLibraries({params: {filters: {id: libraryId}}, ctx});
const _execute = async () => {
const libs = await libraryRepo.getLibraries({params: {filters: {id: libraryId}}, ctx});

if (!libs.list.length) {
throw new ValidationError({id: Errors.UNKNOWN_LIBRARY});
}
if (!libs.list.length) {
throw new ValidationError({id: Errors.UNKNOWN_LIBRARY});
}

return attributeRepo.getLibraryAttributes({libraryId, ctx});
};

const cacheKey = `${utils.getCoreEntityCacheKey('library', libraryId)}:attributes`;

return attributeRepo.getLibraryAttributes({libraryId, ctx});
// Due to race conditions, we sometimes get null when retrieving a newly created core entity. Thus, we don't
// want to keep this "false" null in cache
return cacheService.memoize({key: cacheKey, func: _execute, storeNulls: false, ctx});
},
async getAttributeLibraries({attributeId, ctx}): Promise<ILibrary[]> {
// Validate attribute
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export const updateRecordFile = async (

await deps.updateRecordLastModif(library, recordId, ctx);

await deps.sendRecordUpdateEvent(
deps.sendRecordUpdateEvent(
updatedRecord,
Object.keys(recordData).map(attributeId => ({
id_value: null,
Expand Down
16 changes: 13 additions & 3 deletions apps/core/src/domain/helpers/updateRecordLastModif.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,33 @@
// License text available at https://www.gnu.org/licenses/lgpl-3.0.txt
import {IRecordRepo} from 'infra/record/recordRepo';
import moment from 'moment';
import {IUtils} from 'utils/utils';
import {IQueryInfos} from '_types/queryInfos';
import {IRecord} from '_types/record';
import {ECacheType, ICachesService} from '../../infra/cache/cacheService';

interface IDeps {
'core.infra.record'?: IRecordRepo;
'core.infra.cache.cacheService'?: ICachesService;
'core.utils'?: IUtils;
}

export type UpdateRecordLastModifFunc = (library: string, recordId: string, ctx: IQueryInfos) => Promise<IRecord>;

export default function ({'core.infra.record': recordRepo = null}: IDeps): UpdateRecordLastModifFunc {
return (library, recordId, ctx) =>
recordRepo.updateRecord({
export default function({
'core.infra.record': recordRepo = null,
'core.infra.cache.cacheService': cacheService = null,
'core.utils': utils = null
}: IDeps): UpdateRecordLastModifFunc {
return async (library, recordId, ctx) => {
await cacheService.getCache(ECacheType.RAM).deleteData([utils.getRecordsCacheKey(library, recordId)]);
return recordRepo.updateRecord({
libraryId: library,
recordData: {
id: recordId,
modified_at: moment().unix(),
modified_by: String(ctx.userId)
}
});
};
}
49 changes: 31 additions & 18 deletions apps/core/src/domain/helpers/validate.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// Copyright LEAV Solutions 2017
// This file is released under LGPL V3
// License text available at https://www.gnu.org/licenses/lgpl-3.0.txt
import {ICachesService} from 'infra/cache/cacheService';
import {ILibraryRepo} from 'infra/library/libraryRepo';
import {IRecordRepo} from 'infra/record/recordRepo';
import {IUtils} from 'utils/utils';
import {ILibrary} from '_types/library';
import {IQueryInfos} from '_types/queryInfos';
import ValidationError from '../../errors/ValidationError';
import {AttributeTypes} from '../../_types/attribute';
Expand All @@ -16,21 +18,23 @@ interface IDeps {
'core.infra.record'?: IRecordRepo;
'core.utils'?: IUtils;
'core.infra.library'?: ILibraryRepo;
'core.infra.cache.cacheService'?: ICachesService;
}

export interface IValidateHelper {
validateLibrary(library: string, ctx: IQueryInfos): Promise<void>;
validateLibrary(library: string, ctx: IQueryInfos): Promise<ILibrary>;
validateRecord(library: string, recordId: string, ctx: IQueryInfos): Promise<IRecord>;
validateView(view: string, throwIfNotFound: boolean, ctx: IQueryInfos): Promise<boolean>;
validateTree(tree: string, throwIfNotFound: boolean, ctx: IQueryInfos): Promise<boolean>;
validateLibraryAttribute(library: string, attribute: string, ctx: IQueryInfos): Promise<void>;
}

export default function ({
export default function({
'core.domain.helpers.getCoreEntityById': getCoreEntityById = null,
'core.infra.record': recordRepo = null,
'core.utils': utils = null,
'core.infra.library': libraryRepo = null
'core.infra.library': libraryRepo = null,
'core.infra.cache.cacheService': cacheService = null
}: IDeps): IValidateHelper {
return {
async validateLibraryAttribute(library: string, attribute: string, ctx: IQueryInfos): Promise<void> {
Expand All @@ -41,34 +45,43 @@ export default function ({
}
},
async validateRecord(library, recordId, ctx): Promise<IRecord> {
const recordsRes = await recordRepo.find({
libraryId: library,
filters: [
{
attributes: [{id: 'id', type: AttributeTypes.SIMPLE}],
condition: AttributeCondition.EQUAL,
value: String(recordId)
}
],
retrieveInactive: true,
ctx
});
async function _fetchRecordFromDB() {
const recordsRes = await recordRepo.find({
libraryId: library,
filters: [
{
attributes: [{id: 'id', type: AttributeTypes.SIMPLE}],
condition: AttributeCondition.EQUAL,
value: String(recordId)
}
],
retrieveInactive: true,
ctx
});

if (!recordsRes.list.length) {
return recordsRes.list[0];
}

const cacheKey = utils.getRecordsCacheKey(library, recordId);
const res = await cacheService.memoize({key: cacheKey, func: _fetchRecordFromDB, storeNulls: false, ctx});

if (!res) {
throw utils.generateExplicitValidationError(
'recordId',
{msg: Errors.UNKNOWN_RECORD, vars: {library, recordId}},
ctx.lang
);
}

return recordsRes.list[0];
return res;
},
async validateLibrary(library: string, ctx: IQueryInfos): Promise<void> {
async validateLibrary(library, ctx) {
const lib = await getCoreEntityById('library', library, ctx);
if (!lib) {
throw utils.generateExplicitValidationError('library', Errors.UNKNOWN_LIBRARY, ctx.lang);
}

return lib;
},
async validateView(view: string, throwIfNotFound: boolean, ctx: IQueryInfos): Promise<boolean> {
const existingView = await getCoreEntityById('view', view, ctx);
Expand Down
Loading

0 comments on commit 1197056

Please sign in to comment.