Serialized Javascript & Typescript client
The official Javascript/Typescript client for Serialized.
✨ Features
- Client for Event Sourcing & CQRS APIs provided by Serialized
- Works both for Typescript and Javascript on Node version >= 10.
- Promise-based API that supports async/await
- Built with Typescript
- Provides an easy way to implement DDD Aggregates using Event Sourcing.
💡 Getting Started
Register for a free account at https://serialized.io to get your access keys to the API (if you haven't already).
Install the Serialized TS/JS client via the npm package manager:
npm install @serialized/serialized-client
Import the library and initialize the client instance:
import {Serialized} from "@serialized/serialized-client"
const serialized = Serialized.create({
accessKey: "<YOUR_ACCESS_KEY>",
secretAccessKey: "<YOUR_SECRET_ACCESS_KEY>"
});
Create our domain
State
The state type holds the assembled state from the events during the load of the aggregate.
enum GameStatus {
UNDEFINED = 'UNDEFINED',
CREATED = 'CREATED',
STARTED = 'STARTED',
FINISHED = 'FINISHED'
}
type GameState = {
readonly gameId?: string;
readonly status?: GameStatus;
}
Events
Define your domain events as immutable Typescript classes.
class GameCreated implements DomainEvent {
constructor(readonly gameId: string,
readonly creationTime: number) {
};
}
class GameStarted implements DomainEvent {
constructor(readonly gameId: string,
readonly startTime: number) {
};
}
Next, we create the state builder, which can handle loading events one-by-one to create the current state.
The state builder has methods decorated with @EventHandler
to mark its event handling methods:
class GameStateBuilder {
get initialState(): GameState {
return {
status: GameStatus.UNDEFINED
}
}
@EventHandler(GameCreated)
handleGameCreated(state: GameState, event: GameCreated): GameState {
return {gameId: state.gameId, status: GameStatus.CREATED};
}
@EventHandler(GameStarted)
handleGameStarted(state: GameState, event: GameStarted): GameState {
return {...state, status: GameStatus.STARTED};
}
}
Aggregate
The aggregate contains the domain logic and each method should return 0..n
events that should be stored for a successful operation.
Any unsuccessful operation should throw an error.
@Aggregate('game', GameStateBuilder)
class Game {
constructor(private readonly state: GameState) {
}
create(gameId: string, creationTime: number) {
return [new GameCreated(gameId, creationTime)];
}
start(gameId: string, startTime: number) {
if(this.state.status !== GameStatus.CREATED) {
throw new Error('Must create Game before you can start it');
}
return [new GameStarted(gameId, startTime)];
}
}
Test the client by creating a Game
:
const gameClient = serialized.aggregateClient(Game);
const gameId = uuidv4();
await gameClient.create(gameId, (game) => (game.create(gameId, Date.now())));
To perform an update
operation, which means loading all events, performing business logic and then appending more events
await gameClient.update(gameId, (game: Game) => (game.start(gameId, startTime)));
📄 Client reference
📄 More resources
❓ Troubleshooting
Encountering an issue? Don't feel afraid to add an issue here on Github or to reach out via Serialized.