Home

Awesome

CQRS Wechaty

NPM Version NPM Ducksify Extension ES Modules

An event-driven architecture wrapper for Wechaty that applies the CQS principle by using separate Query and Command messages to retrieve and modify the bot state, respectively.

Command Query Responsibility Segregation (CQRS) Wechaty

Image source: Introducing Derivative Event Sourcing

Command Query Responsibility Separation (CQRS)

Command query responsibility separation (CQRS) generalises CQS to message-driven and event-driven architectures: it applies the CQS principle by using separate Query and Command messages to retrieve and modify data, respectively.

Wikipedia: Command–query separation

Command Query Responsibility Segregation (CQRS) Pattern

Image source: CQRS (command query responsibility segregation)

Motivation

Can we use Wechaty by only sending / receiving the Plain Old JavaScript Object (POJO)?

That's an Event-driven way, which will give us the following benifites:

  1. Better integration with Domain-driven Design (DDD)
  2. Decouple the sub-systems with the Wechaty instance completely
  3. Enable using Wechaty with Microservices
  4. Make it possible for providing an API endpoint with JSON request/responses
  5. etc.

So we decided to support the Event-driven Architecture by enabling the Event-driven Programming with Wechaty by publishing the wechaty-cqrs NPM module.

Features

  1. Convert Wechaty instance to a messaging bus$ with the from() function.
  2. Well-defined commands, queries, responses, and events payload creators.
  3. A great execute$() helper function for sending the events to the bus and get back the response.
  4. Well-defined events$ for the Wechaty events
  5. Well-defined sayables for build all the message contents
  6. Static typing with TypeScript with all events & streams
  7. Working perfect with the powerful RxJS

Usage

Install

npm install wechaty-cqrs wechaty

Quick start

Here's the CQRS version of the Wechaty bot usage:

import * as CQRS    from 'wechaty-cqrs'
import * as WECHATY from 'wechaty'
import { filter, map, mergeMap }  from 'rxjs/operators'

const wechaty = WECHATY.WechatyBuilder.build()
await wechaty.init()

const bus$ = CQRS.from(wechaty)

bus$.pipe(
  filter(CQRS.is(CQRS.events.MessageReceivedEvent)),
  // MessageReceivedEvent -> Sayable
  map(messageId => CQRS.queries.GetSayablePayloadQuery(
    messageReceivedEvent.meta.puppetId,
    messageId,Diagrams
  )),
  mergeMap(CQRS.execute$(bus$)),
  // Log `sayable` to console
).subscribe(sayable =>
  console.info('Sayable:', sayable),
)

bus$.next(CQRS.commands.StartCommand(wechaty.puppet.id))

Learn how to build a Ding Dong BOT with CQRS from our examples/ding-dong-bot.ts

Getting Started

Here's a video introduction for CQRS Wechaty with live demo, presented by Huan:

CQRS Wechaty Getting Started

YouTube: https://youtu.be/kauxyPVa0jo

The getting started ding-dong-bot.ts in the video: https://github.com/wechaty/getting-started/blob/main/examples/cqrs/ding-dong-bot.ts

Architecture Diagrams

CQRS Events Structure

graph LR
  classDef event fill:DarkGoldenRod
  classDef command fill:blue
  classDef query fill:green
Diagrams
  subgraph Command
    C(VerbNounCommand):::command
  end

  subgraph Response
    RC(VerbNounCommandResponse)
    RQ(GetNounQueryResponse)
  end
    
  subgraph Query
    Q(GetNounQuery):::query
  end

  subgraph Event
    ER(ReceivedEvent):::event
  end

  C-->RC

  ER-->ER

  Q-->RQ

Command

sequenceDiagram
    participant Bus
    participant Redux
    participant Wechaty

    Bus->>Redux: ExecuteCommand
    Redux->>Wechaty: Call
    Wechaty->>Redux: Call Return (void)
    Redux->>Bus: ExecuteCommandResponse

Query

sequenceDiagram
    participant Bus
    participant Redux
    participant Wechaty

    Bus->>Redux: GetNounQuery
    Redux->>Wechaty: Call
    Wechaty->>Redux: Call Return (value)
    Redux->>Bus: GetNounQueryResponse

Event

sequenceDiagram
    participant Bus
    participant Redux
    participant Wechaty

    Wechaty->>Redux: ReceivedEvent
    Redux->>Bus: ReceivedEvent

Data Transfer Object (DTO)

A Data Transfer Object (DTO) is an object that carries data between processes.

CQRS Wechaty has encapsulated all the events to DTOs, exported by:

// You will get DTOs from CQRS.{commands,queries} module
import * as CQRS from 'wechaty-cqrs'

/**
 * Examples: building Data Transfer Object for
 *  - `DingCommand`
 *  - `GetIsLoggedInQuery
 */
const dingCommand         = CQRS.commands.DingCommand(...)
const getIsLoggedInQuery  = CQRS.queries.GetIsLoggedInQuery(...)

// Use them as you needed

Learn more from the source code

API Reference

Read CQRS Wechaty API Reference at: https://paka.dev/npm/wechaty-cqrs

How to contribute

How to add a new command/query

The following steps need to be followed:

  1. Duck
    1. Add a new type to the commands or queries
    2. Add a new action to the request and response
    3. Add a new epic to call the async API
  2. DTO
    1. Export new added Command/Query from commands/queries
    2. Export new added Response from responses
  3. Writing unit tests.

Blogs

Resources

History

main v1.15 (Apr 20, 2022)

  1. rename all duck events' name from camelCase to SNAKE_CASE. (i.e. dingCommand -> DING_COMMAMD)

v1.12 (Mar 27, 2022)

  1. Classify action builders so that they will be compatible with Class events with NestJS #1
  2. execute$() helper function for sending the events to the bus and get back the response, with automatically type inferring.
  3. lots of typing enhancements.

Learn more from PR #3

v0.10 (Mar 17) Beta release

Author

Huan LI (李卓桓), Microsoft Regional Director, zixia@zixia.net

Profile of Huan LI (李卓桓) on StackOverflow

Copyright & License