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 {
/*
* cai tụ hợp đối tượng xử vu vị sơ thủy hóa trạng thái, tức cai tụ hợp vị sang kiến thành công.
*/
assertThat(it.initialized, equalTo(false))
}.verify()
}
/**
* sang kiến đính đan - khố tồn bất túc
*/
@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 }
/*
* mô nghĩ khố tồn bất túc
*/
.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: Khố tồn bất túc dị thường.
*/
.expectErrorType(InventoryShortageException::class.java)
.expectStateAggregate {
/*
* cai tụ hợp đối tượng xử vu vị sơ thủy hóa trạng thái, tức cai tụ hợp vị sang kiến 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 |
---|---|---|