npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

redux-cqrs

v1.6.19

Published

A Redux package implementing CQRS architecture.

Downloads

104

Readme

Redux CQRS Integration Guide

This guide provides a comprehensive overview of implementing the CQRS (Command Query Responsibility Segregation) pattern in a Redux-based application using the 'redux-cqrs' package. We’ll demonstrate how to define commands, handle them in command handlers, apply business logic in domain models, and update the Redux store.

Step-by-Step Implementation

1. Setting Up the Redux Store with CQRS Middleware

Set up the Redux store to use the CQRS middleware, which will intercept and route commands and events.

// ./cqrs/store.ts

import { createStore, applyMiddleware } from 'redux';
import { CommandDispatcher, EventDispatcher, createCQRSReduxMiddleware } from 'redux-cqrs';
import rootReducer from './reducers';

export const commandDispatcher = new CommandDispatcher();
export const eventDispatcher = new EventDispatcher();

const cqrsMiddleware = createCQRSReduxMiddleware(commandDispatcher, eventDispatcher);

const store = createStore(rootReducer, applyMiddleware(cqrsMiddleware));

export default store;

2. Defining the Login Command

Define the 'LoginUserCommand' to represent a login action. This command will encapsulate the data required for a login attempt and be handled by a command handler.

// ./cqrs/commands/user/LoginUserCommand.ts

import { Command } from '../Command';
import { Guid } from 'guid-typescript';

export class LoginUserCommand extends Command {
  constructor(userId: Guid, accessToken: string) {
    super(userId);
    this.AccessToken = accessToken;
  }

  public AccessToken: string;
}

3. Implementing the Command Handler

The 'LoginUserCommandHandler' processes the 'LoginUserCommand', interacts with the 'User' domain, and updates the user state. Command handlers are responsible for executing any logic related to the command.

// ./cqrs/handlers/user/LoginUserCommandHandler.ts

import { CommandHandler } from 'redux-cqrs';
import { LoginUserCommand } from '../../commands/user';
import { User } from '../../domains/User';
import { hydrateEntity } from 'redux-cqrs';
import store from '../../store';

export class LoginUserCommandHandler extends CommandHandler<LoginUserCommand> {
  async handle(command: LoginUserCommand): Promise<void> {
    const user = hydrateEntity(store, User, command.Id);
    user.login(command.AccessToken, '', '', false);
  }
}

In this handler:

  • Domain Hydration: The 'User' domain is fetched using 'hydrateEntity'.
  • Domain Method Execution: The 'user.login()' method is called with data from the command.

4. Defining the Login Event

Define the 'UserLoggedInEvent' class to represent the successful login event. Events are used to capture and propagate the results of commands.

// ./cqrs/events/user/UserLoggedInEvent.ts

import { Event } from 'redux-cqrs';
import { Guid } from 'guid-typescript';

export class UserLoggedInEvent extends Event {
  constructor(userId: Guid, email: string, name: string, premium: boolean) {
    super(userId);
    this.Email = email;
    this.Name = name;
    this.Premium = premium;
  }

  public Email: string;
  public Name: string;
  public Premium: boolean;
}

5. Updating the Domain Model

The 'User' domain model encapsulates the business logic for handling user-related actions. It defines the 'login' method, which applies the 'UserLoggedInEvent' upon successful login. This domain model aligns with your current project structure and handles events through the 'apply' method.

// ./cqrs/domains/User.ts

import { Domain } from './Domain';
import { IEvent } from 'redux-cqrs/';
import { Address } from '../../../../../Template/implementations';
import { UserCreatedEvent, UserLoggedInEvent, UserLoggedOutEvent, PremiumUnlockedEvent, ResultsSavedEvent } from '../events/user';
import { Dispatch } from 'redux';
import { Guid } from 'guid-typescript';
import store from '../store';

export class User extends Domain {
  constructor(dispatch: Dispatch, id: Guid, name: string, email: string, password: string, DoB: Date, address: Address, role: number) {
    super(dispatch, id);
    this.Name = name;
    this.Email = email;
    this.Password = password;
    this.DoB = DoB;
    this.Address = address;
    this.Role = role;
  }

  public Name!: string;
  public Email!: string;
  public Password!: string;
  public DoB!: Date;
  public Address!: Address;
  public Role!: number;
  public AccessToken!: string;
  public LastLogin!: Date;
  public Premium!: boolean;

  public login(accessToken: string, email: string, name: string, premium: boolean): void {
    this.Email = email;
    this.Name = name;
    this.AccessToken = accessToken;
    this.apply(new UserLoggedInEvent(this.Id, this.Email, this.Name, premium));
  }

  public apply(event: IEvent): void {
    store.dispatch(event);
  }
}

6. Updating the Auth Reducer

The 'authReducer' listens for the 'UserLoggedInEvent' and updates the Redux store to reflect the authenticated user state.

// ./cqrs/reducers/auth/authReducer.ts

import { IEvent, ActionType } from 'redux-cqrs';
import { UserLoggedInEvent } from '../../events/user/UserLoggedInEvent';

interface AuthState {
  isAuthenticated: boolean;
  userId: string;
  email?: string;
  name?: string;
}

const initialState: AuthState = {
  isAuthenticated: false,
  userId: '',
};

export const authReducer = (state = initialState, action: IEvent): AuthState => {
  if (action.type.toUpperCase() === ActionType(UserLoggedInEvent)) {
    const event = action as UserLoggedInEvent;
    return {
      ...state,
      isAuthenticated: true,
      userId: event.Id.toString(),
      email: event.Email,
      name: event.Name,
    };
  }

  return state;
};

7. Implementing the Login Component

Create a 'LoginContainer' component to dispatch the 'LoginUserCommand'. This component initializes the command with the user ID and access token, then dispatches it.

// ./components/Login/Container.tsx

import React from 'react';
import { commandDispatcher } from '../../cqrs/store';
import { LoginUserCommand } from '../../cqrs/commands/user/LoginUserCommand';
import { Guid } from 'guid-typescript';

const LoginContainer: React.FC = () => {
  const handleLogin = (accessToken: string) => {
    const userId = Guid.create();
    commandDispatcher.dispatch(new LoginUserCommand(userId, accessToken));
  };

  return <LoginPage onLogin={handleLogin} />;
};

export default LoginContainer;

Summary

  1. Commands: Define actions that change the system’s state.
  2. Command Handlers: Process commands and execute related logic in the domain model.
  3. Events: Capture the results of commands and are used to update the store.
  4. Domain Model: Encapsulate business logic and manage state changes.
  5. Reducers: Update Redux store in response to events.
  6. React Component: Dispatch commands to perform actions within the application.

This guide demonstrates how to establish a clear CQRS structure in your project using 'redux-cqrs', ensuring maintainability, scalability, and efficient state management.