领域驱动 | 事件驱动 | 测试驱动 | 声明式设计 | 响应式编程 | 命令查询职责分离 | 事件溯源

架构图

事件源

可观测性

OpenAPI (Spring WebFlux 集成)

自动注册 命令 路由处理函数(HandlerFunction) ,开发人员仅需编写领域模型,即可完成服务开发。

测试套件:80%+ 的测试覆盖率轻而易举

Given -> When -> Expect .

前置条件

  • 理解 领域驱动设计:《实现领域驱动设计》、《领域驱动设计:软件核心复杂性应对之道》
  • 理解 命令查询职责分离(CQRS)
  • 理解 事件源架构
  • 理解 响应式编程

特性

  • Aggregate Modeling

    • Single Class
    • Inheritance Pattern
    • Aggregation Pattern
  • Saga Modeling
    • StatelessSaga
  • Test Suite
    • 兼容性测试规范(TCK)
    • AggregateVerifier
    • SagaVerifier
  • EventSourcing
    • EventStore

      • MongoDB (Recommend)
      • R2dbc
        • Database Sharding
        • Table Sharding
      • Redis
    • Snapshot
      • MongoDB
      • R2dbc
        • Database Sharding
        • Table Sharding
      • ElasticSearch
      • Redis (Recommend)
  • 命令等待策略(WaitStrategy)
    • SENT : 命令发送成功后发送完成信号
    • PROCESSED : 命令处理完成后发送完成信号
    • SNAPSHOT : 快照生成完成后发送完成信号
    • PROJECTED : 命令产生的事件被投影后发送完成信号
  • CommandBus
    • InMemoryCommandBus
    • KafkaCommandBus (Recommend)
    • RedisCommandBus
    • LocalFirstCommandBus
  • DomainEventBus
    • InMemoryDomainEventBus
    • KafkaDomainEventBus (Recommend)
    • RedisDomainEventBus
    • LocalFirstDomainEventBus
  • StateEventBus
    • InMemoryStateEventBus
    • KafkaStateEventBus (Recommend)
    • RedisStateEventBus
    • LocalFirstStateEventBus
  • Spring 集成
    • Spring Boot Auto Configuration
    • Automatically register CommandAggregate to RouterFunction
  • 可观测性
    • OpenTelemetry
  • OpenAPI
  • WowMetadata Generator
    • wow-compiler

Example

Example

单元测试套件

80%+ 的测试覆盖率轻而易举。

Given -> When -> Expect .

Aggregate Unit Test (AggregateVerifier)

Aggregate Test

internal class OrderTest {

    private fun mockCreateOrder(): VerifiedStage<OrderState> {
val tenantId = GlobalIdGenerator.generateAsString()
val customerId = GlobalIdGenerator.generateAsString() val orderItem = OrderItem(
GlobalIdGenerator.generateAsString(),
GlobalIdGenerator.generateAsString(),
BigDecimal.valueOf(10),
10,
)
val orderItems = listOf(orderItem)
val inventoryService = object : InventoryService {
override fun getInventory(productId: String): Mono<Int> {
return orderItems.filter { it.productId == productId }.map { it.quantity }.first().toMono()
}
}
val pricingService = object : PricingService {
override fun getProductPrice(productId: String): Mono<BigDecimal> {
return orderItems.filter { it.productId == productId }.map { it.price }.first().toMono()
}
}
return aggregateVerifier<Order, OrderState>(tenantId = tenantId)
.inject(DefaultCreateOrderSpec(inventoryService, pricingService))
.given()
.`when`(CreateOrder(customerId, orderItems, SHIPPING_ADDRESS, false))
.expectEventCount(1)
.expectEventType(OrderCreated::class.java)
.expectStateAggregate {
assertThat(it.aggregateId.tenantId, equalTo(tenantId))
}
.expectState {
assertThat(it.id, notNullValue())
assertThat(it.customerId, equalTo(customerId))
assertThat(it.address, equalTo(SHIPPING_ADDRESS))
assertThat(it.items, equalTo(orderItems))
assertThat(it.status, equalTo(OrderStatus.CREATED))
}
.verify()
} /**
* 创建订单
*/
@Test
fun createOrder() {
mockCreateOrder()
} @Test
fun createOrderGivenEmptyItems() {
val customerId = GlobalIdGenerator.generateAsString()
aggregateVerifier<Order, OrderState>()
.inject(mockk<CreateOrderSpec>(), "createOrderSpec")
.given()
.`when`(CreateOrder(customerId, listOf(), SHIPPING_ADDRESS, false))
.expectErrorType(IllegalArgumentException::class.java)
.expectStateAggregate {
/*
* 该聚合对象处于未初始化状态,即该聚合未创建成功.
*/
assertThat(it.initialized, equalTo(false))
}.verify()
} /**
* 创建订单-库存不足
*/
@Test
fun createOrderWhenInventoryShortage() {
val customerId = GlobalIdGenerator.generateAsString()
val orderItem = OrderItem(
GlobalIdGenerator.generateAsString(),
GlobalIdGenerator.generateAsString(),
BigDecimal.valueOf(10),
10,
)
val orderItems = listOf(orderItem)
val inventoryService = object : InventoryService {
override fun getInventory(productId: String): Mono<Int> {
return orderItems.filter { it.productId == productId }
/*
* 模拟库存不足
*/
.map { it.quantity - 1 }.first().toMono()
}
}
val pricingService = object : PricingService {
override fun getProductPrice(productId: String): Mono<BigDecimal> {
return orderItems.filter { it.productId == productId }.map { it.price }.first().toMono()
}
} aggregateVerifier<Order, OrderState>()
.inject(DefaultCreateOrderSpec(inventoryService, pricingService))
.given()
.`when`(CreateOrder(customerId, orderItems, SHIPPING_ADDRESS, false))
/*
* 期望:库存不足异常.
*/
.expectErrorType(InventoryShortageException::class.java)
.expectStateAggregate {
/*
* 该聚合对象处于未初始化状态,即该聚合未创建成功.
*/
assertThat(it.initialized, equalTo(false))
}.verify()
} /**
* 创建订单-下单价格与当前价格不一致
*/
@Test
fun createOrderWhenPriceInconsistency() {
val customerId = GlobalIdGenerator.generateAsString()
val orderItem = OrderItem(
GlobalIdGenerator.generateAsString(),
GlobalIdGenerator.generateAsString(),
BigDecimal.valueOf(10),
10,
)
val orderItems = listOf(orderItem)
val inventoryService = object : InventoryService {
override fun getInventory(productId: String): Mono<Int> {
return orderItems.filter { it.productId == productId }.map { it.quantity }.first().toMono()
}
}
val pricingService = object : PricingService {
override fun getProductPrice(productId: String): Mono<BigDecimal> {
return orderItems.filter { it.productId == productId }
/*
* 模拟下单价格、商品定价不一致
*/
.map { it.price.plus(BigDecimal.valueOf(1)) }.first().toMono()
}
}
aggregateVerifier<Order, OrderState>()
.inject(DefaultCreateOrderSpec(inventoryService, pricingService))
.given()
.`when`(CreateOrder(customerId, orderItems, SHIPPING_ADDRESS, false))
/*
* 期望:价格不一致异常.
*/
.expectErrorType(PriceInconsistencyException::class.java).verify()
}
}

Saga Unit Test (SagaVerifier)

Saga Test

class CartSagaTest {

    @Test
fun onOrderCreated() {
val orderItem = OrderItem(
GlobalIdGenerator.generateAsString(),
GlobalIdGenerator.generateAsString(),
BigDecimal.valueOf(10),
10,
)
sagaVerifier<CartSaga>()
.`when`(
mockk<OrderCreated> {
every {
customerId
} returns "customerId"
every {
items
} returns listOf(orderItem)
every {
fromCart
} returns true
},
)
.expectCommandBody<RemoveCartItem> {
assertThat(it.id, equalTo("customerId"))
assertThat(it.productIds, hasSize(1))
assertThat(it.productIds.first(), equalTo(orderItem.productId))
}
.verify()
}
}

设计

聚合建模

Single Class Inheritance Pattern Aggregation Pattern

加载聚合

聚合状态流

发送命令

命令与事件流

Saga - OrderProcessManager (Demo)

Wow: 基于 DDD、EventSourcing 的现代响应式 CQRS 架构微服务开发框架的更多相关文章

  1. WeText项目:一个基于.NET实现的DDD、CQRS与微服务架构的演示案例

    最近出于工作需要,了解了一下微服务架构(Microservice Architecture,MSA).我经过两周业余时间的努力,凭着自己对微服务架构的理解,从无到有,基于.NET打造了一个演示微服务架 ...

  2. NET实现的DDD、CQRS与微服务架构

    WeText项目:一个基于.NET实现的DDD.CQRS与微服务架构的演示案例 最近出于工作需要,了解了一下微服务架构(Microservice Architecture,MSA).我经过两周业余时间 ...

  3. 一款基于jquery和css3的响应式二级导航菜单

    今天给大家分享一款基于jquery和css3的响应式二级导航菜单,这款导航是传统的基于顶部,鼠标经过的时候显示二级导航,还采用了当前流行的响应式设计.效果图如下: 在线预览   源码下载 实现的代码. ...

  4. 一款基于jquery带百分比的响应式进度加载条

    今天要给大家带来一款基于jquery带百分比的响应式进度加载条.这款加载条非常漂亮,而且带有进度的百度比,且在不同的百分比用的是不同的颜色.而且这款加载条采用了响应式设计,在不同的分辨率的显示器下完美 ...

  5. 基于screen.width的伪响应式开发

    一.站在用户的角度看问题 一个用户,访问一个web页面的真实场景是怎样的呢? 下面是某用户访问某站点的一个场景: 1. 小明打开了自己的电脑,访问了鑫空间-鑫生活: 2. 小明体内洪荒之力无法控制,疯 ...

  6. 基于rem的移动端响应式适配方案(详解) 移动端H5页面的设计稿尺寸大小规范

    基于rem的移动端响应式适配方案(详解) : https://www.jb51.net/article/118067.htm 移动端H5页面的设计稿尺寸大小规范 http://www.tuyiyi.c ...

  7. Pizza Pie Charts – 基于 Snap SVG 框架的响应式饼图

    Pizza Pie Charts 是一个基于 Adobe 的 Snap SVG 框架的响应式饼图插件.它着重于集成 HTML 标记和 CSS,而不是 JavaScript 对象,当然Pizza Pie ...

  8. 基于REM的移动端响应式适配方案

    视口 在前一段时间,我曾经写过一篇关于viewport的文章.最近由于在接触移动端开发,对viewport有了新的理解.于是,打算重新写一篇文章,介绍移动端视口的相关概念. 关于这篇文章说到的所有知识 ...

  9. 基于Spring Boot、Spring Cloud、Docker的微服务系统架构实践

    由于最近公司业务需要,需要搭建基于Spring Cloud的微服务系统.遍访各大搜索引擎,发现国内资料少之又少,也难怪,国内Dubbo正统治着天下.但是,一个技术总有它的瓶颈,Dubbo也有它捉襟见肘 ...

  10. 基于spring boot2.0+spring security +oauth2.0+ jwt微服务架构

    github地址:https://github.com/hankuikuide/microservice-spring-security-oauth2 项目介绍 该项目是一个演示项目,主要演示了,基于 ...

随机推荐

  1. 解决PaddlePaddle飞桨在迁移学习使用预训练模型时更改num_classes参数出现警告

    当我们使用 PaddlePaddle 进行迁移学习的时候,直接导入模型虽然是可以的,但是总是会有个警告 如直接用官方的 resnet101 并加载预训练模型的话 model = paddle.visi ...

  2. #Powerquery 数据结构基础 一维数据与二维数据

    本文参考了采悟老师的文章,推荐大家看原文,本文为笔记随笔 https://mp.weixin.qq.com/s?__biz=MzA4MzQwMjY4MA==&mid=2484068871&am ...

  3. allure测试报告美化与定制

    一份简单的测试报告 一份简单的测试报告可以使用pytest的插件html就可以生成, demo如下 先下载 pip install pytest-html 下载完之后,在当前执行过测试用例的测试目录下 ...

  4. Linux系统下CUDA和cuDNN环境配置

    本人配置环境:linux服务器,ubantu18,显卡驱动11.0,安装CUDA11.0和cuDNN8.2.1. 一.安装CUDA11.0 1.先多找几篇博客,了解大概的流程,避免踩坑. 2.官网下载 ...

  5. ACM中的java的使用;

    java大法好,退C保平......开玩笑的: 1.头文件: import java.math.*; // 包含大数类的包 import java.util.*; // 包含输入头的包 2.程序主体, ...

  6. only仅显示一些字段

    only仅显示一些字段 仅显示nickname,age两列的数据 Student.objects.all().only('nickname','age')

  7. 用好kafka,你不得不知的那些工具

    前言 工欲善其事,必先利其器.本文主要分享一下消息中间件kafka安装部署的过程,以及我平时在工作中针对kafka用的一些客户端工具和监控工具. kafka部署架构 一个kafka集群由多个kafka ...

  8. 我写了本开源书:《3D编程模式》

    大家好,我写了本开源书,罗列了我从自己的实战项目中提炼出来的关于3D编程(主要包括"3D引擎/游戏引擎"."编辑器"开发)的各种编程模式 本书的在线阅读地址在这 ...

  9. Java网络编程----通过实现简易聊天工具来聊聊NIO

    前文我们说过了BIO,今天我们聊聊NIO.NIO 是什么?NIO官方解释它为New lO,由于其特性我们也称之为,Non-Blocking IO.这是jdk1.4之后新增的一套IO标准.为什么要用NI ...

  10. 如何通过Java代码将 PDF文档转为 HTML格式

    虽然PDF文件适合用于打印和发布,但不适合所有类型的文档.例如,包含复杂图表和图形的文档可能无法在PDF中呈现得很好.但是HTML文件可以在任何可运行浏览器的计算机上进行阅读并显示.并且HTML还具有 ...