Wow微服務(wù)開發(fā)框架
Wow 是基于 DDD、EventSourcing 的現(xiàn)代響應(yīng)式 CQRS 架構(gòu)微服務(wù)開發(fā)框架。
領(lǐng)域驅(qū)動 | 事件驅(qū)動 | 測試驅(qū)動 | 聲明式設(shè)計(jì) | 響應(yīng)式編程 | 命令查詢職責(zé)分離 | 事件源
架構(gòu)圖
事件源
可觀測性
Spring WebFlux 集成
自動注冊 命令 路由處理函數(shù)(
HandlerFunction) ,開發(fā)人員僅需編寫領(lǐng)域模型,即可完成服務(wù)開發(fā)。
測試套件:80%+ 的測試覆蓋率輕而易舉
Given -> When -> Expect .
前置條件
- 理解 領(lǐng)域驅(qū)動設(shè)計(jì):《實(shí)現(xiàn)領(lǐng)域驅(qū)動設(shè)計(jì)》、《領(lǐng)域驅(qū)動設(shè)計(jì):軟件核心復(fù)雜性應(yīng)對之道》
- 理解 命令查詢職責(zé)分離(CQRS)
- 理解 事件源架構(gòu)
- 理解 響應(yīng)式編程
特性
- Aggregate Modeling
- Single Class
- Inheritance Pattern
- Aggregation Pattern
- Saga Modeling
-
StatelessSaga
-
- Test Suite
- 兼容性測試規(guī)范(TCK)
-
AggregateVerifier -
SagaVerifier
- EventSourcing
- EventStore
- MongoDB (Recommend)
- R2dbc
- Database Sharding
- Table Sharding
- Redis
- Snapshot
- MongoDB
- R2dbc
- Database Sharding
- Table Sharding
- ElasticSearch
- Redis (Recommend)
- EventStore
- 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
CommandAggregatetoRouterFunction
- 可觀測性
- OpenTelemetry
- OpenApi
-
WowMetadataGenerator-
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()
}
/**
* 創(chuàng)建訂單
*/
@Test
fun createOrder() {
mockCreateOrder()
}
/**
* 創(chuàng)建訂單-庫存不足
*/
@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 {
/*
* 該聚合對象處于未初始化狀態(tài),即該聚合未創(chuàng)建成功.
*/
assertThat(it.initialized, equalTo(false))
}.verify()
}
/**
* 創(chuàng)建訂單-下單價(jià)格與當(dāng)前價(jià)格不一致
*/
@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 }
/*
* 模擬下單價(jià)格、商品定價(jià)不一致
*/
.map { it.price.plus(BigDecimal.valueOf(1)) }.last()
}
}
aggregateVerifier<Order, OrderState>()
.inject(DefaultCreateOrderSpec(inventoryService, pricingService))
.given()
.`when`(CreateOrder(customerId, orderItems, SHIPPING_ADDRESS))
/*
* 期望:價(jià)格不一致異常.
*/
.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. 當(dāng)接收到命令
*/
.`when`(payOrder)
/*
* 3.1 期望將會產(chǎn)生1個(gè)事件
*/
.expectEventCount(1)
/*
* 3.2 期望將會產(chǎn)生一個(gè) OrderPaid 事件 (3.1 可以不需要)
*/
.expectEventType(OrderPaid::class.java)
/*
* 3.3 期望產(chǎn)生的事件狀態(tài)
*/
.expectEventBody<OrderPaid> {
assertThat(it.amount, equalTo(payOrder.amount))
}
/*
* 4. 期望當(dāng)前聚合狀態(tài)
*/
.expectState {
assertThat(it.address, equalTo(SHIPPING_ADDRESS))
assertThat(it.paidAmount, equalTo(payOrder.amount))
assertThat(it.status, equalTo(OrderStatus.PAID))
}
/*
* 完成測試編排后,驗(yàn)證期望.
*/
.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 期望將會產(chǎn)生倆個(gè)事件分別是: OrderPaid、OrderOverPaid
*/
.expectEventType(OrderPaid::class.java, OrderOverPaid::class.java)
/*
* 3.2 期望產(chǎn)生的事件狀態(tài)
*/
.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. 期望當(dāng)前聚合狀態(tài)
*/
.expectState {
assertThat(it.paidAmount, equalTo(previousState.totalAmount))
assertThat(it.status, equalTo(OrderStatus.PAID))
}
.verify()
}
/**
* 發(fā)貨
*/
@Test
fun ship() {
val verifiedStageAfterPayOrder = mockPayOrder()
val shipOrder = ShipOrder(verifiedStageAfterPayOrder.stateRoot.id)
verifiedStageAfterPayOrder
.then().given()
.`when`(shipOrder)
.expectEventType(OrderShipped::class.java)
/*
* 4. 期望當(dāng)前聚合狀態(tài)
*/
.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 {
/*
* 驗(yàn)證聚合狀態(tài)[未]發(fā)生變更.
*/
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()
}
}
設(shè)計(jì)
聚合建模
| Single Class | Inheritance Pattern | Aggregation Pattern |
|---|---|---|
加載聚合
聚合狀態(tài)流
發(fā)送命令
命令與事件流
Saga - OrderProcessManager (Demo)
評論
圖片
表情
