Awesome
<p align="center" style="text-align:center;"> <img width="150" src="document/design/assets/logo.svg" alt="Wow:A Modern Reactive CQRS Architecture Microservice development framework based on DDD and EventSourcing"/> </p>Wow : Modern Reactive CQRS Architecture Microservice development framework based on DDD and EventSourcing
Domain-Driven | Event-Driven | Test-Driven | Declarative-Design | Reactive Programming | Command Query Responsibility Segregation | Event Sourcing
Quick Start
Use Wow Project Template to quickly create a DDD project based on the Wow framework.
Features Overview
<p align="center" style="text-align:center"> <img src="documentation/docs/public/images/Features.png" alt="Wow-Features"/> </p>Architecture
<p align="center" style="text-align:center"> <img src="documentation/docs/public/images/Architecture.svg" alt="Wow-Architecture"/> </p>Performance Test (Example)
- Test Code: Example
- Test Case: Add To Shopping Cart / Create Order
- Command
WaitStrategy
:SENT
、PROCESSED
Deployment
Test Report
Add To Shopping Cart
<p align="center" style="text-align:center"> <img src="./document/example/perf/Example.Cart.Add@SENT.png" alt="AddCartItem-SENT"/> </p>
WaitStrategy
:SENT
Mode, TheAddCartItem
command write request API After 2 minutes of stress testing, the average TPS was 59625, the peak was 82312, and the average response time was 29 ms.
<p align="center" style="text-align:center"> <img src="./document/example/perf/Example.Cart.Add@PROCESSED.png" alt="AddCartItem-PROCESSED"/> </p>
WaitStrategy
:PROCESSED
Mode, TheAddCartItem
command write request API After 2 minutes of stress testing, the average TPS was 18696, the peak was 24141, and the average response time was 239 ms.
Create Order
<p align="center" style="text-align:center"> <img src="./document/example/perf/Example.Order.Create@SENT.png" alt="CreateOrder-SENT"/> </p>
WaitStrategy
:SENT
Mode, TheCreateOrder
command write request API After 2 minutes of stress testing, the average TPS was 47838, the peak was 86200, and the average response time was 217 ms.
<p align="center" style="text-align:center"> <img src="./document/example/perf/Example.Order.Create@PROCESSED.png" alt="CreateOrder-PROCESSED"/> </p>
WaitStrategy
:PROCESSED
Mode, TheCreateOrder
command write request API After 2 minutes of stress testing, the average TPS was 18230, the peak was 25506, and the average response time was 268 ms.
Event Sourcing
<p align="center" style="text-align:center"> <img src="./document/design/assets/EventSourcing.svg" alt="Wow-EventSourcing"/> </p>Observability
<p align="center" style="text-align:center"> <img src="./document/design/assets/OpenTelemetry.png" alt="Wow-Observability"/> </p>OpenAPI (Spring WebFlux Integration)
<p align="center" style="text-align:center"> <img src="document/design/assets/OpenAPI-Swagger.png" alt="Wow-Spring-WebFlux-Integration"/> </p>Automatically register the
Command
routing processing function (HandlerFunction
), and developers only need to write the domain model to complete the service development.
Test suite: 80%+ test coverage is very easy
<p align="center" style="text-align:center"> <img src="./document/design/assets/CI-Flow.png" alt="Wow-CI-Flow"/> </p>Given -> When -> Expect .
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
Order Service(Kotlin)
Transfer(JAVA)
Unit Test Suite
80%+ test coverage is very easy.
Given -> When -> Expect .
Aggregate Unit Test (AggregateVerifier
)
internal class OrderTest {
@Test
private fun createOrder() {
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()
}
}
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 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()
}
}
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()
}
}
Design
Modeling
Single Class | Inheritance Pattern | Aggregation Pattern |
---|---|---|