eventstore

A simple filebased eventsourced store

< Back to frontpage

Getting started with EventStore

To start using EventStore, you will first need to create 3 things:

In this tutorial, we’ll create seperate artifacts for each these three things.

The project

In this tutorial we’re creating a small membership database. The main functionality will be to register members, change their details etc.

The Domain Model

The domain model has the methods on it to “do the business”. The functionality available to the user of the eventstore is represented on the domain model.

When the domain model needs to persist data, an event is dispatched (by calling dispatch(eventName, eventData)) to write an event to the event log, and consequently handle it in the log aggregator.

When the domain model needs to retrieve data, it gets it from the storage model. The storage model uses the event log the recreate the data model when it is needed.

The following are the methods and properties on the domain model for our membership database:


MemberListDomainModel: class


function MemberListDomainModel(dispatch, logAggregatorData) {
	this.registerNewMember = function(member) {
		dispatch("newMemberRegistered", { member });
		console.log("SEND MAIL -> welcome to new member");
	}

	this.endMembership = function(name) {
		dispatch("membershipEnded", { name });
		console.log("SEND MAIL -> goodbye to member");
	}

	this.correctAddress = function(name, address) {
		dispatch("addressCorrected", { name, address });
	}

	this.memberHasMoved = function(name, address) {
		dispatch("memberHasMoved", { name, address });
	}

	this.listMembers = function() {
		let members = logAggregatorData;
		let ret = Object.keys(members).map(key => Object.assign({ name: key }, members[key]));
		return ret;
	}

	this.getMember = function(name) {
		let members = logAggregatorData;
		return members[name];
	}
}

The Log Aggregator

Everytime data is persisted, it is written to the event log. Its hard to use data from the eventlog directly, so the log aggregator is used to replay the events from the event log to build a more comprehensible data model. See the Patterns page for a description of different patterns for building a model from a log.

The user of the eventstore will not see the log aggregator directly, but will instead interact with the domain model.

Code in the log aggregator must not do external integrations, as the event handlers on the log aggregator will be called for each event each time the log is played back. External integrations (like sending email in this case) should happen in the domain model.

The following are the methods and properties on the log aggregator for our membership database:


MemberListLogAggregator: class


function MemberListLogAggregator(logAggregatorData) {
	this.eventHandlers = {
		onNewMemberRegistered(eventdata) {
			if (logAggregatorData[eventdata.member.name]) {
				throw new Error(`onNewMemberRegistered failed. ${eventdata.member.name} is already a member.`)
			}
			logAggregatorData[eventdata.member.name] = {
				address: eventdata.member.address,
				membershipLevel: eventdata.member.membershipLevel
			};
		},

		onMembershipEnded(eventdata) {
			if (!logAggregatorData[eventdata.name]) {
				throw new Error(`onMembershipEnded failed. ${eventdata.name} is not a member.`)
			}
			delete logAggregatorData[eventdata.name];
		},

		onAddressCorrected(eventdata) {
			if (!logAggregatorData[eventdata.name]) {
				throw new Error(`onAddressCorrected failed. ${eventdata.name} is not a member.`)
			}
			logAggregatorData[eventdata.name].address = eventdata.address;
		},

		onMemberHasMoved(eventdata) {
			if (!logAggregatorData[eventdata.name]) {
				throw new Error(`onMemberHasMoved failed. ${eventdata.name} is not a member.`)
			}
			logAggregatorData[eventdata.name].address = eventdata.address;
		}
	}
}

The Model Definition

The model definition object tells Eventstore how to initialize the domain model and the log aggregator.

The following are the methods and properties on the model definition:


modelDefinition: object


More documentation on all methods and properties on the model definition

let modelDefinition = {
	snapshotName: "memberlist",
	initializeLogAggregatorData: () => ({}),
	createLogAggregator: logAggregatorData => new MemberListLogAggregator(logAggregatorData),
	createDomainModel: (dispatch, logAggregatorData) => new MemberListDomainModel(dispatch, logAggregatorData)
}

Putting the parts together

Use defineStore to point at where the physical store for the logs. For node.js applications, you can use the default file system provider. If you want to use Eventstore in the browser, you can supply alternative options for how and where the logs are persisted.

The resulting object is the root for either writing straight to the event log or working with instances of the domain model.


const {defineStore} = require("@scriptabuild/eventstore");

let store = defineStore("/foldername");
let model = store.defineModel(modelDefinition);

await model.withReadWriteInstance((instance, readyToCommit) => {
	instance.registerNewMember({ name: "arjan einbu", address: {} });
	readyToCommit();
});

See the API reference for defineStore for more information.