首页 > 其他分享 >Wow: 基于 DDD、EventSourcing 的现代响应式 CQRS 架构微服务开发框架

Wow: 基于 DDD、EventSourcing 的现代响应式 CQRS 架构微服务开发框架

时间:2023-08-07 11:47:22浏览次数:35  
标签:val Wow EventSourcing CQRS generateAsString GlobalIdGenerator customerId orderIt

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

领域驱动 | 事件驱动 | 测试驱动 | 声明式设计 | 响应式编程 | 命令查询职责分离 | 事件溯源

架构图

Wow-Architecture

事件源

Wow-EventSourcing

可观测性

Wow-Observability

OpenAPI (Spring WebFlux 集成)

自动注册 命令 路由处理函数(HandlerFunction) ,开发人员仅需编写领域模型,即可完成服务开发。

Wow-Spring-WebFlux-Integration

测试套件:80%+ 的测试覆盖率轻而易举

Given -> When -> Expect .

Wow-CI-Flow

前置条件

  • 理解 领域驱动设计:《实现领域驱动设计》、《领域驱动设计:软件核心复杂性应对之道》
  • 理解 命令查询职责分离(CQRS)
  • 理解 事件源架构
  • 理解 响应式编程

特性

    • EventStore
    • Snapshot

Example

Example

单元测试套件

80%+ 的测试覆盖率轻而易举。

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

设计

聚合建模

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

加载聚合

Load Aggregate

聚合状态流

Aggregate State Flow

发送命令

Send Command

命令与事件流

Command And Event Flow

Saga - OrderProcessManager (Demo)

OrderProcessManager

标签:val,Wow,EventSourcing,CQRS,generateAsString,GlobalIdGenerator,customerId,orderIt
From: https://www.cnblogs.com/Ahoo-Wang/p/wow.html

相关文章

  • CQRS读写分离MySQL数据库如何部署至Linux
    FearlessGuo首先有一台可以使用的Linux服务器,可以自行购买,当然也可以白嫖。有一款可以连接Linux的软件,我用的是putty在Linux上下载docker镜像,类似应用商店。安装过程参阅下方链接Linux安装Docker完整教程_docker安装_风随心飞飞的博客-CSDN博客下载mysql镜像,查看版本本次......
  • MFC-IsWow64Process 32位进程是否运行在64位操作系统中
     CStringstr;BOOLbIsWow64=FALSE;BOOLb=IsWow64Process(GetCurrentProcess(),&bIsWow64);//32位进程是否运行在64位操作系统中/*指定进程是否运行在64位操作系统的32环境(WOW64)下参数1:HANDLEhProcess进程的句柄。句柄必须具有PROCESS_......
  • wow 闪屏幕可能处理方法(待验证)
    http://bbs.ngacn.cc/read.php?tid=3355682你是不是优化过,看注册表里面的hungapptimeout是多少,如果没有,就在HKEY_CURRENT_USER\ControlPanel\Desktop\下添加,将它的值改高,我设置的是30000,默认5000不够......
  • Wow魔兽世界服务器搭建详细教程
    自从《魔兽世界》国服关服后,很多魔兽老玩家心里都是空落落的,魔兽陪伴了我们十多年,此次关服犹如关上了通往艾泽拉斯大陆的大门。上帝关上了一扇门,我们也可以自己开扇窗,随着国服关闭,越来越多的玩家想要自己开服当服主,今天明杰将和你们分享魔兽世界服务器搭建教程。想要搭建一个魔兽商......
  • 领域驱动设计(DDD)实践之路(二):事件驱动与CQRS
    vivo互联网技术微信公众号 作者:wenbozhang【领域驱动设计实践之路】系列往期精彩文章:《领域驱动设计(DDD)实践之路(一)》主要讲述了战略层面的DDD原则。这是“领域驱动设计实践之路”系列的第二篇文章,分析了如何应用事件来分离软件核心复杂度。探究CQRS为什么广泛应用于DDD项......
  • 「解题报告」CF1178B WOW Factor
    ¿题目链接这是一道非常富有启发性的题目,值得一做,闪耀着人类和机器人的智慧光辉的绝佳题目.首先注意到(vv)o(vv)的结构,可以考虑枚举中间的o,这样只需要算两边的选法......
  • wow魔兽世界服务端主体结构​
    wow魔兽世界服务端主体结构服务端主要由三大块组成,数据库、服务端逻辑、脚本。数据库用的MySQL,这里不是很关键暂且不说。脚本有自己的脚本引擎,简单的任务、战斗等都可以通......
  • 通达信--顶部底部反转形态wowkbar
    {风险资金}FMONEY:=1000;LX1:-8,DOTLINE;LX2:-4,DOTLINE;{L0:0,DOTLINE;L1:1,DOTLINE;L2:2,DOTLINE;}LX3:4,DOTLINE;LX4:8,DOTLINE;{开仓K线形态短期}{开......
  • OnionArch-NorthwindTraders,sample-dotnet-core-cqrs-api
    NorthwindTraders, sample-dotnet-core-cqrs-api 项目OnionArch-采用DDD+CQRS+.Net7.0实现的洋葱架构 博主最近失业在家,找工作之余,看了一些关于洋葱(整洁)架构的资......
  • 认知篇:CQRS架构模式的本质
    作者:京东科技倪新明CQRS只是一种非常简单的模式(pattern),CQRS本身并不是一种架构风格,和最终一致性/消息/读写分离/事件溯源/DDD等没有必然的联系,它最大优势是给我们带来更多......