一、简单入门之入门

  CQRS/ES和领域驱动设计更搭,故整体分层沿用经典的DDD四层。其实要实现的功能概要很简单,如下图。

  基础框架选择了https://github.com/looplab/eventhorizon,该框架功能强大、示例都挺复杂的,囊括的概念太多,不太适合入门,所以决定在其基础上,进行简化。

二、简化使用eventhorizon

  Eventhorizon已经提供了详尽的使用案例(https://github.com/looplab/eventhorizon/tree/master/examples),只是概念比较多,也不符合之前的一般使用方式,故按照概要图进行简化使用。

1.presentation

  使用github.com/gin-gonic/gin,路由功能等,与业务无关都可以委托出去,同时抽象了一个核心的函数,作为衔接presentation 和application层。

从gin上下文中读取输入数据,并根据约定的Command Key,转交给application层进行相应的Command解析。

 func handles(command string) gin.HandlerFunc {
return func(c *gin.Context) {
data, err := c.GetRawData()
if err != nil {
c.JSON(http.StatusBadRequest, "")
return
}
result := application.HandCommand(data, command)
c.JSON(http.StatusOK, result)
}
}

2. application

  Application很薄的一层,依然是与业务无关的,重点在于将计算机领域的数据、模型,转换为业务领域建模所需。

  核心函数依然只有一个,主要功能为:创建正确的Command;将presentation层传递上来数据转为为领域层所需要的模型(Command来承载);委托“命令总线”发布命令,不必关心命令的接收方会怎样,解除对命令执行方的依赖,只关心命令是否正确发送出去;向presentation层报告命令发布情况。

//api2Cmd  路由到领域CMD的映射
var api2Cmd map[string]eh.CommandType type Result struct {
Succ bool `json:"success"`
Code int `json:"code"`
Msg string `json:"msg"` // message
Data interface{} `json:"data"` // data object
} func HandCommand(postBody []byte, commandKey string) (result Result) {
cmd, err := eh.CreateCommand(eh.CommandType(commandKey))
if err != nil {
result.Msg = "could not create command: " + err.Error()
return
}
if err := json.Unmarshal(postBody, &cmd); err != nil {
result.Msg = "could not decode Json" + err.Error()
return
}
ctx := context.Background()
if err := bus.HandleCommand(ctx, cmd); err != nil {
result.Msg = "could not handle command: " + err.Error()
return
} result.Succ = true
result.Msg = "ok" return
}

3. domain

  Domain层,核心的业务逻辑层,不进行累赘的表述,重点需要介绍下domain/Bus。总线也可以放置到infrastructure层,不过根据个人习惯写在了domain层里。

  Domain/Bus,整个CQRS的核心、负责命令、事件的发布、注册等功能。核心功能主要有:命令的注册、命令的执行、事件的注册、事件的发布(异步)和存储、EventStore的构建等。核心功能和方法如下:

//commandBus 命令总线
var commandBus = bus.NewCommandHandler() //eventBus 事件总线
var eventBus = eventbus.NewEventBus(nil) //
var eventStore eh.EventStore //aggregateStore 领域事件存储与发布
//var AggregateStore *events.AggregateStore func InitBus() {
eventStore, _ = eventstore.NewEventStore("127.0.0.1:27017", "EventStore")
//AggregateStore, _ = events.NewAggregateStore(eventStore, eventBus)
} //RegisterHandler 注册命令的处理
func RegisterHandler(cmd eh.CommandType, cmdHandler eh.Aggregate) {
err := commandBus.SetHandler(cmdHandler, cmd)
if err != nil {
panic(err)
}
} //HandleCommand 命令的执行
func HandleCommand(ctx context.Context, cmd eh.Command) error {
return commandBus.HandleCommand(ctx, cmd)
} //RegisterEventHandler 注册事件的处理
func RegisterEventHandler(evtMatcher eh.EventMatcher, evtHandler eh.EventHandler) {
eventBus.AddHandler(evtMatcher, evtHandler)
} //RaiseEvents 异步进行事件的存储 和 发布
func RaiseEvents(ctx context.Context, events []eh.Event, originalVersion int) error {
go eventStore.Save(ctx, events, originalVersion)
for _, event := range events {
err := eventBus.PublishEvent(ctx, event)
if err != nil {
return err
}
} return nil
}

4. infrastructure

  由于是简单入门infrastructure层进行了抽象简化,提供基本的仓储功能。领域层进行业务处理根据所需进行数据的持久化以及读取等。

三、简要总结

  一种方法是使其足够简单以至于不存在明显的缺陷,另外一种是使其足够复杂以至于看不出有什么问题。

  以上组合已经能够支持最基础的CQRS/ES的概念和功能了。

  现在看看如何利用已有的东西,对具体业务进行融合。

四、小试牛刀

  支付项目中第三方支付公司需要和客户进行签约。需要调用支付公司的接口将用户提交的基本信息提交给支付公司,支付公司发送验证码并告知用户须知,签约成功之后需要将协约基本信息进行保存,以后使用该协约进行代收付等资金业务。

  单纯演示,将概要设计简化如下:获取客户端提交的用户信息,校验数据,调用第三方支付的接口,持久化到SQLite数据库,激活领域事件存储到MongoDB,领域事件的处理。

1. presentation

  这里偷懒,没有进行API路由和命令的映射,统一使用了"/api/sign_protocol"。核心代码注册API。

    signProtocolAPI := "/api/sign_protocol"
router.POST(signProtocolAPI, handles(signProtocolAPI))

2. application

Application层不需要额外代码

3. domain

domain层只需要commands.go、protocol.go;代码也很简单,command主要两个功能承载入参、承接应用层到聚合根。

func init() {
eh.RegisterCommand(func() eh.Command { return &SignProtocol{} })
} const (
SignCommand eh.CommandType = "/api/sign_protocol"
) type SignProtocol struct {
ID uuid.UUID
//通道号
AisleType string `json:"AisleType"`
//银行code,各平台不一样
BankCode string `json:"BankCode"`
//账户类型
AccountType string `json:"AccountType"`
//账户属性
AccountProp string `json:"AccountProp"`
//银行卡号
BankCardNo string `json:"BankCardNo"`
//预留手机号
ReservePhone string `json:"Tel"`
//银行卡预留的证件类型
IDCardType string `json:"IDType"`
//银行卡开户姓名
CardName string `json:"CardName"`
//银行卡预留的证件号码
IDCardNo string `json:"IDCardNo"`
//提示标识
Merrem string `json:"Merrem"`
//备注
Remark string `json:"Remark"`
} func (c SignProtocol) AggregateID() uuid.UUID { return c.ID }
func (c SignProtocol) CommandType() eh.CommandType { return SignCommand }
func (c SignProtocol) AggregateType() eh.AggregateType { return "" } //Command需要知道具体Aggregate的存在,貌似不甚合理呀!!!

protocol.go聚合根,主要的业务逻辑。这里也很简单,进行领域服务请求、并且进行持久化。

func init() {
prdctAgg := &ProtocolAggregate{
AggregateBase: events.NewAggregateBase("ProtocolAggregate", uuid.New()),
}
bus.RegisterHandler(SignCommand, prdctAgg)
} type ProtocolAggregate struct {
*events.AggregateBase
} var _ = eh.Aggregate(&ProtocolAggregate{}) func (a *ProtocolAggregate) HandleCommand(ctx context.Context, cmd eh.Command) error {
switch cmd := cmd.(type) {
case *SignProtocol:
//命令只需要确定输入参数满足业务校验即可
err := a.CheckSign()
if err != nil {
return err
}
//实际的业务可以异步进行处理
go a.CfrmSign(cmd) return nil
}
return fmt.Errorf("couldn't handle command")
} func (a *ProtocolAggregate) CheckSign() error {
//校验输入参数是否有效,
return nil
} func (a *ProtocolAggregate) CfrmSign(cmd *SignProtocol) error { protocolOrm := &interfaces.ProtocolOrm{
ProtocolNo: uuid.New().String(),
AisleType: cmd.AisleType,
BankCode: cmd.BankCode,
BankCardNo: cmd.BankCardNo,
ReservePhone: cmd.ReservePhone,
CardName: cmd.CardName,
IDCardNo: cmd.IDCardNo,
Merrem: cmd.Merrem,
Remark: cmd.Remark,
Status: 1,
}
protocolOrm.AccountType, _ = strconv.Atoi(cmd.AccountType)
protocolOrm.AccountProp, _ = strconv.Atoi(cmd.AccountProp)
protocolOrm.IDCardType, _ = strconv.Atoi(cmd.IDCardType) //这里本应该的业务逻辑请求,通过领域服务
//result := domainser.Allinpay.SignGetCode(protocolOrm) protocolRepo := infrastructure.RepoFac.ProtocolRepo
_, err := protocolRepo.Add(protocolOrm) if err != nil {
return err
}
ctx := context.Background()
//业务处理成功后,激活领域事件
bus.RaiseEvents(ctx, a.Events(), 0) return nil
}

4. infrastructure

Infrastructure一般的持久化。

五、一句啰嗦

  麻雀虽小五脏俱全,很小的一golang项目(https://github.com/KendoCross/kendocqrs),入门CQRS是足够的。掌握了核心的基础,稍微融会贯通、举一反三其实就可以组合出大项目了。

CQRS简单入门(Golang)的更多相关文章

  1. 数据结构和算法(Golang实现)(1)简单入门Golang-前言

    数据结构和算法在计算机科学里,有非常重要的地位.此系列文章尝试使用 Golang 编程语言来实现各种数据结构和算法,并且适当进行算法分析. 我们会先简单学习一下Golang,然后进入计算机程序世界的第 ...

  2. 数据结构和算法(Golang实现)(2)简单入门Golang-包、变量和函数

    包.变量和函数 一.举个例子 现在我们来建立一个完整的程序main.go: // Golang程序入口的包名必须为 main package main // import "golang&q ...

  3. 数据结构和算法(Golang实现)(3)简单入门Golang-流程控制语句

    流程控制语句 计算机编程语言中,流程控制语句很重要,可以让机器知道什么时候做什么事,做几次.主要有条件和循环语句. Golang只有一种循环:for,只有一种判断:if,还有一种特殊的switch条件 ...

  4. 数据结构和算法(Golang实现)(4)简单入门Golang-结构体和方法

    结构体和方法 一.值,指针和引用 我们现在有一段程序: package main import "fmt" func main() { // a,b 是一个值 a := 5 b : ...

  5. 数据结构和算法(Golang实现)(5)简单入门Golang-接口

    接口 在Golang世界中,有一种叫interface的东西,很是神奇. 一.数据类型 interface{} 如果你事前并不知道变量是哪种数据类型,不知道它是整数还是字符串,但是你还是想要使用它. ...

  6. 数据结构和算法(Golang实现)(6)简单入门Golang-并发、协程和信道

    并发.协程和信道 Golang语言提供了go关键字,以及名为chan的数据类型,以及一些标准库的并发锁等,我们将会简单介绍一下并发的一些概念,然后学习这些Golang特征知识. 一.并发介绍 我们写程 ...

  7. 数据结构和算法(Golang实现)(7)简单入门Golang-标准库

    使用标准库 一.避免重复造轮子 官方提供了很多库给我们用,是封装好的轮子,比如包fmt,我们多次使用它来打印数据. 我们可以查看到其里面的实现: package fmt func Println(a ...

  8. 用IntelliJ IDEA创建Gradle项目简单入门

    Gradle和Maven一样,是Java用得最多的构建工具之一,在Maven之前,解决jar包引用的问题真是令人抓狂,有了Maven后日子就好过起来了,而现在又有了Gradle,Maven有的功能它都 ...

  9. [原创]MYSQL的简单入门

    MYSQL简单入门: 查询库名称:show databases; information_schema mysql test 2:创建库 create database 库名 DEFAULT CHAR ...

随机推荐

  1. linux 文件操作命令 touch、cat、more、less、head、tail

    touch /bin/touch 创建空文件 linux 创建文件可以使用特殊符号,/除外 touch test test1 创建了两个文件touch "test test1" 创 ...

  2. windows操作系统中安装、启动和卸载memcached

    今天总结一下如何在Windows操作系统中安装.启动和卸载memcached:下载地址: http://download.csdn.net/download/wangshuxuncom/8249501 ...

  3. 也许,这样理解HTTPS更容易

    http://kb.cnblogs.com/page/563885/ 本文尝试一步步还原HTTPS的设计过程,以理解为什么HTTPS最终会是这副模样.但是这并不代表HTTPS的真实设计过程.在阅读本文 ...

  4. Python学习---Django关于POST的请求解析源码分析

    当有请求到来之后,先判断请求头content_type是不是[application/x-www-form-urlencoded] --> 如果是则将请求数据赋值给request.body然后解 ...

  5. VMware 11 安装苹果系统

    没事研究了一下虚拟机安装苹果系统 1.下载需要的软件- F, c1 X: e- o1 }& V/ o9 J        1.1 VMware 11 下载和安装* P( R; O6 v1 N! ...

  6. c# Windows Service 桌面上显示UI

    介绍 本文的目的是说明如何从Windows Vista中的服务正确启动交互式进程,以及演示如何以完全管理员权限启动该进程.交互式过程是能够在桌面上显示UI的过程. 本文介绍如何创建一个名为Loader ...

  7. [EffectiveC++]item28:避免返回handles指向对象内部成分

    可以先参考一个帖子:http://bbs.csdn.net/topics/390731394?page=1

  8. 推荐一个好用的以多tab标签方式打开windows CMD的工具

    最近我在做基于nodejs的微服务开发,需要在windows命令行里启动很多微服务.我的windows 10任务栏是这样子的: 我想找一款能像下图Chrome标签页这样打开windows 10 CMD ...

  9. GIT非常见命令使用笔记

    1:修改已经提交N次代码的user.name和user.email 解决我在多电脑间,使用不同账户,git config 的global,system,local配置忽略改动,而添加了多台电脑ssh ...

  10. 通过 Chrome 调试运行在 IOS-safari 上的页面

    本文重点讨论如何在 Windows 系统中通过chrome 浏览器调试运行在 iPhone Safari 浏览器中的网页.如果你有一台 iMac/MacBook,可忽略该文档.iMac 环境下,直接通 ...