/Wow

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

Primary LanguageKotlinApache License 2.0Apache-2.0

Wow

中文文档

A 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

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

SENT

WaitStrategy: PROCESSED

PROCESSED

Create Order

WaitStrategy: SENT

SENT

WaitStrategy: PROCESSED

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

Features

  • Aggregate Modeling
    • Single Class
    • Inheritance Pattern
    • Aggregation Pattern
  • Saga Modeling
    • StatelessSaga
  • Test Suite
    • The Technology Compatibility Kit
    • AggregateVerifier
    • SagaVerifier
  • EventSourcing
    • EventStore
      • MongoDB (Recommend)
      • R2dbc
        • Database Sharding
        • Table Sharding
      • Redis
    • Snapshot
      • MongoDB
      • R2dbc
        • Database Sharding
        • Table Sharding
      • ElasticSearch
      • Redis (Recommend)
  • Command WaitStrategy
    • SENT : Send a completion signal after the command is sent
    • PROCESSED : Send a completion signal when command processing is complete
    • SNAPSHOT : Send a completion signal after the snapshot generation is complete
    • PROJECTED : Send a completion signal when the event generated by the command has been projected
  • CommandBus
    • InMemoryCommandBus
    • KafkaCommandBus (Recommend)
    • RedisCommandBus
    • LocalFirstCommandBus
  • DomainEventBus
    • InMemoryDomainEventBus
    • KafkaDomainEventBus (Recommend)
    • RedisDomainEventBus
    • LocalFirstDomainEventBus
  • StateEventBus
    • InMemoryStateEventBus
    • KafkaStateEventBus (Recommend)
    • RedisStateEventBus
    • LocalFirstStateEventBus
  • Spring Integration
    • Spring Boot Auto Configuration
    • Automatically register CommandAggregate to RouterFunction
  • Observability
    • OpenTelemetry
  • OpenAPI
  • WowMetadata Generator
    • wow-compiler

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

internal class OrderTest {

    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.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()
            }
        }
        return 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 createOrder() {
        mockCreateOrder()
    }

    @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()
    }

    /**
     * 创建订单-下单价格与当前价格不一致
     */
    @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.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.plus(BigDecimal.valueOf(1)) }.first().toMono()
            }
        }
        aggregateVerifier<Order, OrderState>()
            .inject(DefaultCreateOrderSpec(inventoryService, pricingService))
            .given()
            .`when`(CreateOrder(customerId, orderItems, SHIPPING_ADDRESS, false))
            /*
             * 期望:价格不一致异常.
             */
            .expectErrorType(PriceInconsistencyException::class.java).verify()
    }
}

Saga Unit Test (SagaVerifier)

Saga Test

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
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

Saga - OrderProcessManager (Demo)

OrderProcessManager