领域建模即微服务开发 | Wow 2.9.6 发布
基于 DDD、EventSourcing 的现代响应式 CQRS 架构微服务开发框架
领域驱动 | 事件驱动 | 测试驱动 | 声明式设计 | 响应式编程 | 命令查询职责分离 | 事件溯源
🎉 更新内容 🎉
- 测试: 项目单元测试覆盖率提升至 86%
- 特性: 新增
MessageFunction.invoke
API - 依赖: 升级
io.opentelemetry:opentelemetry-bom
版本v1.33.0
- 特性: 增强
MarkNullableConverter
以支持Jackson
注解
快速开始
使用Wow 项目模板快速创建基于 Wow 框架的 DDD 项目。
架构图
性能测试 (Example)
- 测试代码:Example
- 测试场景:加入购物车、下单
- 命令发送等待模式(
WaitStrategy
):SENT
、PROCESSED
部署
测试报告
加入购物车
WaitStrategy
:SENT
WaitStrategy
:PROCESSED
下单
WaitStrategy
:SENT
WaitStrategy
:PROCESSED
事件源
可观测性
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)
- EventStore
- 命令等待策略(
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
toRouterFunction
- 可观测性
- OpenTelemetry
- OpenAPI
-
WowMetadata
Generator-
wow-compiler
-
Example
单元测试套件
80%+ 的测试覆盖率轻而易举。
Given -> When -> Expect .
Aggregate Unit Test (AggregateVerifier
)
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
)
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)

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
Arc 浏览器开始 Windows 版 Beta 测试
12 月 11 日,Arc 浏览器开始 Windows 版 Beta 测试,第一批邀请已在加入等待队列的用户中筛选并发送完毕。 感兴趣的用户可以在上线的IsArcOnWindowsYet页面中,填写表单加入等待队列。 Arc 基于 Chromium 并用 Swift 语言编写。它支持 Chrome 浏览器扩充功能,同时默认使用 Google 搜索。7 月份,Arc 正式发布了 1.0。 Arc 浏览器正式发布 1.0,声称是 Chrome 的替代品 Arc 旨在成为一个 “万维网的操作系统”,并试图将网页浏览与内置应用程序和功能整合在一起。其内置的功能包括虚拟记事本、拼贴风格的 “easel” 和 “boosts”,该功能允许用户美化和重新设计网站界面。Arc 的选项卡垂直排列在侧边栏中,侧边栏包含除浏览窗口之外的所有浏览器功能。
-
下一篇
CudaText 1.205.0 发布,跨平台的文本编辑器
CudaText 是一个跨平台的文本编辑器,用 Object Pascal 编写。它是开源项目,启动速度相当快,它可以通过 Python 插件进行扩展,借助 EControl 引擎还带来了功能丰富的语法分析器。 CudaText 1.205.0 正式发布,更新内容如下: 添加:将 TRegExpr 引擎更新至 1.182;现在支持 \K 添加:选项“auto_fold_comments”现在不仅为“comments”,还为“strings”设置范围,即任何多行字符串字面量设定范围 添加:新选项“find_hotkey_more”更改查找对话框中“...”按钮的热键 添加:tree-helpers in Pascal:编辑后,展开之前展开的代码树节点 更改:查找对话框:将“...”按钮的热键从 Ctrl+Alt+dot 更改为 Ctrl+Alt+D,因为 Ctrl+Alt+dot 用于斯洛伐克语键盘布局 修复:根本无法加载损坏的 UTF-8 文本,但应用程序必须能够以 cp1252 编码自动重新加载文本 修复:图片查看器:Ctrl+MouseWheel 时状态栏未更新的问题 更多详情可查...
相关文章
文章评论
共有0条评论来说两句吧...