CQRS and Event Sourcing — TypeScript Implementation
Advertisement
CQRS separates commands (writes) from queries (reads). Event sourcing stores all changes as immutable events.
Event Sourcing
interface Event {
type: string;
data: any;
timestamp: Date;
}
class EventStore {
private events: Event[] = [];
append(event: Event): void {
this.events.push(event);
}
getEvents(id: string): Event[] {
return this.events.filter((e) => e.data.id === id);
}
replay(id: string): any {
const events = this.getEvents(id);
let state = {};
events.forEach((event) => {
if (event.type === "user:created") {
state = event.data;
} else if (event.type === "user:updated") {
state = { ...state, ...event.data };
}
});
return state;
}
}
const store = new EventStore();
store.append({ type: "user:created", data: { id: 1, name: "Alice" }, timestamp: new Date() });
store.append({ type: "user:updated", data: { name: "Alice Updated" }, timestamp: new Date() });
const user = store.replay(1);
console.log(user); // { id: 1, name: "Alice Updated" }
CQRS
// Command side (writes)
class CreateUserCommand {
constructor(private name: string, private email: string) {}
}
class UserCommandHandler {
execute(command: CreateUserCommand): void {
const event = {
type: "user:created",
data: { id: 1, name: command.name, email: command.email },
timestamp: new Date(),
};
eventStore.append(event);
messageBus.publish(event);
}
}
// Query side (reads)
class GetUserQuery {
constructor(private id: number) {}
}
class UserQueryHandler {
async execute(query: GetUserQuery): Promise<any> {
return userReadModel.findById(query.id);
}
}
FAQ
Q: When to use CQRS? A: Complex domains, audit trails needed, different read/write scales.
CQRS and Event Sourcing enable temporal consistency and auditability.
Advertisement