events-backend: add cleanup of old events for memory store

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2024-09-18 23:45:44 +02:00
parent 483c8dc243
commit cda26b8822
2 changed files with 104 additions and 1 deletions
@@ -0,0 +1,86 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { EventParams } from '@backstage/plugin-events-node';
import { MemoryEventBusStore } from './MemoryEventBusStore';
function mkEvent(message: string): EventParams {
return {
topic: 'test',
eventPayload: { message },
};
}
describe('MemoryEventBusStore', () => {
it('should publish to subscribers', async () => {
const store = new MemoryEventBusStore();
await expect(
store.publish({
event: mkEvent('hello'),
notifiedSubscribers: [],
}),
).resolves.toEqual(undefined);
await expect(store.readSubscription('test')).rejects.toThrow(
'Subscription not found',
);
await store.upsertSubscription('tester', ['test']);
await expect(
store.publish({
event: mkEvent('hello'),
notifiedSubscribers: [],
}),
).resolves.toEqual({ eventId: '1' });
await expect(
store.publish({
event: mkEvent('ignored'),
notifiedSubscribers: ['tester'],
}),
).resolves.toEqual(undefined);
await expect(store.readSubscription('tester')).resolves.toEqual({
events: [mkEvent('hello')],
});
});
it('should clean up old events', async () => {
const store = new MemoryEventBusStore({ maxEvents: 5 });
await store.upsertSubscription('tester', ['test']);
for (let i = 0; i < 20; ++i) {
await expect(
store.publish({
event: mkEvent(`hello ${i}`),
notifiedSubscribers: [],
}),
).resolves.toEqual({ eventId: String(i + 1) });
}
await expect(store.readSubscription('tester')).resolves.toEqual({
events: [
mkEvent('hello 15'),
mkEvent('hello 16'),
mkEvent('hello 17'),
mkEvent('hello 18'),
mkEvent('hello 19'),
],
});
});
});
@@ -18,8 +18,10 @@ import { EventBusStore } from './types';
import { NotFoundError } from '@backstage/errors';
const MAX_BATCH_SIZE = 10;
const MAX_EVENTS_DEFAULT = 1_000;
export class MemoryEventBusStore implements EventBusStore {
#maxEvents: number;
#events = new Array<
EventParams & { seq: number; notifiedSubscribers: Set<string> }
>();
@@ -32,6 +34,10 @@ export class MemoryEventBusStore implements EventBusStore {
resolve(result: { topic: string }): void;
}>();
constructor(options: { maxEvents?: number } = {}) {
this.#maxEvents = options.maxEvents ?? MAX_EVENTS_DEFAULT;
}
async publish(options: {
event: EventParams;
notifiedSubscribers: string[];
@@ -59,6 +65,12 @@ export class MemoryEventBusStore implements EventBusStore {
this.#listeners.delete(listener);
}
}
// Trim old events
if (this.#events.length > this.#maxEvents) {
this.#events.shift();
}
return { eventId: String(nextSeq) };
}
@@ -96,7 +108,12 @@ export class MemoryEventBusStore implements EventBusStore {
sub.seq = events[events.length - 1]?.seq ?? sub.seq;
return { events: events.map(event => ({ ...event, seq: undefined })) };
return {
events: events.map(({ topic, eventPayload }) => ({
topic,
eventPayload,
})),
};
}
async setupListener(