Skip to content

Commit 3813247

Browse files
gkachruJulien-R44
authored andcommitted
refactor: Only one bus instance per named cache. All subsequent namespaces under it use the same bus.
1 parent c772f83 commit 3813247

File tree

7 files changed

+95
-37
lines changed

7 files changed

+95
-37
lines changed

packages/bentocache/src/bus/bus.ts

+20-16
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { Bus as RlanzBus } from '@boringnode/bus'
22
import type { Transport } from '@boringnode/bus/types/main'
33

44
import { CacheBusMessageType } from '../types/bus.js'
5-
import { BaseDriver } from '../drivers/base_driver.js'
65
import type { LocalCache } from '../cache/facades/local_cache.js'
76
import { BusMessageReceived } from '../events/bus/bus_message_received.js'
87
import { BusMessagePublished } from '../events/bus/bus_message_published.js'
@@ -17,22 +16,20 @@ import type { BusOptions, CacheBusMessage, Emitter, Logger } from '../types/main
1716
* the same channel and will receive the message and update their
1817
* local cache accordingly.
1918
*/
20-
export class Bus extends BaseDriver {
19+
export class Bus {
2120
#bus: RlanzBus
2221
#logger: Logger
2322
#emitter: Emitter
24-
#cache?: LocalCache
23+
#localCaches: Map<string, LocalCache> = new Map()
2524
#channelName = 'bentocache.notifications'
2625

2726
constructor(
27+
name: string,
2828
driver: Transport,
29-
cache: LocalCache,
3029
logger: Logger,
3130
emitter: Emitter,
3231
options: BusOptions = {},
3332
) {
34-
super(options)
35-
this.#cache = cache
3633
this.#emitter = emitter
3734
this.#logger = logger.child({ context: 'bentocache.bus' })
3835

@@ -44,37 +41,44 @@ export class Bus extends BaseDriver {
4441
},
4542
})
4643

47-
if (this.prefix) this.#channelName += `:${this.prefix}`
44+
if (name) this.#channelName += `:${name}`
4845

4946
this.#bus.subscribe<CacheBusMessage>(this.#channelName, this.#onMessage.bind(this))
5047
this.#logger.trace({ channel: this.#channelName }, 'bus subscribed to channel')
5148
}
5249

53-
namespace(namespace: string): string {
54-
return this.createNamespacePrefix(namespace)
50+
/**
51+
* Add a LocalCache for this bus to manage
52+
* @param namespace The namespace
53+
* @param cache The LocalCache instance
54+
*/
55+
manageCache(namespace: string, cache: LocalCache) {
56+
this.#logger.trace({ namespace, channel: this.#channelName }, 'added namespaced cache')
57+
this.#localCaches?.set(namespace, cache)
5558
}
5659

5760
/**
5861
* When a message is received through the bus.
5962
* This is where we update the local cache.
6063
*/
6164
async #onMessage(message: CacheBusMessage) {
62-
this.#logger.trace(
63-
{ keys: message.keys, type: message.type, channel: this.#channelName },
64-
'received message from bus',
65-
)
65+
if (!message.namespace || !this.#localCaches.has(message.namespace)) return
66+
67+
this.#logger.trace({ ...message, channel: this.#channelName }, 'received message from bus')
6668
this.#emitter.emit('bus:message:received', new BusMessageReceived(message))
6769

70+
const cache = this.#localCaches.get(message.namespace)
71+
6872
if (message.type === CacheBusMessageType.Delete) {
69-
for (const key of message.keys) this.#cache?.delete(key)
73+
for (const key of message.keys) cache?.delete(key)
7074
}
7175

7276
if (message.type === CacheBusMessageType.Set) {
73-
for (const key of message.keys) this.#cache?.logicallyExpire(key)
77+
for (const key of message.keys) cache?.logicallyExpire(key)
7478
}
7579

7680
if (message.type === CacheBusMessageType.Clear) {
77-
this.#cache?.clear()
81+
cache?.clear()
7882
}
7983
}
8084

packages/bentocache/src/bus/encoders/binary_encoder.ts

+33-3
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ export class BinaryEncoder implements TransportEncoder {
5656
0,
5757
)
5858

59-
const totalLength = this.#busIdLength + 1 + totalKeysLength
59+
const namespaceKeyLength = payload.namespace ? Buffer.byteLength(payload.namespace, 'utf8') : 0
60+
61+
const totalLength = this.#busIdLength + 1 + 4 + namespaceKeyLength + totalKeysLength
6062

6163
/**
6264
* Allocate a single buffer for the entire message
@@ -74,9 +76,26 @@ export class BinaryEncoder implements TransportEncoder {
7476
buffer.writeUInt8(this.busMessageTypeToNum(payload.type), this.#busIdLength)
7577

7678
/**
77-
* 3. Write the keys
79+
* 3. Write the namespace
7880
*/
7981
let offset = this.#busIdLength + 1
82+
/**
83+
* Write the length of the namespace key
84+
*/
85+
buffer.writeUInt32BE(namespaceKeyLength, offset)
86+
offset += 4
87+
88+
/**
89+
* Write the namespace itself, if not empty
90+
*/
91+
if (payload.namespace) {
92+
buffer.write(payload.namespace, offset, namespaceKeyLength, 'utf8')
93+
offset += namespaceKeyLength
94+
}
95+
96+
/**
97+
* 4. Write the keys
98+
*/
8099
for (const key of payload.keys) {
81100
/**
82101
* Compute the length of the key in bytes and write it as a 4-byte big-endian integer
@@ -114,6 +133,17 @@ export class BinaryEncoder implements TransportEncoder {
114133
const typeValue = buffer.readUInt8(offset++)
115134
const type = this.numToBusMessageType(typeValue)
116135

136+
/**
137+
* Then the namespace
138+
*/
139+
const namespaceKeyLength = buffer.readUInt32BE(offset)
140+
offset += 4
141+
142+
const namespace = namespaceKeyLength
143+
? buffer.toString('utf8', offset, offset + namespaceKeyLength)
144+
: ''
145+
offset += namespaceKeyLength
146+
117147
/**
118148
* Finally, the keys
119149
*/
@@ -134,6 +164,6 @@ export class BinaryEncoder implements TransportEncoder {
134164
keys.push(key)
135165
}
136166

137-
return { busId, payload: { keys, type } }
167+
return { busId, payload: { keys, type, namespace } }
138168
}
139169
}

packages/bentocache/src/cache/cache.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ export class Cache implements CacheProvider {
217217

218218
this.#stack.emit(new events.CacheDeleted(key, this.name))
219219

220-
await this.#stack.bus?.publish({ type: CacheBusMessageType.Delete, keys: [key] })
220+
await this.#stack.publish({ type: CacheBusMessageType.Delete, keys: [key] })
221221

222222
return true
223223
}
@@ -240,7 +240,7 @@ export class Cache implements CacheProvider {
240240

241241
keys.forEach((key) => this.#stack.emit(new events.CacheDeleted(key, this.name)))
242242

243-
await this.#stack.bus?.publish({ type: CacheBusMessageType.Delete, keys })
243+
await this.#stack.publish({ type: CacheBusMessageType.Delete, keys })
244244

245245
return true
246246
}
@@ -254,7 +254,7 @@ export class Cache implements CacheProvider {
254254
await Promise.all([
255255
this.#stack.l1?.clear(),
256256
this.#stack.l2?.clear(cacheOptions),
257-
this.#stack.bus?.publish({ type: CacheBusMessageType.Clear, keys: [] }),
257+
this.#stack.publish({ type: CacheBusMessageType.Clear, keys: [] }),
258258
])
259259

260260
this.#stack.emit(new events.CacheCleared(this.name))

packages/bentocache/src/cache/stack/cache_stack.ts

+29-12
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import lodash from '@poppinss/utils/lodash'
33
import { Bus } from '../../bus/bus.js'
44
import { LocalCache } from '../facades/local_cache.js'
55
import { RemoteCache } from '../facades/remote_cache.js'
6+
import { BaseDriver } from '../../drivers/base_driver.js'
67
import { JsonSerializer } from '../../serializers/json.js'
78
import type { BentoCacheOptions } from '../../bento_cache_options.js'
89
import { CacheEntryOptions } from '../cache_entry/cache_entry_options.js'
@@ -11,10 +12,11 @@ import type {
1112
BusOptions,
1213
CacheEvent,
1314
CacheStackDrivers,
15+
CacheBusMessage,
1416
Logger,
1517
} from '../../types/main.js'
1618

17-
export class CacheStack {
19+
export class CacheStack extends BaseDriver {
1820
#serializer = new JsonSerializer()
1921

2022
l1?: LocalCache
@@ -32,29 +34,31 @@ export class CacheStack {
3234
drivers: CacheStackDrivers,
3335
bus?: Bus,
3436
) {
37+
super(options)
3538
this.logger = options.logger.child({ cache: this.name })
3639

3740
if (drivers.l1Driver) this.l1 = new LocalCache(drivers.l1Driver, this.logger)
3841
if (drivers.l2Driver) this.l2 = new RemoteCache(drivers.l2Driver, this.logger)
3942

40-
this.bus = this.#createBus(drivers.busDriver, bus, drivers.busOptions)
43+
this.bus = bus ? bus : this.#createBus(drivers.busDriver, drivers.busOptions)
44+
if (this.l1) this.bus?.manageCache(this.prefix, this.l1)
45+
4146
this.defaultOptions = new CacheEntryOptions(options)
4247
}
4348

4449
get emitter() {
4550
return this.options.emitter
4651
}
4752

48-
#createBus(busDriver?: BusDriver, bus?: Bus, busOptions?: BusOptions) {
49-
if (bus) return bus
50-
if (!busDriver || !this.l1) return
53+
#createBus(busDriver?: BusDriver, busOptions?: BusOptions) {
54+
if (!busDriver) return
5155

5256
this.#busDriver = busDriver
5357
this.#busOptions = lodash.merge(
5458
{ retryQueue: { enabled: true, maxSize: undefined } },
5559
busOptions,
5660
)
57-
const newBus = new Bus(this.#busDriver, this.l1, this.logger, this.emitter, this.#busOptions)
61+
const newBus = new Bus(this.name, this.#busDriver, this.logger, this.emitter, this.#busOptions)
5862

5963
return newBus
6064
}
@@ -63,18 +67,31 @@ export class CacheStack {
6367
if (!this.#namespaceCache.has(namespace)) {
6468
this.#namespaceCache.set(
6569
namespace,
66-
new CacheStack(this.name, this.options, {
67-
l1Driver: this.l1?.namespace(namespace),
68-
l2Driver: this.l2?.namespace(namespace),
69-
busDriver: this.#busDriver,
70-
busOptions: { ...this.#busOptions, prefix: this.bus?.namespace(namespace) },
71-
}),
70+
new CacheStack(
71+
this.name,
72+
this.options.cloneWith({ prefix: this.createNamespacePrefix(namespace) }),
73+
{
74+
l1Driver: this.l1?.namespace(namespace),
75+
l2Driver: this.l2?.namespace(namespace),
76+
},
77+
this.bus,
78+
),
7279
)
7380
}
7481

7582
return <CacheStack>this.#namespaceCache.get(namespace)
7683
}
7784

85+
/**
86+
* Publish a message to the bus channel
87+
*
88+
* @returns true if the message was published, false if not
89+
* and undefined if a bus is not part of the stack
90+
*/
91+
async publish(message: CacheBusMessage): Promise<boolean | undefined> {
92+
return this.bus?.publish({ ...message, namespace: this.prefix })
93+
}
94+
7895
emit(event: CacheEvent) {
7996
return this.emitter.emit(event.name, event.toJSON())
8097
}

packages/bentocache/src/cache/stack/cache_stack_writer.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export class CacheStackWriter {
2222

2323
this.cacheStack.l1?.set(key, item, options)
2424
await this.cacheStack.l2?.set(key, item, options)
25-
await this.cacheStack.bus?.publish({ type: CacheBusMessageType.Set, keys: [key] })
25+
await this.cacheStack.publish({ type: CacheBusMessageType.Set, keys: [key] })
2626

2727
this.cacheStack.emit(new CacheWritten(key, value, this.cacheStack.name))
2828
return true

packages/bentocache/src/types/bus.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { Transport } from '@boringnode/bus/types/main'
22

33
import type { Duration } from './helpers.js'
4-
import type { DriverCommonOptions } from './main.js'
54

65
/**
76
* Interface for the bus driver
@@ -14,6 +13,7 @@ export type BusDriver = Transport
1413
export type CacheBusMessage = {
1514
keys: string[]
1615
type: CacheBusMessageType
16+
namespace?: string
1717
}
1818

1919
export enum CacheBusMessageType {
@@ -54,4 +54,4 @@ export type BusOptions = {
5454
*/
5555
retryInterval?: Duration | false
5656
}
57-
} & DriverCommonOptions
57+
}

packages/bentocache/tests/bus/bus.spec.ts

+7
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ test.group('Bus synchronization', () => {
6969

7070
await cache1NSUsersMe.set(key, 24)
7171
await cache3NSAdmin.set(key, 42)
72+
await cache1.set(key, 33)
7273
await setTimeout(100)
7374

7475
assert.equal(await cache1NSUsersMe.get(key), 24)
@@ -87,6 +88,11 @@ test.group('Bus synchronization', () => {
8788
await setTimeout(100)
8889

8990
assert.isUndefined(await cache3NSAdmin.get(key))
91+
assert.equal(await cache2.get(key), 33)
92+
await cache2.delete(key)
93+
await setTimeout(100)
94+
95+
assert.isUndefined(await cache1.get(key))
9096
}).disableTimeout()
9197

9298
test('synchronize clear across namespaces', async ({ assert }) => {
@@ -312,6 +318,7 @@ test.group('Bus synchronization', () => {
312318
const data = {
313319
keys: [],
314320
type: CacheBusMessageType.Clear,
321+
namespace: 'users',
315322
}
316323

317324
bus1.subscribe('foo', (message: any) => {

0 commit comments

Comments
 (0)