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
UseWow Project Templateto quickly create a DDD project based on the Wow framework.
- Test Code:Example
- Test Case: Add To Shopping Cart / Create Order
- Command
WaitStrategy
:SENT
,PROCESSED
WaitStrategy
:SENT
Mode, TheAddCartItem
command write request API After 2 minutes of stress testing, the average TPS was59625,the peak was82312,and the average response time was29ms.
WaitStrategy
:PROCESSED
Mode, TheAddCartItem
command write request API After 2 minutes of stress testing, the average TPS was18696,the peak was24141,and the average response time was239ms.
WaitStrategy
:SENT
Mode, TheCreateOrder
command write request API After 2 minutes of stress testing, the average TPS was47838,the peak was86200,and the average response time was217ms.
WaitStrategy
:PROCESSED
Mode, TheCreateOrder
command write request API After 2 minutes of stress testing, the average TPS was18230,the peak was25506,and the average response time was268ms.
Automatically register the
Command
routing processing function (HandlerFunction
), and developers only need to write the domain model to complete the service development.
Given -> When -> Expect.
- UnderstandingDomain Driven Design:《Implementing Domain-Driven Design》,《Domain-Driven Design: Tackling Complexity in the Heart of Software》
- UnderstandingCommand Query Responsibility Segregation(CQRS)
- UnderstandingEventSourcing
- UnderstandingReactive Programming
Given -> When -> Expect.
internalclassOrderTest{
@Test
privatefuncreateOrder() {
valtenantId=GlobalIdGenerator.generateAsString()
valcustomerId=GlobalIdGenerator.generateAsString()
valorderItem=OrderItem(
GlobalIdGenerator.generateAsString(),
GlobalIdGenerator.generateAsString(),
BigDecimal.valueOf(10),
10,
)
valorderItems=listOf(orderItem)
valinventoryService=object:InventoryService{
overridefungetInventory(productId:String):Mono<Int> {
returnorderItems.filter { it.productId==productId }.map { it.quantity }.first().toMono()
}
}
valpricingService=object:PricingService{
overridefungetProductPrice(productId:String):Mono<BigDecimal> {
returnorderItems.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
funcreateOrderGivenEmptyItems() {
valcustomerId=GlobalIdGenerator.generateAsString()
aggregateVerifier<Order,OrderState>()
.inject(mockk<CreateOrderSpec>(),"createOrderSpec")
.given()
.`when`(CreateOrder(customerId,listOf(),SHIPPING_ADDRESS,false))
.expectErrorType(IllegalArgumentException::class.java)
.expectStateAggregate {
/*
* nên tụ hợp đối tượng ở vào chưa khởi động lại trạng thái, tức nên tụ hợp chưa sáng tạo thành công.
*/
assertThat(it.initialized, equalTo(false))
}.verify()
}
/**
* sáng tạo đơn đặt hàng - tồn kho không đủ
*/
@Test
funcreateOrderWhenInventoryShortage() {
valcustomerId=GlobalIdGenerator.generateAsString()
valorderItem=OrderItem(
GlobalIdGenerator.generateAsString(),
GlobalIdGenerator.generateAsString(),
BigDecimal.valueOf(10),
10,
)
valorderItems=listOf(orderItem)
valinventoryService=object:InventoryService{
overridefungetInventory(productId:String):Mono<Int> {
returnorderItems.filter { it.productId==productId }
/*
* bắt chước tồn kho không đủ
*/
.map { it.quantity-1}.first().toMono()
}
}
valpricingService=object:PricingService{
overridefungetProductPrice(productId:String):Mono<BigDecimal> {
returnorderItems.filter { it.productId==productId }.map { it.price }.first().toMono()
}
}
aggregateVerifier<Order,OrderState>()
.inject(DefaultCreateOrderSpec(inventoryService, pricingService))
.given()
.`when`(CreateOrder(customerId, orderItems,SHIPPING_ADDRESS,false))
/*
* kỳ vọng: Tồn kho không đủ dị thường.
*/
.expectErrorType(InventoryShortageException::class.java)
.expectStateAggregate {
/*
* nên tụ hợp đối tượng ở vào chưa khởi động lại trạng thái, tức nên tụ hợp chưa sáng tạo thành công.
*/
assertThat(it.initialized, equalTo(false))
}.verify()
}
}
classCartSagaTest{
@Test
funonOrderCreated() {
valorderItem=OrderItem(
GlobalIdGenerator.generateAsString(),
GlobalIdGenerator.generateAsString(),
BigDecimal.valueOf(10),
10,
)
sagaVerifier<CartSaga>()
.`when`(
mockk<OrderCreated> {
every {
customerId
} returns"customerId"
every {
items
} returnslistOf(orderItem)
every {
fromCart
} returnstrue
},
)
.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 |
---|---|---|