Releases: Ahoo-Wang/Wow
Releases · Ahoo-Wang/Wow
v1.8.24
What's Changed
- refactor:
wow.parallelism
forMessageDispatcher
by @Ahoo-Wang in #32
Full Changelog: v1.8.23...v1.8.24
v1.8.23
What's Changed
- refactor:
TracingLocalCommandBus
andTracingLocalEventBus
by @Ahoo-Wang in #31
Full Changelog: v1.8.22...v1.8.23
v1.8.22
What's Changed
- feat: support customize
BloomFilterIdempotencyChecker
by @Ahoo-Wang in #30 - fix(deps): update dependency org.testcontainers:testcontainers-bom to v1.18.1 by @renovate in #26
Full Changelog: v1.8.21...v1.8.22
v1.8.21
What's Changed
- feat: support TracingLocalMessageBus by @Ahoo-Wang in #29
Full Changelog: v1.8.20...v1.8.21
v1.8.20
What's Changed
- feat: support
LocalFirstCommandBus
by @Ahoo-Wang in #25 - fix(deps): update dependency io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom to v1.26.0 by @renovate in #27
- feat: support Tracing for
MessageBus
by @Ahoo-Wang in #28
Full Changelog: v1.8.18...v1.8.20
v1.8.18
Wow
A Modern Reactive CQRS Architecture Microservice development framework based on DDD and EventSourcing.
Architecture
Event Sourcing
Preconditions
- Understanding Domain Driven Design:《Implementing Domain-Driven Design》,《Domain-Driven Design: Tackling Complexity
in the Heart of Software》 - Understanding Command Query Responsibility Segregation(CQRS)
- Understanding EventSourcing
- Understanding Reactive Programming
Features
- 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 Integration
-
CommandBus
-
DomainEventBus
-
- Spring Integration
- Spring Boot Auto Configuration
- Automatically register
CommandAggregate
toRouterFunction
- Observability
- OpenTelemetry
- OpenApi
-
WowMetadata
Generator-
wow-compiler
-
Example
Aggregate Unit Test
80% test coverage is a breeze.
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
a...