在Node.js中调度CQRS消息

在Node.js中调度CQRS消息,node.js,rxjs,cqrs,mediator,mediatr,Node.js,Rxjs,Cqrs,Mediator,Mediatr,我想为一个节点应用程序做CQR 我不是节点人,我来自.NET,它有一个名为MediatR的优秀库,它将命令/查询分派给中介,中介可以用来分离请求和处理程序。因此,它允许非常简单和优雅的CQR 在节点世界中,我发现了许多库/博客,但它们也总是包含事件源。我对这本书不感兴趣 我可以很好地对命令和查询进行建模,但接下来呢?他们需要被分派到某个地方,以一种解耦的方式避免混乱 据我目前对节点平台的了解,一个可能的解决方案是使用observer模式(通过RxJs库),这样控制器就可以向observer发送消

我想为一个节点应用程序做CQR

我不是节点人,我来自.NET,它有一个名为MediatR的优秀库,它将命令/查询分派给中介,中介可以用来分离请求和处理程序。因此,它允许非常简单和优雅的CQR

在节点世界中,我发现了许多库/博客,但它们也总是包含事件源。我对这本书不感兴趣

我可以很好地对命令和查询进行建模,但接下来呢?他们需要被分派到某个地方,以一种解耦的方式避免混乱

据我目前对节点平台的了解,一个可能的解决方案是使用observer模式(通过RxJs库),这样控制器就可以向observer发送消息(即CQRS请求),然后observer为订阅者发布相应的事件(即请求处理程序)。这在类似DDD的设计中解耦了控制器和服务。虽然我不知道如何将结果传递回控制器


其他人就是这样做的吗?在Node中有更好的方法吗?

TL:DR:应用CQRS体系结构不需要一些花哨的框架,特别是当您只进行进程内通信时。来自
事件
模块的本机已足够。如果您想要进程间通信,那么它确实做得很好。要查看一个实现示例(以下长版本答案),您可以深入了解此存储库的代码:

让我们以一个非常简单的聊天应用程序为例,在该应用程序中,如果聊天未关闭,您可以发送消息,并且喜欢/不喜欢消息

我们的主要聚合(或聚合根,概念上)是
Chat
writeModel/domain/Chat.js
):

然后,我们有一个聚合(
writeModel/domain/Message.js
):

发送消息的行为可以是(
writeModel/domain/chat.js
):

我们现在需要命令(
writeModel/domain/commands.js
):

因为我们使用的是javascript,所以我们没有提供抽象的
接口
,所以我们使用
高阶函数
writeModel/domain/getChatOfId.js
):

writeModel/domain/saveMessage.js
):

我们现在需要实现我们的
命令处理程序
(应用程序服务层):

writeModel/commandHandlers/handleSendMessage.js

由于javascript中没有
接口
,因此我们使用
高阶函数
通过在运行时注入依赖项来应用依赖项反转原则

然后,我们可以实现write模型的合成根:(`writeModel/index.js):

您的
命令
命令处理程序
没有绑定在一起,您可以在运行时提供这些函数的实现,例如,使用内存中的数据库和节点
EventEmitter
writeModel/infrastructure/inMemory/index.js
):

以及我们的
TestWriteModel
将其捆绑在一起:

const EventEmitter = require('events');
const { SimpleNodeCQRSwriteModel } = require('../writeModel');
const { InMemoryRepository } = require('../writeModel/infrastructure/inMemory');

const TestWriteModel = () => {
  const { saveMessage, getChatOfId, getNextMessageId } = InMemoryRepository();
  const commandEmitter = new EventEmitter();
  const dispatchCommand = command => commandEmitter.emit(command.type, command.payload);
  const handleCommand = (commandType, commandHandler) => {
    commandEmitter.on(commandType, commandHandler);
  };
  return SimpleNodeCQRSwriteModel({
    dispatchCommand,
    handleCommand,
    getChatOfId,
    getNextMessageId,
    saveMessage,
  });
};

您可以深入研究此存储库中的代码(使用非常简单的
读取模型)

TL:DR:应用CQRS体系结构不需要一些奇特的框架,特别是当您只进行进程内通信时。来自
事件
模块的本机已足够。如果您想要进程间通信,那么它确实做得很好。要查看一个实现示例(以下长版本答案),您可以深入了解此存储库的代码:

让我们以一个非常简单的聊天应用程序为例,在该应用程序中,如果聊天未关闭,您可以发送消息,并且喜欢/不喜欢消息

我们的主要聚合(或聚合根,概念上)是
Chat
writeModel/domain/Chat.js
):

然后,我们有一个聚合(
writeModel/domain/Message.js
):

发送消息的行为可以是(
writeModel/domain/chat.js
):

我们现在需要命令(
writeModel/domain/commands.js
):

因为我们使用的是javascript,所以我们没有提供抽象的
接口
,所以我们使用
高阶函数
writeModel/domain/getChatOfId.js
):

writeModel/domain/saveMessage.js
):

我们现在需要实现我们的
命令处理程序
(应用程序服务层):

writeModel/commandHandlers/handleSendMessage.js

由于javascript中没有
接口
,因此我们使用
高阶函数
通过在运行时注入依赖项来应用依赖项反转原则

然后,我们可以实现write模型的合成根:(`writeModel/index.js):

您的
命令
命令处理程序
没有绑定在一起,您可以在运行时提供这些函数的实现,例如,使用内存中的数据库和节点
EventEmitter
writeModel/infrastructure/inMemory/index.js
):

以及我们的
TestWriteModel
将其捆绑在一起:

const EventEmitter = require('events');
const { SimpleNodeCQRSwriteModel } = require('../writeModel');
const { InMemoryRepository } = require('../writeModel/infrastructure/inMemory');

const TestWriteModel = () => {
  const { saveMessage, getChatOfId, getNextMessageId } = InMemoryRepository();
  const commandEmitter = new EventEmitter();
  const dispatchCommand = command => commandEmitter.emit(command.type, command.payload);
  const handleCommand = (commandType, commandHandler) => {
    commandEmitter.on(commandType, commandHandler);
  };
  return SimpleNodeCQRSwriteModel({
    dispatchCommand,
    handleCommand,
    getChatOfId,
    getNextMessageId,
    saveMessage,
  });
};

您可以深入研究此存储库中的代码(使用非常简单的
读取模型
):

您想保持进程内的通信,还是想拥有某种其他服务?@PierreCriulanscy我更喜欢保持简单,只拥有一个进程(即节点),并避免rabbitmq、redis、,因此,您可以使用非常轻量级的
redux
,您可以在
redux
中间件中调度“命令”并拦截它们。我不知道你是否熟悉
redux
。你也可以看看,但它也是事件源代码。@PierreCriulanscy不幸的是,现在已经没有类似的东西了。我会调查redux。。。希望它足够好。谢谢。你想保持沟通吗
const invariant = require('invariant');
const { Message } = require('./message');

const Chat = ({ id, isClosed } = {}) =>
  Object.freeze({
    id,
    isClosed,
  });

const sendMessage = ({ chatState, messageId, userId, content, sentAt }) => {
  invariant(!chatState.isClosed, "can't post in a closed chat");
  return Message({ id: messageId, chatId: chatState.id, userId, content, sentAt });
};
const commands = {
  types: {
    SEND_MESSAGE: '[chat] send a message',
  },
  sendMessage({ chatId, userId, content, sentAt }) {
    return Object.freeze({
      type: commands.types.SEND_MESSAGE,
      payload: {
        chatId,
        userId,
        content,
        sentAt,
      },
    });
  },
};

module.exports = {
  commands,
};
const { Chat } = require('./message');

const getChatOfId = (getChatOfId = async id => Chat({ id })) => async id => {
  try {
    const chatState = await getChatOfId(id);
    if (typeof chatState === 'undefined') {
      throw chatState;
    }
    return chatState;
  } catch (e) {
    throw new Error(`chat with id ${id} was not found`);
  }
};

module.exports = {
  getChatOfId,
};
const { Message } = require('./message');

const saveMessage = (saveMessage = async (messageState = Message()) => {}) => saveMessage;

module.exports = {
  saveMessage,
};
const { sendMessage } = require('../domain/chat');

const handleSendMessage = ({
  getChatOfId,
  getNextMessageId,
  saveMessage,
}) => async sendMessageCommandPayload => {
  const { chatId, userId, content, sentAt } = sendMessageCommandPayload;
  const chat = await getChatOfId(chatId);
  return saveMessage(
    sendMessage({
      chatState: chat,
      messageId: getNextMessageId(),
      userId,
      content,
      sentAt,
    }),
  );
};

module.exports = {
  handleSendMessage,
};
const { handleSendMessage } = require('./commandHandlers/handleSendMessage');
const { commands } = require('./domain/commands');

const SimpleNodeCQRSwriteModel = ({
  dispatchCommand,
  handleCommand,
  getChatOfId,
  getNextMessageId,
  saveMessage,
}) => {
  handleCommand(
    commands.types.SEND_MESSAGE,
    handleSendMessage({ getChatOfId, getNextMessageId, saveMessage }),
  );
};

module.exports = {
  SimpleNodeCQRSwriteModel,
};
const uuid = require('uuid/v1');
const { saveMessage } = require('../../domain/saveMessage');
const { getChatOfId } = require('../../domain/getChatOfId');
const { getNextMessageId } = require('../../domain/getNextMessageId');

const InMemoryRepository = (initialDbState = { chats: {}, messages: {}, users: {} }) => {
  const listeners = [];

  const db = {
    ...initialDbState,
  };

  const addOnDbUpdatedListener = onDbUpdated => listeners.push(onDbUpdated);

  const updateDb = updater => {
    updater();
    listeners.map(listener => listener(db));
  };

  const saveMessageInMemory = saveMessage(async messageState => {
    updateDb(() => (db.messages[messageState.id] = messageState));
  });

  const getChatOfIdFromMemory = getChatOfId(async id => db.chats[id]);

  const getNextMessageUuid = getNextMessageId(uuid);

  return {
    addOnDbUpdatedListener,
    saveMessage: saveMessageInMemory,
    getChatOfId: getChatOfIdFromMemory,
    getNextMessageId: getNextMessageUuid,
  };
};

module.exports = {
  InMemoryRepository,
};
const EventEmitter = require('events');
const { SimpleNodeCQRSwriteModel } = require('../writeModel');
const { InMemoryRepository } = require('../writeModel/infrastructure/inMemory');

const TestWriteModel = () => {
  const { saveMessage, getChatOfId, getNextMessageId } = InMemoryRepository();
  const commandEmitter = new EventEmitter();
  const dispatchCommand = command => commandEmitter.emit(command.type, command.payload);
  const handleCommand = (commandType, commandHandler) => {
    commandEmitter.on(commandType, commandHandler);
  };
  return SimpleNodeCQRSwriteModel({
    dispatchCommand,
    handleCommand,
    getChatOfId,
    getNextMessageId,
    saveMessage,
  });
};