Wow v1.15.2 发布,让领域驱动设计变得触手可得
基于 DDD、EventSourcing 的现代响应式 CQRS 架构微服务开发框架。
领域驱动 | 事件驱动 | 测试驱动 | 声明式设计 | 响应式编程 | 命令查询职责分离 | 事件源
更新内容(v1.15.2) 🎉 🎉 🎉
- 特性:支持
CommandProducerInstrumenter
/EventProducerInstrumenter
- 特性:支持
RedisCommandBus
/RedisDomainEventBus
- 重构:变更
Header
为可变的 - 特性:支持
RedisEventStore
/RedisSnapshotRepository
- 特性:支持
RedisPrepareKey
架构图
事件源
可观测性
前置条件
- 理解 领域驱动设计:《实现领域驱动设计》、《领域驱动设计:软件核心复杂性应对之道》
- 理解 命令查询职责分离(CQRS)
- 理解 事件源架构
- 理解 响应式编程
特性
- Aggregate Modeling
- Single Class
- Inheritance Pattern
- Aggregation Pattern
- Saga Modeling
-
StatelessSaga
-
StatefulSaga
-
- Test Suite
- Test Specification
-
AggregateVerifier
-
StatelessSagaVerifier
- EventSourcing
- EventStream
- MongoDB (Recommend)
- R2dbc
- Database Sharding
- Table Sharding
- Snapshot
- MongoDB (Recommend)
- R2dbc
- Database Sharding
- Table Sharding
- ElasticSearch
- EventStream
- Kafka 集成
-
CommandBus
-
DomainEventBus
-
KafkaSnapshotSink
-
- Spring 集成
- Spring Boot Auto Configuration
- Automatically register
CommandAggregate
toRouterFunction
- 可观测性
- OpenTelemetry
- OpenApi
-
WowMetadata
Generator-
wow-compiler
-
Example
测试套件
80%+ 的测试覆盖率轻而易举。
Given -> When -> Expect .
internal class OrderTest {
companion object {
val SHIPPING_ADDRESS = ShippingAddress("China", "ShangHai", "ShangHai", "HuangPu", "001")
}
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.toFlux().filter { it.productId == productId }.map { it.quantity }.last()
}
}
val pricingService = object : PricingService {
override fun getProductPrice(productId: String): Mono<BigDecimal> {
return orderItems.toFlux().filter { it.productId == productId }.map { it.price }.last()
}
}
return aggregateVerifier<Order, OrderState>(tenantId = tenantId)
.inject(DefaultCreateOrderSpec(inventoryService, pricingService))
.given()
.`when`(CreateOrder(customerId, orderItems, SHIPPING_ADDRESS))
.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 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.toFlux().filter { it.productId == productId }
/*
* 模拟库存不足
*/
.map { it.quantity - 1 }.last()
}
}
val pricingService = object : PricingService {
override fun getProductPrice(productId: String): Mono<BigDecimal> {
return orderItems.toFlux().filter { it.productId == productId }.map { it.price }.last()
}
}
aggregateVerifier<Order, OrderState>()
.inject(DefaultCreateOrderSpec(inventoryService, pricingService))
.given()
.`when`(CreateOrder(customerId, orderItems, SHIPPING_ADDRESS))
/*
* 期望:库存不足异常.
*/
.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.toFlux().filter { it.productId == productId }.map { it.quantity }.last()
}
}
val pricingService = object : PricingService {
override fun getProductPrice(productId: String): Mono<BigDecimal> {
return orderItems.toFlux().filter { it.productId == productId }
/*
* 模拟下单价格、商品定价不一致
*/
.map { it.price.plus(BigDecimal.valueOf(1)) }.last()
}
}
aggregateVerifier<Order, OrderState>()
.inject(DefaultCreateOrderSpec(inventoryService, pricingService))
.given()
.`when`(CreateOrder(customerId, orderItems, SHIPPING_ADDRESS))
/*
* 期望:价格不一致异常.
*/
.expectErrorType(PriceInconsistencyException::class.java).verify()
}
private fun mockPayOrder(): VerifiedStage<OrderState> {
val verifiedStageAfterCreateOrder = mockCreateOrder()
val previousState = verifiedStageAfterCreateOrder.stateRoot
val payOrder = PayOrder(
previousState.id,
GlobalIdGenerator.generateAsString(),
previousState.totalAmount
)
return verifiedStageAfterCreateOrder
.then()
.given()
/*
* 2. 当接收到命令
*/
.`when`(payOrder)
/*
* 3.1 期望将会产生1个事件
*/
.expectEventCount(1)
/*
* 3.2 期望将会产生一个 OrderPaid 事件 (3.1 可以不需要)
*/
.expectEventType(OrderPaid::class.java)
/*
* 3.3 期望产生的事件状态
*/
.expectEventBody<OrderPaid> {
assertThat(it.amount, equalTo(payOrder.amount))
}
/*
* 4. 期望当前聚合状态
*/
.expectState {
assertThat(it.address, equalTo(SHIPPING_ADDRESS))
assertThat(it.paidAmount, equalTo(payOrder.amount))
assertThat(it.status, equalTo(OrderStatus.PAID))
}
/*
* 完成测试编排后,验证期望.
*/
.verify()
}
/**
* 支付订单
*/
@Test
fun payOrder() {
mockPayOrder()
}
/**
* 支付订单-超付
*/
@Test
fun payOrderWhenOverPay() {
val verifiedStageAfterCreateOrder = mockCreateOrder()
val previousState = verifiedStageAfterCreateOrder.stateRoot
val payOrder = PayOrder(
previousState.id,
GlobalIdGenerator.generateAsString(),
previousState.totalAmount.plus(
BigDecimal.valueOf(1)
)
)
verifiedStageAfterCreateOrder
.then()
.given()
/*
* 2. 处理 PayOrder 命令
*/
.`when`(payOrder)
/*
* 3.1 期望将会产生俩个事件分别是: OrderPaid、OrderOverPaid
*/
.expectEventType(OrderPaid::class.java, OrderOverPaid::class.java)
/*
* 3.2 期望产生的事件状态
*/
.expectEventStream {
val itr = it.iterator()
/*
* OrderPaid
*/
val orderPaid = itr.next().body as OrderPaid
assertThat(orderPaid.paid, equalTo(true))
/*
* OrderOverPaid
*/
val orderOverPaid = itr.next().body as OrderOverPaid
assertThat(
orderOverPaid.overPay,
equalTo(payOrder.amount.minus(previousState.totalAmount))
)
}
/*
* 4. 期望当前聚合状态
*/
.expectState {
assertThat(it.paidAmount, equalTo(previousState.totalAmount))
assertThat(it.status, equalTo(OrderStatus.PAID))
}
.verify()
}
/**
* 发货
*/
@Test
fun ship() {
val verifiedStageAfterPayOrder = mockPayOrder()
val shipOrder = ShipOrder(verifiedStageAfterPayOrder.stateRoot.id)
verifiedStageAfterPayOrder
.then().given()
.`when`(shipOrder)
.expectEventType(OrderShipped::class.java)
/*
* 4. 期望当前聚合状态
*/
.expectState {
assertThat(it.status, equalTo(OrderStatus.SHIPPED))
}
.verify()
}
@Test
fun shipGivenUnpaid() {
val verifiedStageAfterCreateOrder = mockCreateOrder()
val shipOrder = ShipOrder(verifiedStageAfterCreateOrder.stateRoot.id)
verifiedStageAfterCreateOrder.then().given()
.`when`(shipOrder)
.expectErrorType(IllegalStateException::class.java)
.expectState {
/*
* 验证聚合状态[未]发生变更.
*/
assertThat(it.paidAmount, equalTo(BigDecimal.ZERO))
assertThat(it.status, equalTo(OrderStatus.CREATED))
}
.verify()
}
private fun mockDeleteOrder(): VerifiedStage<OrderState> {
val verifiedStageAfterCreateOrder = mockCreateOrder()
return verifiedStageAfterCreateOrder.then().given()
.`when`(DeleteAggregate)
.expectEventType(AggregateDeleted::class.java)
.expectStateAggregate {
assertThat(it.deleted, equalTo(true))
}
.verify()
}
@Test
fun deleteOrder() {
mockDeleteOrder()
}
@Test
fun deleteGivenDeleted() {
val verifiedStageAfterDelete = mockDeleteOrder()
verifiedStageAfterDelete.then().given()
.`when`(DeleteAggregate)
.expectErrorType(IllegalAccessDeletedAggregateException::class.java)
.expectError<IllegalAccessDeletedAggregateException> {
assertThat(it.aggregateId, equalTo(verifiedStageAfterDelete.stateAggregate.aggregateId))
}.expectStateAggregate {
assertThat(it.deleted, equalTo(true))
}
.verify()
}
}
设计
聚合建模
Single Class | Inheritance Pattern | Aggregation Pattern |
---|---|---|
加载聚合
聚合状态流
发送命令
命令与事件流
Saga - OrderProcessManager (Demo)

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
Monibuca v4.6.1 发布—— 开源 Go 语言流媒体开发框架
通过官网下载二进制文件 或者 git clone https://github.com/langhuihui/monibuca 获得最新版 1 新增LL-HLS支持 该协议为苹果公司推出的低延迟HLS协议,本次更新新增了对该协议播放支持。 同rtsp库一样,采用了第三方库来实现:「github.com/bluenviron/gohlslib」 1.1 使用方法 通过访问形如http://localhost:8080/llhls/live/test/index.m3u8来播放。 其中「live/test」为streamPath,可以替换成实际的值 1.2 插件配置 在llhls下可以配置http配置,用于修改端口号。插件配置方式可以参考文档 1.3 源码说明 llhls相关代码位于「github.com/Monibuca/plugin-hls」 的 「llhls.go」 中。hls和llhls两个插件共用一个代码仓库,因此引入hls时也同时引入了llhls插件。 2 新增HLS.js测试页面 HLS.js测试页面是将hls.js的编译后的代码嵌入到了hls插件仓库中,以方便调试hls流使...
-
下一篇
REBUILD 3.3.2 发布,高度可定制化企业管理系统
REBUILD 3.3.2 已经发布,高度可定制化企业管理系统。 V3.3.2 Fix Version 修订版 [修复] 级联字段(多个级联时)无法选择中间级联字段值问题 [修复] 若动态内容包含链接,编辑时存在编码问题 [修复] 缓存文件损坏时影响启动问题 [优化] 一些样式优化 V3.3 更新详情 CHANGELOG 一键安装包 https://getrebuild.com/download 安装指南 https://getrebuild.com/docs/admin/install 详情查看:https://gitee.com/getrebuild/rebuild/releases/3.3.2
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- MySQL数据库在高并发下的优化方案
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- MySQL8.0.19开启GTID主从同步CentOS8
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- CentOS8编译安装MySQL8.0.19
- SpringBoot2全家桶,快速入门学习开发网站教程