@serialized/serialized-client
TypeScript icon, indicating that this package has built-in type declarations

8.3.0 • Public • Published

Serialized Typescript client

The official Typescript client for Serialized.

Features

  • Built with Typescript
  • Client for Event Sourcing & CQRS APIs provided by Serialized
  • Promise-based API that supports async/await
  • 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.

// The different statuses our game can be in
enum GameStatus {
  UNDEFINED = 'UNDEFINED',
  CREATED = 'CREATED',
  STARTED = 'STARTED',
  CANCELED = 'CANCELED',
  FINISHED = 'FINISHED'
}

type GameState = {
  readonly gameId?: string
  readonly status?: GameStatus
}

Events

Define your domain events as types

type GameCreated = DomainEvent<'GameCreated', { gameId: string, creationTime: number }>
type GameStarted = DomainEvent<'GameStarted', { gameId: string, startTime: number }>
type GameCanceled = DomainEvent<'GameCanceled', { gameId: string, cancelTime: number }>
type GameFinished = DomainEvent<'GameFinished', { gameId: string, endTime: number }>
type GameEvent = GameCreated | GameStarted | GameCanceled | GameFinished;

Next, we create the StateBuilder implementation, which can handle loading events one-by-one to create the current state. Each method should have apply as a prefix and the event type as the suffix and return the new state.

const stateBuilder: StateBuilder<GameState, GameEvent> = {
  initialState: () => {
    return {gameId: '', status: GameStatus.UNDEFINED}
  },
  applyGameCreated: (state, event) => {
    return {...state, gameId: event.data.gameId, status: GameStatus.CREATED}
  },
  applyGameStarted: (state, event) => {
    return {...state, status: GameStatus.STARTED}
  },
  applyGameCanceled: (state, event) => {
    return {...state, status: GameStatus.CANCELED}
  },
  applyGameFinished: (state, event) => {
    return {...state, status: GameStatus.FINISHED}
  }
}

Aggregate

The aggregate contains the domain logic and each method should return 0..n events that should be stored for a successful operation. The aggregate takes the state as a constructor argument and should be immutable.

Any unsuccessful operation should throw an error.

class Game {
  constructor(private readonly state: GameState) {
  }

  create(gameId: string, creationTime: number): GameCreated[] {
    const currentStatus = this.state.status;
    if (currentStatus == GameStatus.UNDEFINED) {
      return [{
        eventType: 'GameCreated',
        eventId: uuidv4(),
        data: {
          gameId,
          creationTime
        }
      }];
    } else if (currentStatus == GameStatus.CREATED) {
      return [];
    } else {
      throw new InvalidGameStatusException(GameStatus.UNDEFINED, currentStatus);
    }
  }

  start(startTime: number): GameStarted[] {
    const currentStatus = this.state.status;
    if (this.state.status == GameStatus.STARTED) {
      return [];
    } else if (this.state.status == GameStatus.CREATED) {
      return [{
        eventType: 'GameStarted',
        eventId: uuidv4(),
        data: {
          gameId: this.state.gameId,
          startTime
        }
      }];
    }
    throw new InvalidGameStatusException(GameStatus.CREATED, currentStatus);
  }

...

}

Test the client by creating a Game:

const gameClient = serialized.aggregateClient({aggregateType: 'game'}, stateBuilder, (state: GameState) => new Game(state));
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({aggregateId: gameId}, (game: Game) => game.start(startTime))

📄 More resources

Troubleshooting

Encountering an issue? Don't feel afraid to add an issue here on Github or to reach out via Serialized.

Package Sidebar

Install

npm i @serialized/serialized-client

Weekly Downloads

0

Version

8.3.0

License

MIT

Unpacked Size

81.1 kB

Total Files

29

Last publish

Collaborators

  • jeham
  • mholmqvist