Skip to content
/ Wow Public

Modern Reactive CQRS Architecture Microservice development framework based on DDD and EventSourcing | 基于 DDD & EventSourcing 的现代响应式 CQRS 架构微服务开发框架

License

Notifications You must be signed in to change notification settings

Ahoo-Wang/Wow

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Wow:A Modern Reactive CQRS Architecture Microservice development framework based on DDD and EventSourcing

Wow : Modern Reactive CQRS Architecture Microservice development framework based on DDD and EventSourcing

中文文档

License GitHub release Maven Central Codacy Badge codecov Integration Test Status Awesome Kotlin Badge

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

Wow-Features

Architecture

Wow-Architecture

Performance Test (Example)

  • Test Code: Example
  • Test Case: Add To Shopping Cart / Create Order
  • Command WaitStrategy: SENTPROCESSED

Deployment

Test Report

Add To Shopping Cart

WaitStrategy:SENT Mode, The AddCartItem 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.

AddCartItem-SENT

WaitStrategy:PROCESSED Mode, The AddCartItem 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.

AddCartItem-PROCESSED

Create Order

WaitStrategy:SENT Mode, The CreateOrder 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.

CreateOrder-SENT

WaitStrategy:PROCESSED Mode, The CreateOrder 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.

CreateOrder-PROCESSED

Event Sourcing

Wow-EventSourcing

Observability

Wow-Observability

OpenAPI (Spring WebFlux Integration)

Automatically register the Command routing processing function (HandlerFunction), and developers only need to write the domain model to complete the service development.

Wow-Spring-WebFlux-Integration

Test suite: 80%+ test coverage is very easy

Given -> When -> Expect .

Wow-CI-Flow

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)

Example-Order

Transfer(JAVA)

Example-Transfer

Unit Test Suite

80%+ test coverage is very easy.

Test Coverage

Given -> When -> Expect .

Aggregate Unit Test (AggregateVerifier)

Aggregate Test

class CartTest {

  @Test
  fun addCartItem() {
    val ownerId = generateGlobalId()
    val addCartItem = AddCartItem(
      productId = "productId",
      quantity = 1,
    )

    aggregateVerifier<Cart, CartState>(ownerId)
      .givenOwnerId(ownerId)
      .whenCommand(addCartItem)
      .expectNoError()
      .expectEventType(CartItemAdded::class.java)
      .expectState {
        it.items.assert().hasSize(1)
      }.expectStateAggregate {
        it.ownerId.assert().isEqualTo(ownerId)
      }
      .verify()
  }

  @Test
  fun givenStateWhenAdd() {
    val addCartItem = AddCartItem(
      productId = "productId",
      quantity = 1,
    )

    aggregateVerifier<Cart, CartState>()
      .givenState(CartState(generateGlobalId()), 1)
      .whenCommand(addCartItem)
      .expectNoError()
      .expectEventType(CartItemAdded::class.java)
      .expectState {
        it.items.assert().hasSize(1)
      }
      .verify()
  }

  @Test
  fun addCartItemIfSameProduct() {
    val addCartItem = AddCartItem(
      productId = "productId",
      quantity = 1,
    )

    aggregateVerifier<Cart, CartState>()
      .given(
        CartItemAdded(
          added = CartItem(
            productId = addCartItem.productId,
            quantity = 1,
          ),
        ),
      )
      .whenCommand(addCartItem)
      .expectNoError()
      .expectEventType(CartQuantityChanged::class.java)
      .expectState {
        it.items.assert().hasSize(1)
        it.items.first().quantity.assert().isEqualTo(2)
      }
      .verify()
  }

  @Test
  fun addCartItemIfUnCreated() {
    val addCartItem = AddCartItem(
      productId = "productId",
      quantity = 1,
    )
    aggregateVerifier<Cart, CartState>()
      .given()
      .whenCommand(addCartItem)
      .expectNoError()
      .expectEventType(CartItemAdded::class.java)
      .expectState {
        it.items.assert().hasSize(1)
      }
      .expectStateAggregate {
        it.version.assert().isEqualTo(1)
      }
      .verify()
  }

  @Test
  fun addCartItemGivenMax() {
    val events = buildList {
      for (i in 0..99) {
        add(
          CartItemAdded(
            added = CartItem(
              productId = "productId$i",
              quantity = 1,
            ),
          ),
        )
      }
    }.toTypedArray()
    val addCartItem = AddCartItem(
      productId = "productId",
      quantity = 1,
    )

    aggregateVerifier<Cart, CartState>()
      .given(*events)
      .whenCommand(addCartItem)
      .expectErrorType(IllegalArgumentException::class.java)
      .expectState {
        it.items.assert().hasSize(MAX_CART_ITEM_SIZE)
      }
      .verify()
  }

  @Test
  fun removeCartItem() {
    val removeCartItem = RemoveCartItem(
      productIds = setOf("productId"),
    )
    val added = CartItem(
      productId = "productId",
      quantity = 1,
    )

    aggregateVerifier<Cart, CartState>()
      .given(
        CartItemAdded(
          added = added,
        ),
      )
      .whenCommand(removeCartItem)
      .expectEventType(CartItemRemoved::class.java)
      .expectState {
        it.items.assert().isEmpty()
      }
      .verify()
  }

  @Test
  fun changeQuantity() {
    val changeQuantity = ChangeQuantity(
      productId = "productId",
      quantity = 2,
    )
    val added = CartItem(
      productId = "productId",
      quantity = 1,
    )
    aggregateVerifier<Cart, CartState>()
      .given(
        CartItemAdded(
          added = added,
        ),
      )
      .whenCommand(changeQuantity)
      .expectEventType(CartQuantityChanged::class.java)
      .expectState {
        it.items.assert().hasSize(1)
        it.items.first().quantity.assert().isEqualTo(changeQuantity.quantity)
      }
      .verify()
  }

  @Test
  fun onCreateThenDeleteThenRecover() {
    val addCartItem = AddCartItem(
      productId = "productId",
      quantity = 1,
    )
    aggregateVerifier<Cart, CartState>()
      .whenCommand(addCartItem)
      .expectNoError()
      .expectEventType(CartItemAdded::class.java)
      .expectState {
        it.items.assert().hasSize(1)
      }
      .verify()
      .then()
      .whenCommand(DefaultDeleteAggregate)
      .expectEventType(DefaultAggregateDeleted::class.java)
      .expectStateAggregate {
        it.deleted.assert().isTrue()
      }.verify()
      .then()
      .whenCommand(DefaultDeleteAggregate::class.java)
      .expectErrorType(IllegalAccessDeletedAggregateException::class.java)
      .verify()
      .then()
      .whenCommand(DefaultRecoverAggregate)
      .expectStateAggregate {
        it.deleted.assert().isFalse()
      }.verify()
      .then()
      .whenCommand(DefaultRecoverAggregate)
      .expectErrorType(IllegalStateException::class.java)
      .verify()
  }
}

Saga Unit Test (SagaVerifier)

Saga Test

class CartSagaTest {

  @Test
  fun onOrderCreated() {
    val ownerId = generateGlobalId()
    val orderItem = OrderItem(
      id = generateGlobalId(),
      productId = generateGlobalId(),
      price = BigDecimal.valueOf(10),
      quantity = 10,
    )
    sagaVerifier<CartSaga>()
      .whenEvent(
        event = mockk<OrderCreated> {
          every {
            items
          } returns listOf(orderItem)
          every {
            fromCart
          } returns true
        },
        ownerId = ownerId
      )
      .expectCommand<RemoveCartItem> {
        it.aggregateId.id.assert().isEqualTo(ownerId)
        it.body.productIds.assert().hasSize(1)
        it.body.productIds.assert().first().isEqualTo(orderItem.productId)
      }
      .verify()
  }
}

Design

Modeling

Single Class Inheritance Pattern Aggregation Pattern
Single Class - Modeling Inheritance Pattern- Modeling Aggregation Pattern- Modeling

Load Aggregate

Load Aggregate

Aggregate State Flow

Aggregate State Flow

Send Command

Send Command

Command And Event Flow

Command And Event Flow

Event Compensation

Use Case

Event-Compensation-UserCase

Execution Sequence Diagram

Event-Compensation

Dashboard

Compensation-Dashboard

Compensation-Dashboard

Compensation-Dashboard

Compensation-Dashboard-Error