Skip to content

Commit

Permalink
feat: add cache
Browse files Browse the repository at this point in the history
  • Loading branch information
njlie committed Dec 3, 2024
1 parent c824c56 commit 9211ca3
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 13 deletions.
7 changes: 6 additions & 1 deletion packages/backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,11 +216,16 @@ export function initIocContainer(
return client
})

container.singleton('tenantCache', async () => {
return createInMemoryDataStore(config.localCacheDuration)
})

container.singleton('tenantService', async (deps) => {
return createTenantService({
logger: await deps.use('logger'),
knex: await deps.use('knex'),
apolloClient: await deps.use('apolloClient')
apolloClient: await deps.use('apolloClient'),
tenantCache: await deps.use('tenantCache')
})
})

Expand Down
100 changes: 89 additions & 11 deletions packages/backend/src/tenants/service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { Tenant } from './model'
import { getPageTests } from '../shared/baseModel.test'
import { Pagination, SortOrder } from '../shared/baseModel'
import { createTenant } from '../tests/tenant'
import { CacheDataStore } from '../middleware/cache/data-stores'

const generateMutateGqlError = (path: string = 'createTenant') => ({
errors: [
Expand Down Expand Up @@ -80,20 +81,12 @@ describe('Tenant Service', (): void => {
idpSecret: 'test-idp-secret'
}

const createScope = nock(config.authAdminApiUrl)
.post('')
.reply(200, { data: { createTenant: { id: 1234 } } })

const createdTenant = await tenantService.create(createOptions)
createScope.done()
const createdTenant =
await Tenant.query(knex).insertAndFetch(createOptions)

const tenant = await tenantService.get(createdTenant.id)
assert.ok(tenant)
expect(tenant).toEqual({
...createdTenant,
idpConsentUrl: createOptions.idpConsentUrl,
idpSecret: createOptions.idpSecret
})
expect(tenant).toEqual(createdTenant)
})

test('returns undefined if tenant is deleted', async (): Promise<void> => {
Expand Down Expand Up @@ -382,4 +375,89 @@ describe('Tenant Service', (): void => {
deleteScope.done()
})
})

describe('Tenant Service using cache', (): void => {
let deps: IocContract<AppServices>
let appContainer: TestContainer
let config: IAppConfig
let tenantService: TenantService
let tenantCache: CacheDataStore<Tenant>

beforeAll(async (): Promise<void> => {
deps = initIocContainer({
...Config,
localCacheDuration: 5_000 // 5-second default.
})
appContainer = await createTestApp(deps)
config = await deps.use('config')
tenantService = await deps.use('tenantService')
tenantCache = await deps.use('tenantCache')
})

afterEach(async (): Promise<void> => {
await truncateTables(appContainer.knex)
})

afterAll(async (): Promise<void> => {
await appContainer.shutdown()
})

describe('create, update, and retrieve tenant using cache', (): void => {
test('Tenant can be created, updated, and fetched', async (): Promise<void> => {
const createOptions = {
email: faker.internet.email(),
publicName: faker.company.name(),
apiSecret: 'test-api-secret',
idpConsentUrl: faker.internet.url(),
idpSecret: 'test-idp-secret'
}

const scope = nock(config.authAdminApiUrl)
.post('')
.reply(200, { data: { createTenant: { tenant: { id: 1234 } } } })
.persist()

const spyCacheSet = jest.spyOn(tenantCache, 'set')
const tenant = await tenantService.create(createOptions)
expect(tenant).toMatchObject({
...createOptions,
id: tenant.id
})

// Ensure that the cache was set for create
expect(spyCacheSet).toHaveBeenCalledTimes(1)

const spyCacheGet = jest.spyOn(tenantCache, 'get')
await expect(tenantService.get(tenant.id)).resolves.toEqual(tenant)

expect(spyCacheGet).toHaveBeenCalledTimes(1)
expect(spyCacheGet).toHaveBeenCalledWith(tenant.id)

const spyCacheUpdateSet = jest.spyOn(tenantCache, 'set')
const updatedTenant = await tenantService.update({
id: tenant.id,
apiSecret: 'test-api-secret-2'
})

await expect(tenantService.get(tenant.id)).resolves.toEqual(
updatedTenant
)

// Ensure that cache was set for update
expect(spyCacheUpdateSet).toHaveBeenCalledTimes(2)
expect(spyCacheUpdateSet).toHaveBeenCalledWith(tenant.id, updatedTenant)

const spyCacheDelete = jest.spyOn(tenantCache, 'delete')
await tenantService.delete(tenant.id)

await expect(tenantService.get(tenant.id)).resolves.toBeUndefined()

// Ensure that cache was set for deletion
expect(spyCacheDelete).toHaveBeenCalledTimes(1)
expect(spyCacheDelete).toHaveBeenCalledWith(tenant.id)

scope.done()
})
})
})
})
15 changes: 14 additions & 1 deletion packages/backend/src/tenants/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { gql, NormalizedCacheObject } from '@apollo/client'
import { ApolloClient } from '@apollo/client'
import { TransactionOrKnex } from 'objection'
import { Pagination, SortOrder } from '../shared/baseModel'
import { CacheDataStore } from '../middleware/cache/data-stores'

export interface TenantService {
get: (id: string) => Promise<Tenant | undefined>
Expand All @@ -16,6 +17,7 @@ export interface TenantService {
export interface ServiceDependencies extends BaseService {
knex: TransactionOrKnex
apolloClient: ApolloClient<NormalizedCacheObject>
tenantCache: CacheDataStore<Tenant>
}

export async function createTenantService(
Expand All @@ -40,7 +42,14 @@ async function getTenant(
deps: ServiceDependencies,
id: string
): Promise<Tenant | undefined> {
return await Tenant.query(deps.knex).findById(id).whereNull('deletedAt')
const inMem = await deps.tenantCache.get(id)
if (inMem) return inMem
const tenant = await Tenant.query(deps.knex)
.findById(id)
.whereNull('deletedAt')
if (tenant) await deps.tenantCache.set(tenant.id, tenant)

return tenant
}

async function getTenantPage(
Expand Down Expand Up @@ -95,6 +104,8 @@ async function createTenant(
// TODO: add type to this in https://github.com/interledger/rafiki/issues/3125
await deps.apolloClient.mutate({ mutation, variables })
await trx.commit()

await deps.tenantCache.set(tenant.id, tenant)
return tenant
} catch (err) {
await trx.rollback()
Expand Down Expand Up @@ -155,6 +166,7 @@ async function updateTenant(
}

await trx.commit()
await deps.tenantCache.set(tenant.id, tenant)
return tenant
} catch (err) {
await trx.rollback()
Expand All @@ -168,6 +180,7 @@ async function deleteTenant(
): Promise<void> {
const trx = await deps.knex.transaction()

await deps.tenantCache.delete(id)
try {
const deletedAt = new Date(Date.now())
await Tenant.query(trx).patchAndFetchById(id, {
Expand Down

0 comments on commit 9211ca3

Please sign in to comment.