{"id":13537847,"url":"https://github.com/Ahoo-Wang/Wow","last_synced_at":"2025-04-02T04:32:03.122Z","repository":{"id":157710904,"uuid":"628167080","full_name":"Ahoo-Wang/Wow","owner":"Ahoo-Wang","description":"Modern Reactive CQRS Architecture Microservice development framework based on DDD and EventSourcing | 基于 DDD \u0026 EventSourcing 的现代响应式 CQRS 架构微服务开发框架","archived":false,"fork":false,"pushed_at":"2025-03-30T01:40:51.000Z","size":23442,"stargazers_count":233,"open_issues_count":3,"forks_count":30,"subscribers_count":8,"default_branch":"main","last_synced_at":"2025-03-30T02:28:58.704Z","etag":null,"topics":["architecture","axon","cqrs","ddd","domain-driven-design","elasticsearch","event-driven","event-sourcing","high-performance","java","kafka","kotlin","microservice","mongodb","opentelemetry","reactive-programming","spring","spring-boot","webflux"],"latest_commit_sha":null,"homepage":"https://wow.ahoo.me/","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Ahoo-Wang.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-04-15T05:28:03.000Z","updated_at":"2025-03-29T06:28:12.000Z","dependencies_parsed_at":"2023-10-03T04:54:31.223Z","dependency_job_id":"ee30415a-bc5d-4ed6-b25a-16c56572642e","html_url":"https://github.com/Ahoo-Wang/Wow","commit_stats":{"total_commits":1258,"total_committers":5,"mean_commits":251.6,"dds":"0.27344992050874406","last_synced_commit":"013c436ec1ea55444a010749120c32528c422138"},"previous_names":[],"tags_count":364,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ahoo-Wang%2FWow","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ahoo-Wang%2FWow/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ahoo-Wang%2FWow/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ahoo-Wang%2FWow/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Ahoo-Wang","download_url":"https://codeload.github.com/Ahoo-Wang/Wow/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246757525,"owners_count":20828914,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["architecture","axon","cqrs","ddd","domain-driven-design","elasticsearch","event-driven","event-sourcing","high-performance","java","kafka","kotlin","microservice","mongodb","opentelemetry","reactive-programming","spring","spring-boot","webflux"],"created_at":"2024-08-01T09:01:04.246Z","updated_at":"2025-04-02T04:31:58.113Z","avatar_url":"https://github.com/Ahoo-Wang.png","language":"Kotlin","funding_links":[],"categories":["Libraries","Libraries and Frameworks"],"sub_categories":["JVM"],"readme":"\u003cp align=\"center\" style=\"text-align:center;\"\u003e\n  \u003cimg width=\"150\" src=\"document/design/assets/logo.svg\" alt=\"Wow:A Modern Reactive CQRS Architecture Microservice development framework based on DDD and EventSourcing\"/\u003e\n\u003c/p\u003e\n\n# Wow : Modern Reactive CQRS Architecture Microservice development framework based on DDD and EventSourcing\n\n\u003e [中文文档](https://wow.ahoo.me/)\n\n[![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://github.com/Ahoo-Wang/Wow/blob/mvp/LICENSE)\n[![GitHub release](https://img.shields.io/github/release/Ahoo-Wang/Wow.svg)](https://github.com/Ahoo-Wang/Wow/releases)\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/me.ahoo.wow/wow-core/badge.svg)](https://maven-badges.herokuapp.com/maven-central/me.ahoo.wow/wow-core)\n[![Codacy Badge](https://app.codacy.com/project/badge/Grade/cfc724df22db4f9387525258c8a59609)](https://app.codacy.com/gh/Ahoo-Wang/Wow/dashboard?utm_source=gh\u0026utm_medium=referral\u0026utm_content=\u0026utm_campaign=Badge_grade)\n[![codecov](https://codecov.io/gh/Ahoo-Wang/Wow/branch/main/graph/badge.svg?token=uloJrLoQir)](https://codecov.io/gh/Ahoo-Wang/Wow)\n[![Integration Test Status](https://github.com/Ahoo-Wang/Wow/actions/workflows/integration-test.yml/badge.svg)](https://github.com/Ahoo-Wang/Wow)\n[![Awesome Kotlin Badge](https://kotlin.link/awesome-kotlin.svg)](https://github.com/KotlinBy/awesome-kotlin)\n\n**Domain-Driven** | **Event-Driven** | **Test-Driven** | **Declarative-Design** | **Reactive Programming** | **Command Query Responsibility Segregation** | **Event Sourcing**\n\n## Quick Start\n\nUse [Wow Project Template](https://github.com/Ahoo-Wang/wow-project-template) to quickly create a DDD project based on the Wow framework.\n\n## Features Overview\n\n\u003cp align=\"center\" style=\"text-align:center\"\u003e\n  \u003cimg src=\"documentation/docs/public/images/Features.png\" alt=\"Wow-Features\"/\u003e\n\u003c/p\u003e\n\n## Architecture\n\n\u003cp align=\"center\" style=\"text-align:center\"\u003e\n  \u003cimg src=\"documentation/docs/public/images/Architecture.svg\" alt=\"Wow-Architecture\"/\u003e\n\u003c/p\u003e\n\n## Performance Test (Example)\n\n- Test Code: [Example](./example)\n- Test Case: Add To Shopping Cart / Create Order\n- Command `WaitStrategy`: `SENT`、`PROCESSED`\n\n### Deployment\n\n- [Redis](deploy/example/perf/redis.yaml)\n- [MongoDB](deploy/example/perf/mongo.yaml)\n- [Kafka](deploy/example/perf/kafka.yaml)\n- [Application-Config](deploy/example/perf/config/mongo_kafka_redis.yaml)\n- [Application-Deployment](deploy/example/perf/deployment.yaml)\n\n### Test Report\n\n#### Add To Shopping Cart\n\n- [Request](deploy/example/request/AddCartItem.http)\n- [Detailed Report(PDF)-SENT](./document/example/perf/Example.Cart.Add@SENT.pdf)\n- [Detailed Report(PDF)-PROCESSED](./document/example/perf/Example.Cart.Add@PROCESSED.pdf)\n\n\u003e `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.\n\n\u003cp align=\"center\" style=\"text-align:center\"\u003e\n  \u003cimg src=\"./document/example/perf/Example.Cart.Add@SENT.png\" alt=\"AddCartItem-SENT\"/\u003e\n\u003c/p\u003e\n\n\u003e `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.\n\n\u003cp align=\"center\" style=\"text-align:center\"\u003e\n  \u003cimg src=\"./document/example/perf/Example.Cart.Add@PROCESSED.png\" alt=\"AddCartItem-PROCESSED\"/\u003e\n\u003c/p\u003e\n\n#### Create Order\n\n- [Request](deploy/example/request/CreateOrder.http)\n- [Detailed Report(PDF)-SENT](./document/example/perf/Example.Order.Create@SENT.pdf)\n- [Detailed Report(PDF)-PROCESSED](./document/example/perf/Example.Order.Create@PROCESSED.pdf)\n\n\u003e `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.\n\n\u003cp align=\"center\" style=\"text-align:center\"\u003e\n  \u003cimg src=\"./document/example/perf/Example.Order.Create@SENT.png\" alt=\"CreateOrder-SENT\"/\u003e\n\u003c/p\u003e\n\n\u003e `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.\n\n\u003cp align=\"center\" style=\"text-align:center\"\u003e\n  \u003cimg src=\"./document/example/perf/Example.Order.Create@PROCESSED.png\" alt=\"CreateOrder-PROCESSED\"/\u003e\n\u003c/p\u003e\n\n## Event Sourcing\n\n\u003cp align=\"center\" style=\"text-align:center\"\u003e\n  \u003cimg src=\"./document/design/assets/EventSourcing.svg\" alt=\"Wow-EventSourcing\"/\u003e\n\u003c/p\u003e\n\n## Observability\n\n\u003cp align=\"center\" style=\"text-align:center\"\u003e\n  \u003cimg src=\"./document/design/assets/OpenTelemetry.png\" alt=\"Wow-Observability\"/\u003e\n\u003c/p\u003e\n\n## OpenAPI (Spring WebFlux Integration)\n\n\u003e Automatically register the `Command` routing processing function (`HandlerFunction`), and developers only need to\n\u003e write the domain model to complete the service development.\n\n\u003cp align=\"center\" style=\"text-align:center\"\u003e\n  \u003cimg src=\"document/design/assets/OpenAPI-Swagger.png\" alt=\"Wow-Spring-WebFlux-Integration\"/\u003e\n\u003c/p\u003e\n\n## Test suite: 80%+ test coverage is very easy\n\n\u003e Given -\u003e When -\u003e Expect .\n\n\u003cp align=\"center\" style=\"text-align:center\"\u003e\n  \u003cimg src=\"./document/design/assets/CI-Flow.png\" alt=\"Wow-CI-Flow\"/\u003e\n\u003c/p\u003e\n\n## Preconditions\n\n- Understanding **Domain Driven Design**：《Implementing Domain-Driven Design》,《Domain-Driven Design: Tackling Complexity\n  in the Heart of Software》\n- Understanding **Command Query Responsibility Segregation**(CQRS)\n- Understanding **EventSourcing**\n- Understanding **Reactive Programming**\n\n### Order Service（Kotlin）\n\n[Example-Order](./example)\n\n### Transfer（JAVA）\n\n[Example-Transfer](./example/transfer)\n\n## Unit Test Suite\n\n### 80%+ test coverage is very easy.\n\n![Test Coverage](./document/example/example-domain-jococo.png)\n\n\u003e Given -\u003e When -\u003e Expect .\n\n### Aggregate Unit Test (`AggregateVerifier`)\n\n[Aggregate Test](./example/example-domain/src/test/kotlin/me/ahoo/wow/example/domain/order/OrderTest.kt)\n\n```kotlin\ninternal class OrderTest {\n\n    @Test\n    private fun createOrder() {\n        val tenantId = GlobalIdGenerator.generateAsString()\n        val customerId = GlobalIdGenerator.generateAsString()\n\n        val orderItem = OrderItem(\n            GlobalIdGenerator.generateAsString(),\n            GlobalIdGenerator.generateAsString(),\n            BigDecimal.valueOf(10),\n            10,\n        )\n        val orderItems = listOf(orderItem)\n        val inventoryService = object : InventoryService {\n            override fun getInventory(productId: String): Mono\u003cInt\u003e {\n                return orderItems.filter { it.productId == productId }.map { it.quantity }.first().toMono()\n            }\n        }\n        val pricingService = object : PricingService {\n            override fun getProductPrice(productId: String): Mono\u003cBigDecimal\u003e {\n                return orderItems.filter { it.productId == productId }.map { it.price }.first().toMono()\n            }\n        }\n        aggregateVerifier\u003cOrder, OrderState\u003e(tenantId = tenantId)\n            .inject(DefaultCreateOrderSpec(inventoryService, pricingService))\n            .given()\n            .`when`(CreateOrder(customerId, orderItems, SHIPPING_ADDRESS, false))\n            .expectEventCount(1)\n            .expectEventType(OrderCreated::class.java)\n            .expectStateAggregate {\n                assertThat(it.aggregateId.tenantId, equalTo(tenantId))\n            }\n            .expectState {\n                assertThat(it.id, notNullValue())\n                assertThat(it.customerId, equalTo(customerId))\n                assertThat(it.address, equalTo(SHIPPING_ADDRESS))\n                assertThat(it.items, equalTo(orderItems))\n                assertThat(it.status, equalTo(OrderStatus.CREATED))\n            }\n            .verify()\n    }\n\n    @Test\n    fun createOrderGivenEmptyItems() {\n        val customerId = GlobalIdGenerator.generateAsString()\n        aggregateVerifier\u003cOrder, OrderState\u003e()\n            .inject(mockk\u003cCreateOrderSpec\u003e(), \"createOrderSpec\")\n            .given()\n            .`when`(CreateOrder(customerId, listOf(), SHIPPING_ADDRESS, false))\n            .expectErrorType(IllegalArgumentException::class.java)\n            .expectStateAggregate {\n                /*\n                 * 该聚合对象处于未初始化状态，即该聚合未创建成功.\n                 */\n                assertThat(it.initialized, equalTo(false))\n            }.verify()\n    }\n\n    /**\n     * 创建订单-库存不足\n     */\n    @Test\n    fun createOrderWhenInventoryShortage() {\n        val customerId = GlobalIdGenerator.generateAsString()\n        val orderItem = OrderItem(\n            GlobalIdGenerator.generateAsString(),\n            GlobalIdGenerator.generateAsString(),\n            BigDecimal.valueOf(10),\n            10,\n        )\n        val orderItems = listOf(orderItem)\n        val inventoryService = object : InventoryService {\n            override fun getInventory(productId: String): Mono\u003cInt\u003e {\n                return orderItems.filter { it.productId == productId }\n                    /*\n                     * 模拟库存不足\n                     */\n                    .map { it.quantity - 1 }.first().toMono()\n            }\n        }\n        val pricingService = object : PricingService {\n            override fun getProductPrice(productId: String): Mono\u003cBigDecimal\u003e {\n                return orderItems.filter { it.productId == productId }.map { it.price }.first().toMono()\n            }\n        }\n\n        aggregateVerifier\u003cOrder, OrderState\u003e()\n            .inject(DefaultCreateOrderSpec(inventoryService, pricingService))\n            .given()\n            .`when`(CreateOrder(customerId, orderItems, SHIPPING_ADDRESS, false))\n            /*\n             * 期望：库存不足异常.\n             */\n            .expectErrorType(InventoryShortageException::class.java)\n            .expectStateAggregate {\n                /*\n                 * 该聚合对象处于未初始化状态，即该聚合未创建成功.\n                 */\n                assertThat(it.initialized, equalTo(false))\n            }.verify()\n    }\n}\n```\n\n### Saga Unit Test (`SagaVerifier`)\n\n[Saga Test](./example/example-domain/src/test/kotlin/me/ahoo/wow/example/domain/cart/CartSagaTest.kt)\n\n```kotlin\nclass CartSagaTest {\n\n    @Test\n    fun onOrderCreated() {\n        val orderItem = OrderItem(\n            GlobalIdGenerator.generateAsString(),\n            GlobalIdGenerator.generateAsString(),\n            BigDecimal.valueOf(10),\n            10,\n        )\n        sagaVerifier\u003cCartSaga\u003e()\n            .`when`(\n                mockk\u003cOrderCreated\u003e {\n                    every {\n                        customerId\n                    } returns \"customerId\"\n                    every {\n                        items\n                    } returns listOf(orderItem)\n                    every {\n                        fromCart\n                    } returns true\n                },\n            )\n            .expectCommandBody\u003cRemoveCartItem\u003e {\n                assertThat(it.id, equalTo(\"customerId\"))\n                assertThat(it.productIds, hasSize(1))\n                assertThat(it.productIds.first(), equalTo(orderItem.productId))\n            }\n            .verify()\n    }\n}\n```\n\n## Design\n\n### Modeling\n\n| **Single Class**                                                                       | **Inheritance Pattern**                                                                     | **Aggregation Pattern**                                                                     |\n|----------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------|\n| ![Single Class - Modeling](./document/design/assets/Modeling-Single-Class-Pattern.svg) | ![Inheritance Pattern- Modeling](./document/design/assets/Modeling-Inheritance-Pattern.svg) | ![Aggregation Pattern- Modeling](./document/design/assets/Modeling-Aggregation-Pattern.svg) |\n\n### Load Aggregate\n\n\u003cp align=\"center\" style=\"text-align:center\"\u003e\n  \u003cimg src=\"./document/design/assets/Load-Aggregate.svg\" alt=\"Load Aggregate\"/\u003e\n\u003c/p\u003e\n\n### Aggregate State Flow\n\n\u003cp align=\"center\" style=\"text-align:center\"\u003e\n  \u003cimg src=\"./document/design/assets/Aggregate-State-Flow.svg\" alt=\"Aggregate State Flow\"/\u003e\n\u003c/p\u003e\n\n### Send Command\n\n\u003cp align=\"center\" style=\"text-align:center\"\u003e\n  \u003cimg src=\"./document/design/assets/Send-Command.svg\" alt=\"Send Command\"/\u003e\n\u003c/p\u003e\n\n### Command And Event Flow\n\n\u003cp align=\"center\" style=\"text-align:center\"\u003e\n  \u003cimg src=\"./document/design/assets/Command-Event-Flow.svg\" alt=\"Command And Event Flow\"/\u003e\n\u003c/p\u003e\n\n## Event Compensation\n\n### Use Case\n\n\u003cp align=\"center\" style=\"text-align:center\"\u003e\n  \u003cimg src=\"documentation/docs/public/images/compensation/usercase.svg\" alt=\"Event-Compensation-UserCase\"/\u003e\n\u003c/p\u003e\n\n### Execution Sequence Diagram\n\n\u003cp align=\"center\" style=\"text-align:center\"\u003e\n  \u003cimg src=\"documentation/docs/public/images/compensation/process-sequence-diagram.svg\" alt=\"Event-Compensation\"/\u003e\n\u003c/p\u003e\n\n### Dashboard\n\n\u003cp align=\"center\" style=\"text-align:center\"\u003e\n  \u003cimg src=\"documentation/docs/public/images/compensation/dashboard.png\" alt=\"Compensation-Dashboard\"/\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\" style=\"text-align:center\"\u003e\n  \u003cimg src=\"documentation/docs/public/images/compensation/dashboard-apply-retry-spec.png\" alt=\"Compensation-Dashboard\"/\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\" style=\"text-align:center\"\u003e\n  \u003cimg src=\"documentation/docs/public/images/compensation/dashboard-succeeded.png\" alt=\"Compensation-Dashboard\"/\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\" style=\"text-align:center\"\u003e\n  \u003cimg src=\"documentation/docs/public/images/compensation/dashboard-error.png\" alt=\"Compensation-Dashboard-Error\"/\u003e\n\u003c/p\u003e\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FAhoo-Wang%2FWow","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FAhoo-Wang%2FWow","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FAhoo-Wang%2FWow/lists"}