├── .github └── workflows │ └── maven.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── doc └── assets │ └── img │ └── ddd-depgraph.png ├── order-center-bp-fresh ├── pom.xml └── src │ └── main │ └── java │ └── org │ └── example │ └── bp │ └── oms │ └── fresh │ ├── FreshPartner.java │ └── extension │ ├── DecideStepsExt.java │ ├── PostPersitExt.java │ └── PresortExt.java ├── order-center-bp-isv ├── pom.xml └── src │ └── main │ ├── java │ └── org │ │ └── example │ │ └── bp │ │ └── oms │ │ └── isv │ │ ├── IsvPartner.java │ │ ├── PluginListener.java │ │ ├── aop │ │ ├── AutoLogger.java │ │ └── AutoLoggerAspect.java │ │ └── extension │ │ ├── CustomModelExt.java │ │ ├── DecideStepsExt.java │ │ ├── PresortExt.java │ │ ├── SerializableIsolationExt.java │ │ └── util │ │ └── WarehouseUtil.java │ └── resources │ ├── config.properties │ └── plugin-isv.xml ├── order-center-bp-ka ├── pom.xml └── src │ └── main │ ├── java │ └── org │ │ └── example │ │ └── bp │ │ └── oms │ │ └── ka │ │ ├── KaPartner.java │ │ └── extension │ │ ├── AssignOrderNoExt.java │ │ ├── DecideStepsExt.java │ │ ├── ReviseStepsExt.java │ │ └── SerializableIsolationExt.java │ └── resources │ └── plugin-ka.xml ├── order-center-cp ├── README.md ├── cp-oc-controller │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── org │ │ └── example │ │ └── cp │ │ └── oms │ │ └── controller │ │ ├── OrderController.java │ │ ├── dto │ │ ├── CancelOrderRequest.java │ │ └── SubmitOrderRequest.java │ │ └── translator │ │ └── SubmitOrderRequestTranslator.java ├── cp-oc-domain │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── org │ │ └── example │ │ └── cp │ │ └── oms │ │ └── domain │ │ ├── CoreDomain.java │ │ ├── StartupListener.java │ │ ├── ability │ │ ├── AssignOrderNoAbility.java │ │ ├── CustomModelAbility.java │ │ ├── DecideStepsAbility.java │ │ ├── PostPersistAbility.java │ │ ├── PresortAbility.java │ │ ├── ReviseStepsAbility.java │ │ ├── SerializableIsolationAbility.java │ │ └── extension │ │ │ └── DefaultAssignOrderNoExt.java │ │ ├── facade │ │ ├── cache │ │ │ └── IRedisClient.java │ │ ├── lock │ │ │ └── IRedisLockFactory.java │ │ ├── mq │ │ │ └── IMessageProducer.java │ │ ├── package-info.java │ │ └── repository │ │ │ └── IOrderRepository.java │ │ ├── model │ │ ├── OrderMain.java │ │ ├── OrderModelCreator.java │ │ └── vo │ │ │ ├── OrderItem.java │ │ │ ├── OrderItemDelegate.java │ │ │ ├── Product.java │ │ │ └── ProductDelegate.java │ │ ├── service │ │ ├── CancelOrder.java │ │ └── SubmitOrder.java │ │ ├── specification │ │ └── ProductNotEmptySpec.java │ │ └── step │ │ ├── CancelOrderStep.java │ │ ├── CancelOrderStepsExec.java │ │ ├── SubmitOrderStep.java │ │ ├── SubmitOrderStepsExec.java │ │ ├── cancelorder │ │ ├── BasicStep.java │ │ └── StateStep.java │ │ └── submitorder │ │ ├── BasicStep.java │ │ ├── BroadcastStep.java │ │ ├── PersistStep.java │ │ ├── PresortStep.java │ │ ├── ProductStep.java │ │ └── StockStep.java ├── cp-oc-infrastructure │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── org │ │ │ └── example │ │ │ └── cp │ │ │ └── oms │ │ │ └── infra │ │ │ ├── cache │ │ │ └── RedisClient.java │ │ │ ├── dao │ │ │ ├── OrderItemDao.java │ │ │ ├── OrderMainDao.java │ │ │ └── mock │ │ │ │ ├── MockOrderItemDao.java │ │ │ │ └── MockOrderMainDao.java │ │ │ ├── lock │ │ │ ├── RedisLock.java │ │ │ └── RedisLockFactory.java │ │ │ ├── manager │ │ │ ├── IOrderManager.java │ │ │ └── impl │ │ │ │ └── OrderManager.java │ │ │ ├── mq │ │ │ └── MessageProducer.java │ │ │ ├── po │ │ │ ├── OrderItemData.java │ │ │ └── OrderMainData.java │ │ │ ├── repository │ │ │ └── OrderRepository.java │ │ │ ├── resource │ │ │ └── StockRpc.java │ │ │ └── translator │ │ │ ├── Data2Model.java │ │ │ └── Model2Data.java │ │ └── resources │ │ └── mapper │ │ └── OrderMainMapper.xml ├── cp-oc-main │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── org │ │ │ └── example │ │ │ └── cp │ │ │ └── oms │ │ │ ├── OrderServer.java │ │ │ ├── config │ │ │ └── AppConfig.java │ │ │ └── plugin │ │ │ └── PluginConfig.java │ │ └── resources │ │ └── log4j2.xml ├── cp-oc-spec │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── org │ │ └── example │ │ └── cp │ │ └── oms │ │ └── spec │ │ ├── DomainAbilities.java │ │ ├── Patterns.java │ │ ├── Steps.java │ │ ├── exception │ │ ├── OrderErrorReason.java │ │ ├── OrderErrorSpec.java │ │ └── OrderException.java │ │ ├── ext │ │ ├── IAssignOrderNoExt.java │ │ ├── IPostPersistExt.java │ │ ├── IPresortExt.java │ │ ├── IReviseStepsExt.java │ │ ├── ISensitiveWordsExt.java │ │ └── ISerializableIsolationExt.java │ │ ├── model │ │ ├── IOrderMain.java │ │ └── vo │ │ │ ├── IOrderItem.java │ │ │ ├── IOrderItemDelegate.java │ │ │ ├── IProduct.java │ │ │ ├── IProductDelegate.java │ │ │ └── LockEntry.java │ │ └── resource │ │ ├── IStockRpc.java │ │ └── package-info.java ├── cp-oc-test │ ├── pom.xml │ └── src │ │ └── test │ │ ├── java │ │ └── org │ │ │ └── example │ │ │ └── cp │ │ │ └── oms │ │ │ ├── ExampleTest.java │ │ │ └── PluginMechanismTest.java │ │ └── resources │ │ ├── log4j2.xml │ │ └── spring-test.xml └── pom.xml ├── order-center-domain-stock ├── order-center-stock-domain │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── org │ │ └── example │ │ └── oms │ │ └── d │ │ └── stock │ │ └── domain │ │ ├── facade │ │ └── rpc │ │ │ └── IRemoteStockRpc.java │ │ └── service │ │ └── StockService.java ├── order-center-stock-infrastructure │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── org │ │ └── example │ │ └── oms │ │ └── d │ │ └── stock │ │ └── infra │ │ └── rpc │ │ └── RemoteStockRpc.java ├── order-center-stock-spec │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── org │ │ └── example │ │ └── oms │ │ └── d │ │ └── stock │ │ └── spec │ │ ├── StockDomain.java │ │ └── service │ │ └── IStockService.java └── pom.xml ├── order-center-pattern ├── pom.xml └── src │ └── main │ └── java │ └── org │ └── example │ └── cp │ └── oms │ └── pattern │ ├── ColdChainB2BPattern.java │ ├── HairPattern.java │ ├── HomeAppliancePattern.java │ └── extension │ ├── coldchain_b2b │ └── SerializableIsolationExt.java │ ├── hair │ ├── ReviseStepsExt.java │ └── SerializableIsolationExt.java │ └── home_appliance │ ├── AssignOrderNoExt.java │ ├── PresortExt.java │ └── SerializableIsolationExt.java └── pom.xml /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Java CI with Maven 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up JDK 1.8 20 | uses: actions/setup-java@v1 21 | with: 22 | java-version: 1.8 23 | - name: Build with Maven 24 | run: mvn -B package --file pom.xml 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | target/ 3 | *.swp 4 | *.iml 5 | *.ipr 6 | *.iws 7 | .DS_Store 8 | .idea/ 9 | .classpath 10 | .ekstazi 11 | .project 12 | .settings 13 | .checkstyle 14 | *.log 15 | id_file 16 | *.epoch 17 | .factorypath 18 | .pmd 19 | .sonar/ 20 | .deploy 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | package:clean 2 | @mvn package 3 | 4 | package-plugin:clean 5 | @mvn package -Pplugin 6 | 7 | clean: 8 | @mvn clean 9 | 10 | run:package 11 | @java -XX:+HeapDumpOnOutOfMemoryError -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -Xloggc:gc.log -jar order-center-cp/cp-oc-main/target/dddplus-demo.jar 12 | 13 | run-plugin:package-plugin 14 | @java -XX:+HeapDumpOnOutOfMemoryError -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -Xss512k -Xms1G -Xmx1G -XX:MaxMetaspaceSize=128m -XX:+UnlockDiagnosticVMOptions -XX:+PrintNMTStatistics -XX:NativeMemoryTracking=summary -Xloggc:gc.log -jar order-center-cp/cp-oc-main/target/dddplus-demo.jar 9090 plugin 15 | 16 | offheap: 17 | @echo "jcmd VM.native_memory baseline" 18 | @echo "watch -d jcmd VM.native_memory summary.diff" 19 | 20 | depgraph: 21 | @mvn install 22 | @mvn com.github.ferstl:depgraph-maven-plugin:aggregate -DcreateImage=true -DreduceEdges=false -Dscope=compile "-Dincludes=*:*" 23 | @open target/dependency-graph.png 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Archived 2 | 3 | Please visit [new demo](https://github.com/funkygao/cp-ddd-framework/blob/master/dddplus-test/src/test/java/ddd/plus/showcase/README.md). 4 | 5 | # Demo of DDDplus 6 | 7 | 演示如何使用[DDDplus](https://github.com/funkygao/cp-ddd-framework)实现一套`订单履约中台OMS`。 8 | 9 | ![Requirement](https://img.shields.io/badge/JDK-8+-green.svg) 10 | [![DDDplus version](https://img.shields.io/badge/DDDplus-1.1.0--SNAPSHOT-blue)](https://github.com/funkygao/cp-ddd-framework) 11 | 12 | [OMS业务入门](https://github.com/funkygao/oms/blob/master/README.md)。 13 | 14 | ## 目录 15 | 16 | - [Terms explained](#terms-explained) 17 | - [如何运行该演示](#如何运行该演示) 18 | - [演示代码入口](#演示代码入口) 19 | - [项目基本介绍](https://github.com/funkygao/cp-ddd-framework/wiki/The-Demo) 20 | - [代码快速入门](#代码快速入门) 21 | - [代码结构](#代码结构) 22 | - [依赖关系](#依赖关系) 23 | - 一套[订单履约中台代码库](#order-center-cp) 24 | - [中台的个性化业务包](#order-center-pattern) 25 | - [三个业务前台代码库](#订单履约中台的多个业务前台) 26 | - [KA业务前台](#order-center-bp-ka) 27 | - [ISV业务前台](#order-center-bp-isv) 28 | - [生鲜业务前台](#order-center-bp-fresh) 29 | - [支撑域](#支撑域) 30 | - [如何快速搭建中台工程骨架](#如何快速搭建中台工程骨架) 31 | 32 | ## Terms explained 33 | 34 | - bp 35 | - Business Partner 36 | - 业务前台 37 | - cp 38 | - Central Platform 39 | - 中台 40 | - isv 41 | - Independent Software Vendors 42 | - ka 43 | - Key Account 44 | - 关键客户 45 | - oc 46 | - Order Center 47 | - 订单中心 48 | 49 | 50 | ## 如何运行该演示 51 | 52 | ``` bash 53 | git clone https://github.com/dddplus/dddplus-demo.git 54 | cd dddplus-demo 55 | mvn package 56 | java -jar order-center-cp/cp-oc-main/target/dddplus-demo.jar 57 | #java -jar order-center-cp/cp-oc-main/target/dddplus-demo.jar 9090 plugin 58 | 59 | # in another terminal 60 | curl -XPOST http://localhost:9090/order # submit an order 61 | curl -XPOST http://localhost:9090/reload?plugin=isv # plugin hot reloading 62 | ``` 63 | 64 | ## 演示代码入口 65 | 66 | - 启动入口 [OrderServer.java](order-center-cp/cp-oc-main/src/main/java/org/example/cp/oms/OrderServer.java) 67 | - Web Controller [OrderController.java](order-center-cp/cp-oc-controller/src/main/java/org/example/cp/oms/controller/OrderController.java) 68 | 69 | ## 代码快速入门 70 | 71 | - [中台架构特色的DDD分层架构](order-center-cp) 72 | - 也可以通过[dddplus-archetype](https://github.com/dddplus/dddplus-archetype),快速从零开始搭建中台的分层架构,并融入最佳实践 73 | - [domain层是如何通过依赖倒置模式与infrastructure层交互的](order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/facade/mq/IMessageProducer.java) 74 | - [Repository同理](order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/facade/repository/IOrderRepository.java) 75 | - [为什么依赖倒置统一存放在facade包](order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/facade/package-info.java) 76 | - 如何理解 [Step](https://github.com/funkygao/cp-ddd-framework/wiki/Steps) 77 | - 扩展点:订单的防并发 78 | - [如何识别该业务属于KA业务前台](order-center-bp-ka/src/main/java/org/example/bp/oms/ka/KaPartner.java) 79 | - [一个扩展点声明](order-center-cp/cp-oc-spec/src/main/java/org/example/cp/oms/spec/ext/ISerializableIsolationExt.java) 80 | - [该扩展点,KA业务前台的实现](order-center-bp-ka/src/main/java/org/example/bp/oms/ka/extension/SerializableIsolationExt.java) 81 | - [该扩展点,ISV业务前台的实现](order-center-bp-isv/src/main/java/org/example/bp/oms/isv/extension/SerializableIsolationExt.java) 82 | - [该扩展点,中台的一个业务场景实现](order-center-pattern/src/main/java/org/example/cp/oms/pattern/extension/coldchain_b2b/SerializableIsolationExt.java) 83 | - [扩展点被封装到DomainAbility](order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/ability/SerializableIsolationAbility.java) 84 | - [扩展点被调用](order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/service/SubmitOrder.java) 85 | - [前台对中台的步骤编排](order-center-bp-ka/src/main/java/org/example/bp/oms/ka/extension/DecideStepsExt.java) 86 | - [动态的步骤编排](order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/step/submitorder/BasicStep.java) 87 | - [扩展属性通过扩展点的实现](order-center-bp-isv/src/main/java/org/example/bp/oms/isv/extension/CustomModelExt.java) 88 | - [业务约束规则的显式化](order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/specification/ProductNotEmptySpec.java) 89 | - [如何使用](order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/ability/extension/DefaultAssignOrderNoExt.java) 90 | - [中台统一定义,兼顾前台个性化的错误码机制](order-center-cp/cp-oc-spec/src/main/java/org/example/cp/oms/spec/exception/OrderException.java) 91 | - [中台特色的领域模型](order-center-cp/cp-oc-spec/src/main/java/org/example/cp/oms/spec/model/IOrderMain.java) 92 | - spec jar里定义受限的领域模型输出给业务前台:通过接口,而不是类 93 | - 一种中台控制力更强的shared kernel机制 94 | - domain层是如何实现的领域模型接口的:[订单主档](order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/model/OrderMain.java) 95 | - [Creator的作用](order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/model/OrderMain.java#L50) 96 | - 领域步骤,业务模式等,中台如何统一定义,并输出给前台使用? 97 | - 例如,业务前台是可以编排中台的步骤的,它必须要知道中台有哪些步骤 98 | - [领域步骤的统一输出](order-center-cp/cp-oc-spec/src/main/java/org/example/cp/oms/spec/Steps.java) 99 | - [业务模式的统一输出](order-center-cp/cp-oc-spec/src/main/java/org/example/cp/oms/spec/Patterns.java) 100 | - [中台如何输出资源给业务前台使用](order-center-cp/cp-oc-spec/src/main/java/org/example/cp/oms/spec/resource/IStockRpc.java) 101 | - 如果业务前台要使用中台的MQ怎么办?能否中台封装一下,不直接暴露给前台 102 | - [库存支撑域给订单核心域的能力输出](order-center-domain-stock/order-center-stock-spec/src/main/java/org/example/oms/d/stock/spec/) 103 | - [能力的实现](order-center-domain-stock/order-center-stock-domain/src/main/java/org/example/oms/d/stock/domain/service/StockService.java) 104 | - [订单核心域对库存支撑域的调用](order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/step/submitorder/StockStep.java) 105 | - [按需打包](order-center-cp/cp-oc-main/pom.xml) 106 | - 这样才能做到一套代码,支撑国内、国际业务 107 | - 灵活的部署形式 108 | 109 | ## 代码结构 110 | 111 | ### 依赖关系 112 | 113 | ![](/doc/assets/img/ddd-depgraph.png) 114 | 115 | ### [order-center-cp](order-center-cp) 116 | 117 | 订单履约中台,通过[spec jar](order-center-cp/cp-oc-spec)为业务前台赋能,输出中台标准,并提供扩展机制。 118 | 119 | #### [order-center-pattern](order-center-pattern) 120 | 121 | 订单履约中台本身的个性化业务,即个性化的业务模式包。 122 | 123 | ### 订单履约中台的多个业务前台 124 | 125 | #### [order-center-bp-ka](order-center-bp-ka) 126 | 127 | KA,关键客户的个性化业务通过扩展点的实现完成。 128 | 129 | #### [order-center-bp-isv](order-center-bp-isv) 130 | 131 | ISV,独立软件开发商的个性化业务通过扩展点的实现完成。 132 | 133 | #### [order-center-bp-fresh](order-center-bp-fresh) 134 | 135 | Fresh,生鲜业务前台的个性化业务通过扩展点的实现完成。 136 | 137 | 这个业务BP,被中台要求不能使用Spring框架开发,不能在业务扩展包里使用AOP等Spring机制,只能严格实现中台定义的扩展点。 138 | 139 | 为了演示,ISV和KA这2个业务前台BP在开发业务扩展包时,可以使用Spring框架。 140 | 141 | ### 支撑域 142 | 143 | ![](http://www.plantuml.com/plantuml/svg/RPBFRh905CNtFWMhRy4pV6byWQwwwDf4cfYeCTNkmJzeDKHGI9lM5ajCQfk8DgcK0jMNcJiCRz7HcKObBCyzltivSCZN6uNhHgLKBLOAjPme4DS1pSBc4eyCiErSJXG52CQmkDyfaQh__setvRfqbZXjqARCInoLsdYYGV-5GfH2FnQ-ynY3Rz_WmugRtynYAyXVmBR50B8UW1mbSaWsHWecNeVUWLaxrjMK5KT1qXrcFY9fpQ6dPbY7zAO2xaDs-WFKxMDpam5HIhWylq3130MZz8T1_W261Wh7TF74OAFOj57m6lSzB2lm-8oYwVvSqj78LZ--A82bWkinXe-G7s9hXJNt21ahNB3k86g2x--xAqjN3Q5UAaginiuShvLuFY1VDl7V-HBDShYmIxWCSQ1pJKETQFBfqDVd0YOhU9Ave2LXg_Ut9gkW6pkHbwf5_dFz0W00) 144 | 145 | - [库存支撑域](order-center-domain-stock) 146 | - 更多的支撑域... 147 | 148 | ## 如何快速搭建中台工程骨架 149 | 150 | 使用 [dddplus-archetype](https://github.com/dddplus/dddplus-archetype),可以快速搭建中台的工程骨架。 151 | 152 | -------------------------------------------------------------------------------- /doc/assets/img/ddd-depgraph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dddplus/dddplus-demo/879f2359d1d4d656ec8af67ae4b4a723047ea3b3/doc/assets/img/ddd-depgraph.png -------------------------------------------------------------------------------- /order-center-bp-fresh/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.example 6 | dddplus-demo 7 | 0.0.1 8 | 9 | 10 | order-center-bp-fresh 11 | DDDplus :: Demo :: BP :: Fresh 12 | 生鲜的业务前台BP,演示不支持Spring的业务场景 13 | 14 | 15 | 16 | 17 | org.example 18 | cp-oc-spec 19 | ${project.version} 20 | provided 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /order-center-bp-fresh/src/main/java/org/example/bp/oms/fresh/FreshPartner.java: -------------------------------------------------------------------------------- 1 | package org.example.bp.oms.fresh; 2 | 3 | import io.github.dddplus.annotation.Partner; 4 | import io.github.dddplus.ext.IIdentityResolver; 5 | import org.example.cp.oms.spec.model.IOrderMain; 6 | 7 | @Partner(code = FreshPartner.CODE, name = "生鲜业务前台BP") 8 | public class FreshPartner implements IIdentityResolver { 9 | public static final String CODE = "fresh"; 10 | 11 | @Override 12 | public boolean match(IOrderMain model) { 13 | return model.getSource().toLowerCase().equals(CODE); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /order-center-bp-fresh/src/main/java/org/example/bp/oms/fresh/extension/DecideStepsExt.java: -------------------------------------------------------------------------------- 1 | package org.example.bp.oms.fresh.extension; 2 | 3 | import io.github.dddplus.annotation.Extension; 4 | import io.github.dddplus.ext.IDecideStepsExt; 5 | import io.github.dddplus.model.IDomainModel; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.example.bp.oms.fresh.FreshPartner; 8 | import org.example.cp.oms.spec.Steps; 9 | 10 | import javax.validation.constraints.NotNull; 11 | import java.util.*; 12 | 13 | @Extension(code = FreshPartner.CODE, name = "生鲜业务前台对所有流程的编排", value = "freshDecideStepsExt") 14 | @Slf4j 15 | public class DecideStepsExt implements IDecideStepsExt { 16 | private static final List emptySteps = Collections.emptyList(); 17 | 18 | @Override 19 | public List decideSteps(@NotNull IDomainModel model, @NotNull String activityCode) { 20 | List steps = stepsRegistry.get(activityCode); 21 | if (steps == null) { 22 | return emptySteps; 23 | } 24 | 25 | log.info("Fresh steps: {}", steps); 26 | return steps; 27 | } 28 | 29 | // 所有流程步骤注册表 {activityCode, stepCodeList} 30 | private static Map> stepsRegistry = new HashMap<>(); 31 | static { 32 | // 接单的步骤 33 | List submitOrderSteps = new ArrayList<>(); 34 | stepsRegistry.put(Steps.SubmitOrder.Activity, submitOrderSteps); 35 | submitOrderSteps.add(Steps.SubmitOrder.BasicStep); 36 | submitOrderSteps.add(Steps.SubmitOrder.StockStep); 37 | submitOrderSteps.add(Steps.SubmitOrder.PersistStep); 38 | 39 | // 订单取消步骤 40 | List cancelOrderSteps = new ArrayList<>(); 41 | stepsRegistry.put(Steps.CancelOrder.Activity, cancelOrderSteps); 42 | cancelOrderSteps.add(Steps.CancelOrder.StateStep); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /order-center-bp-fresh/src/main/java/org/example/bp/oms/fresh/extension/PostPersitExt.java: -------------------------------------------------------------------------------- 1 | package org.example.bp.oms.fresh.extension; 2 | 3 | import io.github.dddplus.annotation.Extension; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.example.bp.oms.fresh.FreshPartner; 6 | import org.example.cp.oms.spec.ext.IPostPersistExt; 7 | import org.example.cp.oms.spec.model.IOrderMain; 8 | 9 | @Slf4j 10 | @Extension(code = FreshPartner.CODE, value = "freshPostPersistExt") 11 | public class PostPersitExt implements IPostPersistExt { 12 | 13 | @Override 14 | public void afterPersist(IOrderMain model) { 15 | log.info("{} 落库了,我,Fresh,要发个MQ通知我的下游!", model); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /order-center-bp-fresh/src/main/java/org/example/bp/oms/fresh/extension/PresortExt.java: -------------------------------------------------------------------------------- 1 | package org.example.bp.oms.fresh.extension; 2 | 3 | import io.github.dddplus.annotation.Extension; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.example.bp.oms.fresh.FreshPartner; 6 | import org.example.cp.oms.spec.ext.IPresortExt; 7 | import org.example.cp.oms.spec.model.IOrderMain; 8 | 9 | import javax.validation.constraints.NotNull; 10 | 11 | @Extension(code = FreshPartner.CODE, value = "freshPresort") 12 | @Slf4j 13 | public class PresortExt implements IPresortExt { 14 | 15 | @Override 16 | public void presort(@NotNull IOrderMain model) { 17 | log.info("Fresh里预分拣的结果:{}", new MockInnerClass().getResult()); 18 | } 19 | 20 | // 演示内部类的使用 21 | private static class MockInnerClass { 22 | int getResult() { 23 | return 2; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /order-center-bp-isv/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.example 6 | dddplus-demo 7 | 0.0.1 8 | 9 | 10 | order-center-bp-isv 11 | DDDplus :: Demo :: BP :: ISV 12 | ISV通路的业务前台BP 13 | 14 | 15 | 16 | 17 | org.example 18 | cp-oc-spec 19 | ${project.version} 20 | provided 21 | 22 | 23 | 24 | 25 | com.google.guava 26 | guava 27 | ${guava.version} 28 | provided 29 | 30 | 31 | org.aspectj 32 | aspectjweaver 33 | ${aspectjweaver.version} 34 | provided 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /order-center-bp-isv/src/main/java/org/example/bp/oms/isv/IsvPartner.java: -------------------------------------------------------------------------------- 1 | package org.example.bp.oms.isv; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.example.cp.oms.spec.model.IOrderMain; 5 | import io.github.dddplus.annotation.Partner; 6 | import io.github.dddplus.ext.IIdentityResolver; 7 | import org.springframework.beans.factory.DisposableBean; 8 | 9 | @Partner(code = IsvPartner.CODE, name = "ISV业务前台") 10 | @Slf4j 11 | public class IsvPartner implements IIdentityResolver, DisposableBean { 12 | public static final String CODE = "ISV"; 13 | 14 | public IsvPartner() { 15 | // hook how Spring create bean instance 16 | log.info("ISV new instanced, cl:{}", this.getClass().getClassLoader()); 17 | } 18 | 19 | @Override 20 | public boolean match(IOrderMain model) { 21 | if (model.getSource() == null) { 22 | return false; 23 | } 24 | 25 | return model.getSource().equalsIgnoreCase(CODE); 26 | } 27 | 28 | @Override 29 | public void destroy() throws Exception { 30 | log.warn("IsvPartner destroyed"); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /order-center-bp-isv/src/main/java/org/example/bp/oms/isv/PluginListener.java: -------------------------------------------------------------------------------- 1 | package org.example.bp.oms.isv; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import io.github.dddplus.plugin.IContainerContext; 5 | import io.github.dddplus.plugin.IPluginListener; 6 | 7 | @Slf4j 8 | public class PluginListener implements IPluginListener { 9 | 10 | @Override 11 | public void onCommitted(IContainerContext ctx) throws Exception { 12 | log.info("ISV Jar loaded, ctx:{}", ctx); 13 | } 14 | 15 | @Override 16 | public void onPrepared(IContainerContext ctx) throws Exception { 17 | 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /order-center-bp-isv/src/main/java/org/example/bp/oms/isv/aop/AutoLogger.java: -------------------------------------------------------------------------------- 1 | package org.example.bp.oms.isv.aop; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * 自动打印入参、出参. 7 | */ 8 | @Target(ElementType.METHOD) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | @Documented 11 | public @interface AutoLogger { 12 | } 13 | -------------------------------------------------------------------------------- /order-center-bp-isv/src/main/java/org/example/bp/oms/isv/aop/AutoLoggerAspect.java: -------------------------------------------------------------------------------- 1 | package org.example.bp.oms.isv.aop; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.aspectj.lang.ProceedingJoinPoint; 5 | import org.aspectj.lang.Signature; 6 | import org.aspectj.lang.annotation.Around; 7 | import org.aspectj.lang.annotation.Aspect; 8 | import org.springframework.beans.factory.InitializingBean; 9 | import org.springframework.stereotype.Component; 10 | 11 | // 业务开展包里可以自己实现AOP机制 12 | @Aspect 13 | @Slf4j 14 | @Component 15 | public class AutoLoggerAspect implements InitializingBean { 16 | 17 | public AutoLoggerAspect() { 18 | log.info("Spring created instance AutoLoggerAspect!"); 19 | } 20 | 21 | @Override 22 | public void afterPropertiesSet() throws Exception { 23 | log.info("AutoLoggerAspect 注册 Spring lifecycle ok"); 24 | } 25 | 26 | @Around("@annotation(autoLogger)") 27 | public Object around(ProceedingJoinPoint pjp, AutoLogger autoLogger) throws Throwable { 28 | Signature signature = pjp.getSignature(); 29 | Object[] args = pjp.getArgs(); 30 | log.info("{}.{} 入参:{}", signature.getDeclaringTypeName(), signature.getName(), args); 31 | try { 32 | return pjp.proceed(); 33 | } catch (Throwable cause) { 34 | log.error("{}.{} error", signature.getDeclaringTypeName(), signature.getName(), cause); 35 | throw cause; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /order-center-bp-isv/src/main/java/org/example/bp/oms/isv/extension/CustomModelExt.java: -------------------------------------------------------------------------------- 1 | package org.example.bp.oms.isv.extension; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import io.github.dddplus.annotation.Extension; 5 | import io.github.dddplus.api.ApiResult; 6 | import io.github.dddplus.api.RequestProfile; 7 | import io.github.dddplus.ext.IModelAttachmentExt; 8 | import org.example.bp.oms.isv.IsvPartner; 9 | import org.example.bp.oms.isv.aop.AutoLogger; 10 | import org.example.cp.oms.spec.exception.OrderErrorReason; 11 | import org.example.cp.oms.spec.exception.OrderException; 12 | import org.example.cp.oms.spec.model.IOrderMain; 13 | 14 | import javax.validation.constraints.NotNull; 15 | import java.util.Map; 16 | 17 | @Slf4j 18 | @Extension(code = IsvPartner.CODE, value = "isvCustomModel", name = "ISV前台的订单个性化字段处理逻辑") 19 | public class CustomModelExt implements IModelAttachmentExt { 20 | private static final String KEY_STATION_NO = "_station_contact_"; 21 | 22 | @Override 23 | @AutoLogger 24 | public void explain(@NotNull RequestProfile source, @NotNull IOrderMain target) { 25 | // 入参里预留了扩展属性 26 | Map ext = source.getExt(); 27 | // 站点联系人号码,是个性化字段,中台只存储,不负责逻辑:前台来处理逻辑,并告诉中台存储到哪些已预留的字段 28 | String stationContactNo = ext.get(KEY_STATION_NO); 29 | if (stationContactNo == null || stationContactNo.length() < 5) { 30 | // ISV针对该字段的特有业务逻辑 31 | throw new OrderException(OrderErrorReason.Custom.Custom).withCustom("109"); 32 | } 33 | 34 | // 落到预留字段上,ISV想把它保存到x2字段 35 | // 注意:预留字段也可能保存复杂对象,例如json:前台进行codec就好 36 | log.info("站点联系人号码:{},保存到x2字段", stationContactNo); 37 | target.setX2(stationContactNo); 38 | } 39 | 40 | @Override 41 | public void explain(@NotNull IOrderMain model) { 42 | } 43 | 44 | @Override 45 | public void render(@NotNull IOrderMain source, @NotNull ApiResult target) { 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /order-center-bp-isv/src/main/java/org/example/bp/oms/isv/extension/DecideStepsExt.java: -------------------------------------------------------------------------------- 1 | package org.example.bp.oms.isv.extension; 2 | 3 | import org.example.bp.oms.isv.IsvPartner; 4 | import org.example.bp.oms.isv.aop.AutoLogger; 5 | import org.example.cp.oms.spec.Steps; 6 | import io.github.dddplus.annotation.Extension; 7 | import io.github.dddplus.ext.IDecideStepsExt; 8 | import io.github.dddplus.model.IDomainModel; 9 | import org.example.cp.oms.spec.resource.IStockRpc; 10 | 11 | import javax.annotation.Resource; 12 | import javax.validation.constraints.NotNull; 13 | import java.util.*; 14 | 15 | @Extension(code = IsvPartner.CODE, name = "ISV业务前台对所有流程的编排", value = "isvDecideStepsExt") 16 | public class DecideStepsExt implements IDecideStepsExt { 17 | private static final List emptySteps = Collections.emptyList(); 18 | 19 | // 通过RPC调用库存服务 20 | @Resource 21 | private IStockRpc stockRpc; 22 | 23 | @Override 24 | @AutoLogger 25 | public List decideSteps(@NotNull IDomainModel model, @NotNull String activityCode) { 26 | stockRpc.preOccupyStock("SKU From ISV"); // 测试Plugin的Spring注入功能 27 | 28 | List steps = stepsRegistry.get(activityCode); 29 | if (steps == null) { 30 | return emptySteps; 31 | } 32 | 33 | return steps; 34 | } 35 | 36 | // 所有流程步骤注册表 {activityCode, stepCodeList} 37 | private static Map> stepsRegistry = new HashMap<>(); 38 | static { 39 | List submitOrderSteps = new ArrayList<>(); 40 | stepsRegistry.put(Steps.SubmitOrder.Activity, submitOrderSteps); 41 | submitOrderSteps.add(Steps.SubmitOrder.BasicStep); 42 | submitOrderSteps.add(Steps.SubmitOrder.PersistStep); 43 | submitOrderSteps.add(Steps.SubmitOrder.BroadcastStep); 44 | 45 | List cancelOrderSteps = new ArrayList<>(); 46 | stepsRegistry.put(Steps.CancelOrder.Activity, cancelOrderSteps); 47 | cancelOrderSteps.add(Steps.CancelOrder.BasicStep); 48 | cancelOrderSteps.add(Steps.CancelOrder.StateStep); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /order-center-bp-isv/src/main/java/org/example/bp/oms/isv/extension/PresortExt.java: -------------------------------------------------------------------------------- 1 | package org.example.bp.oms.isv.extension; 2 | 3 | import com.google.common.collect.HashMultiset; 4 | import com.google.common.collect.Multiset; 5 | import lombok.extern.slf4j.Slf4j; 6 | import io.github.dddplus.annotation.Extension; 7 | import org.example.bp.oms.isv.IsvPartner; 8 | import org.example.bp.oms.isv.aop.AutoLogger; 9 | import org.example.bp.oms.isv.extension.util.WarehouseUtil; 10 | import org.example.cp.oms.spec.ext.IPresortExt; 11 | import org.example.cp.oms.spec.model.IOrderMain; 12 | 13 | import javax.validation.constraints.NotNull; 14 | import java.io.IOException; 15 | import java.io.InputStream; 16 | import java.io.InputStreamReader; 17 | import java.util.Properties; 18 | 19 | @Extension(code = IsvPartner.CODE, value = "isvPresort") 20 | @Slf4j 21 | public class PresortExt implements IPresortExt { 22 | 23 | @Override 24 | @AutoLogger 25 | public void presort(@NotNull IOrderMain model) { 26 | log.info("ISV里预分拣的结果:{}", new MockInnerClass().getResult()); 27 | 28 | // 演示第三方包的使用:guava 29 | Multiset multiset = HashMultiset.create(); 30 | multiset.add("a"); 31 | multiset.add("a"); 32 | multiset.add("b"); 33 | log.info("count(a): {}", multiset.count("a")); 34 | log.info("仓库号:{}", WarehouseUtil.getWarehouseNo()); 35 | 36 | // Guava loaded with sun.misc.Launcher$AppClassLoader@1540e19d 37 | log.info("Guava loaded with {}", multiset.getClass().getClassLoader()); 38 | 39 | // 演示Plugin自带资源文件的场景 40 | loadProperties(); 41 | } 42 | 43 | // 演示内部类的使用 44 | private static class MockInnerClass { 45 | int getResult() { 46 | return 1; 47 | } 48 | } 49 | 50 | private void loadProperties() { 51 | InputStream is = this.getClass().getResourceAsStream("/config.properties"); 52 | InputStreamReader inputStreamReader = null; 53 | Properties properties = new Properties(); 54 | try { 55 | inputStreamReader = new InputStreamReader(is, "UTF-8"); 56 | properties.load(inputStreamReader); 57 | } catch (IOException e) { 58 | throw new RuntimeException(e); 59 | } finally { 60 | if (inputStreamReader != null) { 61 | try { 62 | inputStreamReader.close(); 63 | } catch (IOException ignored) { 64 | } 65 | } 66 | } 67 | 68 | log.info("加载资源文件成功!站点名称:{}", properties.getProperty("site_name")); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /order-center-bp-isv/src/main/java/org/example/bp/oms/isv/extension/SerializableIsolationExt.java: -------------------------------------------------------------------------------- 1 | package org.example.bp.oms.isv.extension; 2 | 3 | import io.github.dddplus.annotation.Extension; 4 | import org.example.bp.oms.isv.IsvPartner; 5 | import org.example.cp.oms.spec.ext.ISerializableIsolationExt; 6 | import org.example.cp.oms.spec.model.IOrderMain; 7 | import org.example.cp.oms.spec.model.vo.LockEntry; 8 | 9 | import javax.validation.constraints.NotNull; 10 | import java.util.concurrent.TimeUnit; 11 | 12 | @Extension(code = IsvPartner.CODE, value = "isvSerializableIsolationExt", name = "ISV场景的订单锁机制") 13 | public class SerializableIsolationExt implements ISerializableIsolationExt { 14 | 15 | @Override 16 | public LockEntry createLockEntry(@NotNull IOrderMain model) { 17 | return new LockEntry(model.customerProvidedOrderNo(), 50, TimeUnit.MINUTES); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /order-center-bp-isv/src/main/java/org/example/bp/oms/isv/extension/util/WarehouseUtil.java: -------------------------------------------------------------------------------- 1 | package org.example.bp.oms.isv.extension.util; 2 | 3 | public class WarehouseUtil { 4 | 5 | public static String getWarehouseNo() { 6 | return "WH009"; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /order-center-bp-isv/src/main/resources/config.properties: -------------------------------------------------------------------------------- 1 | site_name=北京市海淀区中关村中路1号 -------------------------------------------------------------------------------- /order-center-bp-isv/src/main/resources/plugin-isv.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /order-center-bp-ka/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.example 6 | dddplus-demo 7 | 0.0.1 8 | 9 | 10 | order-center-bp-ka 11 | DDDplus :: Demo :: BP :: KA 12 | KA(关键客户)的业务前台BP 13 | 14 | 15 | 16 | 17 | org.example 18 | cp-oc-spec 19 | ${project.version} 20 | provided 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /order-center-bp-ka/src/main/java/org/example/bp/oms/ka/KaPartner.java: -------------------------------------------------------------------------------- 1 | package org.example.bp.oms.ka; 2 | 3 | import org.example.cp.oms.spec.model.IOrderMain; 4 | import io.github.dddplus.annotation.Partner; 5 | import io.github.dddplus.ext.IIdentityResolver; 6 | 7 | @Partner(code = KaPartner.CODE, name = "KA业务前台") 8 | public class KaPartner implements IIdentityResolver { 9 | public static final String CODE = "KA"; 10 | 11 | @Override 12 | public boolean match(IOrderMain model) { 13 | if (model.getSource() == null) { 14 | return false; 15 | } 16 | 17 | return model.getSource().equalsIgnoreCase(CODE); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /order-center-bp-ka/src/main/java/org/example/bp/oms/ka/extension/AssignOrderNoExt.java: -------------------------------------------------------------------------------- 1 | package org.example.bp.oms.ka.extension; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.example.bp.oms.ka.KaPartner; 5 | import org.example.cp.oms.spec.ext.IAssignOrderNoExt; 6 | import org.example.cp.oms.spec.model.IOrderMain; 7 | import io.github.dddplus.annotation.Extension; 8 | import org.example.cp.oms.spec.resource.IStockRpc; 9 | 10 | import javax.annotation.Resource; 11 | import javax.validation.constraints.NotNull; 12 | 13 | @Extension(code = KaPartner.CODE, value = "kaAssignOrderNoExt") 14 | @Slf4j 15 | public class AssignOrderNoExt implements IAssignOrderNoExt { 16 | public static final String KA_ORDER_NO = "KA1012"; 17 | 18 | @Resource 19 | private IStockRpc stockRpc; 20 | 21 | @Override 22 | public void assignOrderNo(@NotNull IOrderMain model) { 23 | log.info("KA 预占库存 GSM098"); 24 | if (!stockRpc.preOccupyStock("GSM098")) { 25 | throw new RuntimeException("预占库存失败"); 26 | } 27 | 28 | model.assignOrderNo(this, KA_ORDER_NO); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /order-center-bp-ka/src/main/java/org/example/bp/oms/ka/extension/DecideStepsExt.java: -------------------------------------------------------------------------------- 1 | package org.example.bp.oms.ka.extension; 2 | 3 | import org.example.bp.oms.ka.KaPartner; 4 | import org.example.cp.oms.spec.Steps; 5 | import io.github.dddplus.annotation.Extension; 6 | import io.github.dddplus.ext.IDecideStepsExt; 7 | import io.github.dddplus.model.IDomainModel; 8 | 9 | import javax.validation.constraints.NotNull; 10 | import java.util.*; 11 | 12 | @Extension(code = KaPartner.CODE, name = "KA业务前台对所有流程的编排", value = "kaDecideStepsExt") 13 | public class DecideStepsExt implements IDecideStepsExt { 14 | private static final List emptySteps = Collections.emptyList(); 15 | 16 | @Override 17 | public List decideSteps(@NotNull IDomainModel model, @NotNull String activityCode) { 18 | List steps = stepsRegistry.get(activityCode); 19 | if (steps == null) { 20 | return emptySteps; 21 | } 22 | 23 | return steps; 24 | } 25 | 26 | // 所有流程步骤注册表 {activityCode, stepCodeList} 27 | private static Map> stepsRegistry = new HashMap<>(); 28 | static { 29 | // 接单的步骤 30 | List submitOrderSteps = new ArrayList<>(); 31 | stepsRegistry.put(Steps.SubmitOrder.Activity, submitOrderSteps); 32 | submitOrderSteps.add(Steps.SubmitOrder.BasicStep); 33 | submitOrderSteps.add(Steps.SubmitOrder.PersistStep); 34 | 35 | // 订单取消步骤 36 | List cancelOrderSteps = new ArrayList<>(); 37 | stepsRegistry.put(Steps.CancelOrder.Activity, cancelOrderSteps); 38 | cancelOrderSteps.add(Steps.CancelOrder.StateStep); 39 | cancelOrderSteps.add(Steps.CancelOrder.PersistStep); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /order-center-bp-ka/src/main/java/org/example/bp/oms/ka/extension/ReviseStepsExt.java: -------------------------------------------------------------------------------- 1 | package org.example.bp.oms.ka.extension; 2 | 3 | import io.github.dddplus.annotation.Extension; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.example.bp.oms.ka.KaPartner; 6 | import org.example.cp.oms.spec.ext.IReviseStepsExt; 7 | import org.example.cp.oms.spec.model.IOrderMain; 8 | 9 | import java.util.List; 10 | 11 | @Slf4j 12 | @Extension(code = KaPartner.CODE, value = "kaReviseStepsExt") 13 | public class ReviseStepsExt implements IReviseStepsExt { 14 | 15 | @Override 16 | public List reviseSteps(IOrderMain model) { 17 | log.info("KA will not revise subsequent steps"); 18 | return null; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /order-center-bp-ka/src/main/java/org/example/bp/oms/ka/extension/SerializableIsolationExt.java: -------------------------------------------------------------------------------- 1 | package org.example.bp.oms.ka.extension; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import io.github.dddplus.annotation.Extension; 5 | import org.example.cp.oms.spec.ext.ISerializableIsolationExt; 6 | import org.example.cp.oms.spec.model.IOrderMain; 7 | import org.example.cp.oms.spec.model.vo.LockEntry; 8 | import org.example.bp.oms.ka.KaPartner; 9 | 10 | import javax.validation.constraints.NotNull; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | @Extension(code = KaPartner.CODE, value = "kaSerializableIsolationExt") 14 | @Slf4j 15 | public class SerializableIsolationExt implements ISerializableIsolationExt { 16 | 17 | @Override 18 | public LockEntry createLockEntry(@NotNull IOrderMain model) { 19 | log.info("KA的锁TTL大一些"); 20 | return new LockEntry(model.customerProvidedOrderNo(), 11, TimeUnit.MINUTES); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /order-center-bp-ka/src/main/resources/plugin-ka.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /order-center-cp/README.md: -------------------------------------------------------------------------------- 1 | # order-center-cp(订单履约中台) 2 | 3 | ## 应用的入口 4 | 5 | [订单履约中台的Server启动](cp-oc-main/src/main/java/org/example/cp/oms/OrderServer.java) 6 | 7 | ## HTTP Controller 8 | 9 | [订单履约中台的Controller](cp-oc-controller/src/main/java/org/example/cp/oms/controller/OrderController.java) 10 | 11 | ## 分层架构 12 | 13 | ![](/doc/assets/img/ddd-depgraph.png) 14 | 15 | ### 具有中台特色的DDD分层架构 16 | 17 | 通过[spec jar](cp-oc-spec)为业务前台赋能,输出中台标准,并提供扩展机制。 18 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-controller/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.example 6 | order-center-cp 7 | 0.0.1 8 | 9 | 10 | cp-oc-controller 11 | DDDplus :: Demo :: CP :: Controller 12 | 13 | 14 | 15 | org.example 16 | cp-oc-infrastructure 17 | ${project.version} 18 | 19 | 20 | 21 | org.springframework 22 | spring-webmvc 23 | ${spring.version} 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-controller/src/main/java/org/example/cp/oms/controller/OrderController.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.controller; 2 | 3 | import io.github.dddplus.api.RequestProfile; 4 | import io.github.dddplus.runtime.registry.Container; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.example.cp.oms.domain.model.OrderMain; 7 | import org.example.cp.oms.domain.model.OrderModelCreator; 8 | import org.example.cp.oms.domain.service.SubmitOrder; 9 | import org.slf4j.MDC; 10 | import org.springframework.web.bind.annotation.*; 11 | 12 | import javax.annotation.Resource; 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | 16 | @RestController 17 | @Slf4j 18 | public class OrderController { 19 | 20 | // DDD Application Layer depends on Domain Layer 21 | @Resource 22 | private SubmitOrder submitOrder; 23 | 24 | // 模拟下单 25 | // curl -XPOST localhost:9090/order?type=isv 26 | @RequestMapping(value = "/order", method = {RequestMethod.POST, RequestMethod.GET}) 27 | @ResponseBody 28 | public String submitOrder(@RequestParam(required = false) String type) { 29 | if (type == null) { 30 | type = "ISV"; // ISV by default 31 | } 32 | 33 | log.info("type={}", type); 34 | 35 | // DTO 转换为 domain model,通过creator保护、封装domain model 36 | // 具体项目使用MapStruct会更方便,这里为了演示,全手工进行对象转换了 37 | RequestProfile requestProfile = new RequestProfile(); 38 | requestProfile.setTraceId(String.valueOf(System.nanoTime())); 39 | 40 | // 演示个性化字段:站点联系人号码是ISV前台场景才会需要的字段,其他场景不需要 41 | requestProfile.getExt().put("_station_contact_", "139100988343"); 42 | MDC.put("tid", requestProfile.getTraceId()); // session scope log identifier 43 | 44 | // 这里手工创建一个模拟下单的请求 45 | OrderModelCreator creator = new OrderModelCreator(); 46 | creator.setRequestProfile(requestProfile); 47 | creator.setSource(type); 48 | creator.setCustomerNo("goog"); // if 'home', HomeAppliancePattern will match 49 | creator.setExternalNo("20200987655"); 50 | OrderMain model = OrderMain.createWith(creator); 51 | 52 | // 实际项目会使用MapStruct,进行转换 53 | // OrderModelCreator orderModelCreator = SubmitOrderRequestTranslator.instance.translate(new SubmitOrderRequest()); 54 | 55 | // 调用domain service完成该use case 56 | submitOrder.submit(model); 57 | // ISV业务前台的下单执行: 58 | // SerializableIsolationExt -> DecideStepsExt -> BasicStep(PresortExt) -> PersistStep(AssignOrderNoExt, CustomModelAbility) -> BroadcastStep 59 | // 查看日志,了解具体执行情况 60 | return "Order accepted!"; 61 | } 62 | 63 | // 模拟热加载Plugin Jar 64 | // curl localhost:9090/reload?plugin=isv 65 | @RequestMapping(value = "/reload") 66 | @ResponseBody 67 | public String reload(@RequestParam(required = false) String plugin) { 68 | MDC.put("tid", String.valueOf(System.nanoTime())); // session scope log identifier 69 | 70 | if (plugin == null) { 71 | plugin = "isv"; 72 | } else { 73 | plugin = plugin.toLowerCase(); 74 | } 75 | 76 | String pluginJar = plugins.get(plugin); 77 | if (pluginJar == null) { 78 | log.warn("Invalid plugin param:{}", plugin); 79 | return "Unknown plugin:" + plugin; 80 | } 81 | 82 | log.info("active plugins: {}", Container.getInstance().getActivePlugins()); 83 | 84 | log.info("Reloading plugin:{} {}", plugin, pluginJar); 85 | boolean useSpring = true; 86 | if (plugin.toLowerCase().equals("fresh")) { 87 | useSpring = false; 88 | } 89 | try { 90 | // 具体使用时,需要一套plugin jar的发布平台配合使用:发布,灰度发布,回滚,版本控制,打包管理等 91 | Container.getInstance().loadPartnerPlugin(plugin, "v1", pluginJar, useSpring); 92 | } catch (Throwable cause) { 93 | log.error("fails to reload Plugin:{}", plugin, cause); 94 | return cause.getMessage(); 95 | } 96 | 97 | return plugin + " Plugin Reloaded!"; 98 | } 99 | 100 | private static final Map plugins = new HashMap<>(); 101 | static { 102 | plugins.put("isv", "order-center-bp-isv/target/order-center-bp-isv-0.0.1.jar"); 103 | plugins.put("ka", "order-center-bp-ka/target/order-center-bp-ka-0.0.1.jar"); 104 | plugins.put("fresh", "order-center-bp-fresh/target/order-center-bp-fresh-0.0.1.jar"); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-controller/src/main/java/org/example/cp/oms/controller/dto/CancelOrderRequest.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.controller.dto; 2 | 3 | import lombok.Data; 4 | 5 | import javax.validation.constraints.NotNull; 6 | import java.io.Serializable; 7 | 8 | @Data 9 | public class CancelOrderRequest implements Serializable { 10 | private static final long serialVersionUID = 870061998490977022L; 11 | 12 | @NotNull 13 | private String orderNo; 14 | } 15 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-controller/src/main/java/org/example/cp/oms/controller/dto/SubmitOrderRequest.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.controller.dto; 2 | 3 | import lombok.Data; 4 | 5 | import javax.validation.Valid; 6 | import javax.validation.constraints.NotNull; 7 | import java.io.Serializable; 8 | import java.math.BigDecimal; 9 | import java.util.List; 10 | 11 | @Data 12 | public class SubmitOrderRequest implements Serializable { 13 | private static final long serialVersionUID = 870061998490977022L; 14 | 15 | @NotNull 16 | private String source; 17 | 18 | private String customerNo; 19 | private String externalNo; 20 | 21 | @Valid 22 | private List orderItemList; 23 | 24 | @Data 25 | public static class OrderItem implements Serializable { 26 | private static final long serialVersionUID = 870061998490977022L; 27 | 28 | @NotNull 29 | private String sku; 30 | 31 | @NotNull 32 | private Integer quantity; 33 | 34 | private String orderLine; 35 | 36 | private BigDecimal price; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-controller/src/main/java/org/example/cp/oms/controller/translator/SubmitOrderRequestTranslator.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.controller.translator; 2 | 3 | import io.github.dddplus.IBaseTranslator; 4 | import org.example.cp.oms.controller.dto.SubmitOrderRequest; 5 | import org.example.cp.oms.domain.model.OrderModelCreator; 6 | import org.mapstruct.Mapper; 7 | import org.mapstruct.factory.Mappers; 8 | 9 | @Mapper 10 | public interface SubmitOrderRequestTranslator extends IBaseTranslator { 11 | SubmitOrderRequestTranslator instance = Mappers.getMapper(SubmitOrderRequestTranslator.class); 12 | } 13 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.example 6 | order-center-cp 7 | 0.0.1 8 | 9 | 10 | cp-oc-domain 11 | DDDplus :: Demo :: CP :: Domain 12 | 13 | 14 | 15 | org.example 16 | cp-oc-spec 17 | ${project.version} 18 | 19 | 20 | 21 | 22 | org.example 23 | order-center-stock-spec 24 | ${project.version} 25 | 26 | 27 | 28 | io.github.dddplus 29 | dddplus-plugin 30 | 0.1.0 31 | 32 | 33 | org.aspectj 34 | aspectjweaver 35 | ${aspectjweaver.version} 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/CoreDomain.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.domain; 2 | 3 | import io.github.dddplus.annotation.Domain; 4 | 5 | @Domain(code = CoreDomain.CODE, name = "订单核心域") 6 | public class CoreDomain { 7 | public static final String CODE = "core"; 8 | 9 | private static final ApplicationLifeCycle applicationLifeCycle = new ApplicationLifeCycle(); 10 | 11 | /** 12 | * 获取应用生命周期状态. 13 | */ 14 | public static ApplicationLifeCycle getApplicationLifeCycle() { 15 | return applicationLifeCycle; 16 | } 17 | 18 | public static final class ApplicationLifeCycle { 19 | 20 | /** 21 | * 应用当前状态 22 | */ 23 | private State state = State.INITIALIZED; 24 | 25 | public enum State { 26 | /** 27 | * Initialized but not yet started. 28 | */ 29 | INITIALIZED, 30 | 31 | /** 32 | * In the process of starting. 33 | */ 34 | STARTING, 35 | 36 | /** 37 | * Has started. 38 | */ 39 | STARTED, 40 | 41 | /** 42 | * Stopping is in progress. 43 | */ 44 | STOPPING, 45 | 46 | /** 47 | * Has stopped. 48 | */ 49 | STOPPED 50 | } 51 | 52 | /** 53 | * 启动应用完毕 54 | */ 55 | public void started() { 56 | state = State.STARTED; 57 | } 58 | 59 | /** 60 | * 开始停止应用 61 | */ 62 | public void stop() { 63 | state = State.STOPPING; 64 | } 65 | 66 | /** 67 | * 停止应用完毕 68 | */ 69 | public void stopped() { 70 | state = State.STOPPED; 71 | } 72 | 73 | /** 74 | * 是否停止中 75 | * 76 | * @return true or false 77 | */ 78 | public boolean isStopping() { 79 | return state == State.STOPPING; 80 | } 81 | 82 | /** 83 | * 是否通知完毕 84 | * 85 | * @return true or false 86 | */ 87 | public boolean isStopped() { 88 | return state == State.STOPPED; 89 | } 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/StartupListener.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.domain; 2 | 3 | import io.github.dddplus.runtime.IStartupListener; 4 | import io.github.dddplus.runtime.registry.DomainArtifacts; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.stereotype.Component; 7 | 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | @Component 12 | @Slf4j 13 | public class StartupListener implements IStartupListener { 14 | 15 | @Override 16 | public void onStartComplete() { 17 | log.info("DDD framework booted!"); 18 | 19 | DomainArtifacts artifacts = DomainArtifacts.getInstance(); 20 | log.info("Domains:"); 21 | for (DomainArtifacts.Domain domain : artifacts.getDomains()) { 22 | log.info(" {}", domain.getName()); 23 | } 24 | 25 | log.info("Specifications:"); 26 | for (DomainArtifacts.Specification specification : artifacts.getSpecifications()) { 27 | log.info(" {}", specification.getName()); 28 | } 29 | 30 | log.info("Steps:"); 31 | for (Map.Entry> entry : artifacts.getSteps().entrySet()) { 32 | log.info(" activity: {}", entry.getKey()); 33 | for (DomainArtifacts.Step step : entry.getValue()) { 34 | log.info(" step: {}, tags:{}", step.getCode(), step.getTags()); 35 | } 36 | } 37 | 38 | log.info("Extensions:"); 39 | for (DomainArtifacts.Extension extension : artifacts.getExtensions()) { 40 | log.info(" {}: partners:{}, patterns:{}", extension.getExt().getCanonicalName(), extension.getPartners(), extension.getPatterns()); 41 | } 42 | 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/ability/AssignOrderNoAbility.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.domain.ability; 2 | 3 | import org.example.cp.oms.domain.CoreDomain; 4 | import org.example.cp.oms.domain.ability.extension.DefaultAssignOrderNoExt; 5 | import org.example.cp.oms.spec.ext.IAssignOrderNoExt; 6 | import org.example.cp.oms.spec.model.IOrderMain; 7 | import io.github.dddplus.annotation.DomainAbility; 8 | import io.github.dddplus.runtime.BaseDomainAbility; 9 | 10 | import javax.annotation.Resource; 11 | import javax.validation.constraints.NotNull; 12 | 13 | @DomainAbility(domain = CoreDomain.CODE, name = "分配订单号的能力") 14 | public class AssignOrderNoAbility extends BaseDomainAbility { 15 | 16 | @Resource 17 | private DefaultAssignOrderNoExt defaultAssignOrderNoExt; 18 | 19 | public void assignOrderNo(@NotNull IOrderMain model) { 20 | firstExtension(model).assignOrderNo(model); 21 | } 22 | 23 | @Override 24 | public IAssignOrderNoExt defaultExtension(@NotNull IOrderMain model) { 25 | return defaultAssignOrderNoExt; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/ability/CustomModelAbility.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.domain.ability; 2 | 3 | import io.github.dddplus.annotation.DomainAbility; 4 | import io.github.dddplus.ext.IModelAttachmentExt; 5 | import io.github.dddplus.runtime.BaseDomainAbility; 6 | import org.example.cp.oms.domain.CoreDomain; 7 | import org.example.cp.oms.spec.model.IOrderMain; 8 | 9 | import javax.validation.constraints.NotNull; 10 | 11 | @DomainAbility(domain = CoreDomain.CODE) 12 | public class CustomModelAbility extends BaseDomainAbility { 13 | 14 | public void explain(@NotNull IOrderMain model) { 15 | firstExtension(model).explain(model.requestProfile(), model); 16 | } 17 | 18 | @Override 19 | public IModelAttachmentExt defaultExtension(IOrderMain model) { 20 | return null; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/ability/DecideStepsAbility.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.domain.ability; 2 | 3 | import io.github.dddplus.annotation.DomainAbility; 4 | import io.github.dddplus.runtime.BaseDecideStepsAbility; 5 | import org.example.cp.oms.domain.CoreDomain; 6 | import org.example.cp.oms.spec.DomainAbilities; 7 | import org.example.cp.oms.spec.model.IOrderMain; 8 | 9 | @DomainAbility(domain = CoreDomain.CODE, name = "动态决定领域步骤的能力", tags = DomainAbilities.decideSteps) 10 | public class DecideStepsAbility extends BaseDecideStepsAbility { 11 | } 12 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/ability/PostPersistAbility.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.domain.ability; 2 | 3 | import io.github.dddplus.annotation.DomainAbility; 4 | import io.github.dddplus.runtime.BaseDomainAbility; 5 | import org.example.cp.oms.domain.CoreDomain; 6 | import org.example.cp.oms.spec.ext.IPostPersistExt; 7 | import org.example.cp.oms.spec.model.IOrderMain; 8 | 9 | import javax.validation.constraints.NotNull; 10 | 11 | @DomainAbility(domain = CoreDomain.CODE, name = "落库后的扩展能力") 12 | public class PostPersistAbility extends BaseDomainAbility { 13 | 14 | public void afterPersist(@NotNull IOrderMain model) { 15 | firstExtension(model).afterPersist(model); 16 | } 17 | 18 | @Override 19 | public IPostPersistExt defaultExtension(@NotNull IOrderMain model) { 20 | return null; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/ability/PresortAbility.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.domain.ability; 2 | 3 | import io.github.dddplus.annotation.DomainAbility; 4 | import io.github.dddplus.runtime.BaseDomainAbility; 5 | import org.example.cp.oms.domain.CoreDomain; 6 | import org.example.cp.oms.spec.ext.IPresortExt; 7 | import org.example.cp.oms.spec.model.IOrderMain; 8 | 9 | import javax.validation.constraints.NotNull; 10 | 11 | @DomainAbility(domain = CoreDomain.CODE, name = "预分拣的能力") 12 | public class PresortAbility extends BaseDomainAbility { 13 | 14 | public void presort(@NotNull IOrderMain model) { 15 | firstExtension(model).presort(model); 16 | } 17 | 18 | @Override 19 | public IPresortExt defaultExtension(@NotNull IOrderMain model) { 20 | return null; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/ability/ReviseStepsAbility.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.domain.ability; 2 | 3 | import io.github.dddplus.annotation.DomainAbility; 4 | import io.github.dddplus.runtime.BaseDomainAbility; 5 | import org.example.cp.oms.domain.CoreDomain; 6 | import org.example.cp.oms.domain.model.OrderMain; 7 | import org.example.cp.oms.spec.ext.IReviseStepsExt; 8 | 9 | import javax.validation.constraints.NotNull; 10 | import java.util.List; 11 | 12 | @DomainAbility(domain = CoreDomain.CODE) 13 | public class ReviseStepsAbility extends BaseDomainAbility { 14 | 15 | public List revisedSteps(@NotNull OrderMain model) { 16 | // execute ext with timeout 300ms 17 | return firstExtension(model, 300).reviseSteps(model); 18 | } 19 | 20 | @Override 21 | public IReviseStepsExt defaultExtension(@NotNull OrderMain model) { 22 | return null; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/ability/SerializableIsolationAbility.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.domain.ability; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.example.cp.oms.domain.CoreDomain; 5 | import org.example.cp.oms.domain.facade.cache.IRedisClient; 6 | import org.example.cp.oms.domain.facade.lock.IRedisLockFactory; 7 | import org.example.cp.oms.spec.ext.ISerializableIsolationExt; 8 | import org.example.cp.oms.spec.model.IOrderMain; 9 | import org.example.cp.oms.spec.model.vo.LockEntry; 10 | import io.github.dddplus.annotation.DomainAbility; 11 | import io.github.dddplus.runtime.BaseDomainAbility; 12 | 13 | import javax.annotation.Resource; 14 | import javax.validation.constraints.NotNull; 15 | import java.util.concurrent.locks.Lock; 16 | 17 | @DomainAbility(domain = CoreDomain.CODE, name = "订单串行化隔离的能力") 18 | @Slf4j 19 | public class SerializableIsolationAbility extends BaseDomainAbility { 20 | private static final Lock withoutLock = null; 21 | 22 | @Resource 23 | private IRedisLockFactory redisLockFactory; 24 | 25 | @Resource 26 | private IRedisClient redisClient; 27 | 28 | public Lock acquireLock(@NotNull IOrderMain model) { 29 | LockEntry lockEntry = firstExtension(model).createLockEntry(model); 30 | if (lockEntry == null) { 31 | return withoutLock; 32 | } 33 | 34 | // 为了避免前台锁key冲突,中台统一加锁前缀,隔离不同的业务前台 35 | lockEntry.withPrefix(model.getCustomerNo()); 36 | log.info("key:{} expires:{} {}", lockEntry.lockKey(), lockEntry.getLeaseTime(), lockEntry.getTimeUnit()); 37 | return redisLockFactory.create(redisClient, lockEntry); 38 | } 39 | 40 | public static boolean useLock(Lock lock) { 41 | return lock != withoutLock; 42 | } 43 | 44 | @Override 45 | public ISerializableIsolationExt defaultExtension(@NotNull IOrderMain model) { 46 | // 默认不防并发 47 | return null; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/ability/extension/DefaultAssignOrderNoExt.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.domain.ability.extension; 2 | 3 | import io.github.dddplus.annotation.Extension; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.example.cp.oms.domain.specification.ProductNotEmptySpec; 6 | import org.example.cp.oms.spec.ext.IAssignOrderNoExt; 7 | import org.example.cp.oms.spec.model.IOrderMain; 8 | 9 | import javax.annotation.Resource; 10 | import javax.validation.constraints.NotNull; 11 | 12 | @Extension(code = IAssignOrderNoExt.DefaultCode, value = "defaultAssignOrderNoExt") 13 | @Slf4j 14 | public class DefaultAssignOrderNoExt implements IAssignOrderNoExt { 15 | 16 | @Resource 17 | private ProductNotEmptySpec productNotEmptySpec; 18 | 19 | @Override 20 | public void assignOrderNo(@NotNull IOrderMain model) { 21 | // 演示调用业务约束的使用:把implicit business rules变成explicit 22 | if (!productNotEmptySpec.satisfiedBy(model)) { 23 | log.warn("Spec:{} not satisfied", productNotEmptySpec); 24 | //throw new OrderException(OrderErrorReason.SubmitOrder.ProductEmpty); 25 | } 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/facade/cache/IRedisClient.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.domain.facade.cache; 2 | 3 | public interface IRedisClient { 4 | 5 | // 这里省略了,应该是完整的redis指令集: get/set/hset/hget/rPush/setNX/etc 6 | } 7 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/facade/lock/IRedisLockFactory.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.domain.facade.lock; 2 | 3 | import org.example.cp.oms.domain.facade.cache.IRedisClient; 4 | import org.example.cp.oms.spec.model.vo.LockEntry; 5 | 6 | import java.util.concurrent.locks.Lock; 7 | 8 | public interface IRedisLockFactory { 9 | 10 | /** 11 | * Create a mutex lock. 12 | */ 13 | Lock create(IRedisClient redisClient, LockEntry lockEntry); 14 | } 15 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/facade/mq/IMessageProducer.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.domain.facade.mq; 2 | 3 | import org.example.cp.oms.domain.model.OrderMain; 4 | 5 | import javax.validation.constraints.NotNull; 6 | 7 | /** 8 | * MQ的使用方式通过接口形式定义在domain层,Infrastructure来实现. 9 | * 10 | *

一种依赖倒置模式

11 | *

这样,domain层才能主宰世界,成为系统的中心:它要求Infrastructure做到什么

12 | *

至于如何做到,domain层不关心:那是技术的事情,domain只负责业务逻辑

13 | */ 14 | public interface IMessageProducer { 15 | 16 | void produce(@NotNull OrderMain orderModel); 17 | } 18 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/facade/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * DDD Domain层和Infrastructure的粘合剂:通过依赖倒置. 3 | * 4 | *

domain层声明需要基础设施层实现的接口:RPC, DB, Cache, MQ等.

5 | *

为了方便产品人员查看领域层代码,梳理业务,统一放在facade package,减少对产品同学的干扰.

6 | */ 7 | package org.example.cp.oms.domain.facade; -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/facade/repository/IOrderRepository.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.domain.facade.repository; 2 | 3 | import org.example.cp.oms.domain.model.OrderMain; 4 | 5 | import javax.validation.constraints.NotNull; 6 | 7 | public interface IOrderRepository { 8 | 9 | void persist(@NotNull OrderMain orderModel); 10 | 11 | OrderMain getOrder(@NotNull Long orderId); 12 | } 13 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/model/OrderMain.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.domain.model; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import lombok.extern.slf4j.Slf4j; 6 | import io.github.dddplus.api.RequestProfile; 7 | import org.example.cp.oms.domain.model.vo.OrderItemDelegate; 8 | import org.example.cp.oms.spec.exception.OrderException; 9 | import org.example.cp.oms.domain.model.vo.ProductDelegate; 10 | import org.example.cp.oms.spec.model.IOrderMain; 11 | import org.example.cp.oms.spec.model.vo.IOrderItemDelegate; 12 | import org.example.cp.oms.spec.model.vo.IProductDelegate; 13 | 14 | import javax.validation.constraints.NotNull; 15 | 16 | /** 17 | * 订单主档. 18 | * 19 | *

注意,它没有实现Serializable,因为它不会网络传递,也不会本地文件存储.

20 | */ 21 | @Getter // 注意:它没有@Setter,是为了封装,包含订单一致性 22 | @Slf4j 23 | public class OrderMain implements IOrderMain { 24 | private Long id; 25 | 26 | private String source; 27 | private String customerNo; 28 | 29 | private String orderNo; 30 | 31 | private String externalNo; 32 | 33 | private RequestProfile requestProfile; 34 | 35 | @Setter 36 | private String activity; 37 | 38 | @Setter 39 | private String step; 40 | 41 | private ProductDelegate productDelegate; 42 | private OrderItemDelegate orderItemDelegate; 43 | 44 | @Getter 45 | private String x1, x2; 46 | 47 | public static OrderMain createWith(@NotNull OrderModelCreator creator) throws OrderException { 48 | log.debug("creating with {}", creator); 49 | return new OrderMain(creator).validate(); 50 | } 51 | 52 | private OrderMain(OrderModelCreator creator) { 53 | this.id = creator.getId(); 54 | this.source = creator.getSource(); 55 | this.customerNo = creator.getCustomerNo(); 56 | this.externalNo = creator.getExternalNo(); 57 | this.requestProfile = creator.getRequestProfile(); 58 | 59 | this.productDelegate = ProductDelegate.createWith(creator); 60 | this.orderItemDelegate = OrderItemDelegate.createWith(creator); 61 | } 62 | 63 | private OrderMain validate() throws OrderException { 64 | // 模型本身的基础校验 65 | return this; 66 | } 67 | 68 | @Override 69 | public void assignOrderNo(Object who, String orderNo) { 70 | this.orderNo = orderNo; 71 | } 72 | 73 | @Override 74 | public String currentStep() { 75 | return step; 76 | } 77 | 78 | @Override 79 | public String currentActivity() { 80 | return activity; 81 | } 82 | 83 | @Override 84 | public boolean isColdChain() { 85 | return false; 86 | } 87 | 88 | @Override 89 | public boolean isB2B() { 90 | return false; 91 | } 92 | 93 | @Override 94 | public void setX1(String x1) { 95 | this.x1 = x1; 96 | } 97 | 98 | @Override 99 | public void setX2(String x2) { 100 | this.x2 = x2; 101 | } 102 | 103 | @Override 104 | public IProductDelegate productDelegate() { 105 | return productDelegate; 106 | } 107 | 108 | @Override 109 | public IOrderItemDelegate itemDelegate() { 110 | return orderItemDelegate; 111 | } 112 | 113 | @Override 114 | public RequestProfile requestProfile() { 115 | return requestProfile; 116 | } 117 | 118 | @Override 119 | public String customerProvidedOrderNo() { 120 | return externalNo; 121 | } 122 | 123 | public String label() { 124 | return "Order(source=" + source + ", customer=" + customerNo + ")"; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/model/OrderModelCreator.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.domain.model; 2 | 3 | import io.github.dddplus.api.RequestProfile; 4 | import io.github.dddplus.model.IDomainModelCreator; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | import lombok.ToString; 8 | import org.example.cp.oms.domain.model.vo.OrderItem; 9 | import org.example.cp.oms.domain.model.vo.Product; 10 | 11 | import java.util.List; 12 | 13 | @Getter 14 | @Setter 15 | @ToString 16 | public class OrderModelCreator implements IDomainModelCreator { 17 | private Long id; 18 | 19 | private RequestProfile requestProfile; 20 | 21 | /** 22 | * 订单来源 23 | */ 24 | private String source; 25 | 26 | /** 27 | * 客户编号. 28 | */ 29 | private String customerNo; 30 | 31 | /** 32 | * 客户携带的外部单号. 33 | */ 34 | private String externalNo; 35 | 36 | private List orderItems; 37 | 38 | private List products; 39 | } 40 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/model/vo/OrderItem.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.domain.model.vo; 2 | 3 | import lombok.Data; 4 | import org.example.cp.oms.spec.model.vo.IOrderItem; 5 | 6 | import java.math.BigDecimal; 7 | 8 | /** 9 | * 订单项. 10 | * 11 | *

每个{@link org.example.cp.oms.domain.model.OrderMain}包含多个订单项.

12 | */ 13 | @Data 14 | public class OrderItem implements IOrderItem { 15 | private String sku; 16 | private Integer quantity; 17 | private String orderLine; 18 | private BigDecimal price; 19 | } 20 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/model/vo/OrderItemDelegate.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.domain.model.vo; 2 | 3 | import org.example.cp.oms.domain.model.OrderModelCreator; 4 | import org.example.cp.oms.spec.model.vo.IOrderItem; 5 | import org.example.cp.oms.spec.model.vo.IOrderItemDelegate; 6 | import org.example.cp.oms.spec.model.vo.IProduct; 7 | import org.example.cp.oms.spec.model.vo.IProductDelegate; 8 | 9 | import javax.validation.constraints.NotNull; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | public class OrderItemDelegate implements IOrderItemDelegate { 14 | 15 | private List items; 16 | 17 | private OrderItemDelegate() {} 18 | 19 | public static OrderItemDelegate createWith(@NotNull OrderModelCreator creator) { 20 | OrderItemDelegate delegate = new OrderItemDelegate(); 21 | delegate.items = new ArrayList<>(); 22 | return delegate; 23 | } 24 | 25 | @Override 26 | public List getItems() { 27 | return items; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/model/vo/Product.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.domain.model.vo; 2 | 3 | import lombok.Data; 4 | import org.example.cp.oms.spec.model.vo.IProduct; 5 | 6 | /** 7 | * 订单里包含的增值服务产品. 8 | */ 9 | @Data 10 | public class Product implements IProduct { 11 | private String code; 12 | 13 | void setCode(String code) { 14 | this.code = code; 15 | } 16 | 17 | @Override 18 | public String code() { 19 | return code; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/model/vo/ProductDelegate.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.domain.model.vo; 2 | 3 | import org.example.cp.oms.domain.model.OrderModelCreator; 4 | import org.example.cp.oms.spec.model.vo.IProduct; 5 | import org.example.cp.oms.spec.model.vo.IProductDelegate; 6 | 7 | import javax.validation.constraints.NotNull; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | public class ProductDelegate implements IProductDelegate { 12 | 13 | private List products; 14 | 15 | private ProductDelegate() {} 16 | 17 | public static ProductDelegate createWith(@NotNull OrderModelCreator creator) { 18 | ProductDelegate delegate = new ProductDelegate(); 19 | delegate.products = new ArrayList<>(); 20 | return delegate; 21 | } 22 | 23 | @Override 24 | public List getProducts() { 25 | return products; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/service/CancelOrder.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.domain.service; 2 | 3 | import org.example.cp.oms.domain.CoreDomain; 4 | import org.example.cp.oms.domain.ability.DecideStepsAbility; 5 | import org.example.cp.oms.domain.ability.SerializableIsolationAbility; 6 | import org.example.cp.oms.spec.exception.OrderErrorReason; 7 | import org.example.cp.oms.spec.exception.OrderException; 8 | import org.example.cp.oms.domain.model.OrderMain; 9 | import org.example.cp.oms.domain.step.CancelOrderStepsExec; 10 | import io.github.dddplus.annotation.DomainService; 11 | import io.github.dddplus.model.IDomainService; 12 | import io.github.dddplus.runtime.DDD; 13 | import lombok.extern.slf4j.Slf4j; 14 | import org.example.cp.oms.spec.Steps; 15 | 16 | import javax.annotation.Resource; 17 | import javax.validation.constraints.NotNull; 18 | import java.util.List; 19 | import java.util.concurrent.locks.Lock; 20 | 21 | @DomainService(domain = CoreDomain.CODE) 22 | @Slf4j 23 | public class CancelOrder implements IDomainService { 24 | 25 | @Resource 26 | private CancelOrderStepsExec cancelOrderStepsExec; 27 | 28 | public void submit(@NotNull OrderMain orderModel) throws OrderException { 29 | Lock lock = DDD.findAbility(SerializableIsolationAbility.class).acquireLock(orderModel); 30 | if (SerializableIsolationAbility.useLock(lock) && !lock.tryLock()) { 31 | // 存在并发 32 | throw new OrderException(OrderErrorReason.SubmitOrder.OrderConcurrentNotAllowed); 33 | } 34 | 35 | List steps = DDD.findAbility(DecideStepsAbility.class).decideSteps(orderModel, Steps.CancelOrder.Activity); 36 | cancelOrderStepsExec.execute(Steps.CancelOrder.Activity, steps, orderModel); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/service/SubmitOrder.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.domain.service; 2 | 3 | import org.example.cp.oms.domain.ability.PostPersistAbility; 4 | import org.example.cp.oms.domain.ability.SerializableIsolationAbility; 5 | import org.example.cp.oms.domain.step.SubmitOrderStepsExec; 6 | import io.github.dddplus.annotation.DomainService; 7 | import io.github.dddplus.model.IDomainService; 8 | import io.github.dddplus.runtime.DDD; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.example.cp.oms.domain.CoreDomain; 11 | import org.example.cp.oms.domain.ability.DecideStepsAbility; 12 | import org.example.cp.oms.spec.exception.OrderErrorReason; 13 | import org.example.cp.oms.spec.exception.OrderException; 14 | import org.example.cp.oms.domain.model.OrderMain; 15 | import org.example.cp.oms.spec.Steps; 16 | 17 | import javax.annotation.Resource; 18 | import javax.validation.constraints.NotNull; 19 | import java.util.List; 20 | import java.util.concurrent.locks.Lock; 21 | 22 | @DomainService(domain = CoreDomain.CODE) 23 | @Slf4j 24 | public class SubmitOrder implements IDomainService { 25 | 26 | @Resource 27 | private SubmitOrderStepsExec submitOrderStepsExec; 28 | 29 | public void submit(@NotNull OrderMain orderModel) throws OrderException { 30 | // 先通过防并发扩展点防止一个订单多次处理:但防并发逻辑在不同场景下不同 31 | // 同时,也希望研发清楚:扩展点不是绑定到领域步骤的,它可以在任何地方使用! 32 | Lock lock = DDD.findAbility(SerializableIsolationAbility.class).acquireLock(orderModel); 33 | if (!SerializableIsolationAbility.useLock(lock)) { 34 | log.info("will not use lock"); 35 | } else if (!lock.tryLock()) { 36 | // 存在并发 37 | throw new OrderException(OrderErrorReason.SubmitOrder.OrderConcurrentNotAllowed); 38 | } 39 | 40 | // 不同场景下,接单的执行步骤不同:通过扩展点实现业务的多态 41 | List steps = DDD.findAbility(DecideStepsAbility.class).decideSteps(orderModel, Steps.SubmitOrder.Activity); 42 | log.info("steps {}", steps); 43 | 44 | if (steps != null && !steps.isEmpty()) { 45 | // 通过步骤编排的模板方法执行每一个步骤,其中涉及到:步骤回滚,步骤重新编排 46 | submitOrderStepsExec.execute(Steps.SubmitOrder.Activity, steps, orderModel); 47 | } 48 | 49 | DDD.findAbility(PostPersistAbility.class).afterPersist(orderModel); 50 | 51 | log.info("接单完毕!"); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/specification/ProductNotEmptySpec.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.domain.specification; 2 | 3 | import io.github.dddplus.annotation.Specification; 4 | import io.github.dddplus.specification.ISpecification; 5 | import io.github.dddplus.specification.Notification; 6 | import org.example.cp.oms.spec.model.IOrderMain; 7 | 8 | // 之前的implicit business rules,现在变成了explicit rules 9 | @Specification("产品项不能空") 10 | public class ProductNotEmptySpec implements ISpecification { 11 | 12 | @Override 13 | public boolean satisfiedBy(IOrderMain candidate, Notification notification) { 14 | if (candidate.productDelegate() == null || candidate.productDelegate().getProducts() == null || candidate.productDelegate().getProducts().isEmpty()) { 15 | return false; 16 | } 17 | 18 | return true; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/step/CancelOrderStep.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.domain.step; 2 | 3 | import org.example.cp.oms.spec.exception.OrderException; 4 | import org.example.cp.oms.domain.model.OrderMain; 5 | import io.github.dddplus.step.IDomainStep; 6 | import org.example.cp.oms.spec.Steps; 7 | 8 | public abstract class CancelOrderStep implements IDomainStep { 9 | 10 | @Override 11 | public String activityCode() { 12 | return Steps.CancelOrder.Activity; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/step/CancelOrderStepsExec.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.domain.step; 2 | 3 | import io.github.dddplus.runtime.StepsExecTemplate; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.example.cp.oms.domain.model.OrderMain; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | @Slf4j 10 | public class CancelOrderStepsExec extends StepsExecTemplate { 11 | 12 | @Override 13 | protected void beforeStep(CancelOrderStep step, OrderMain model) { 14 | log.info("step:{}.{} before:{}", step.activityCode(), step.stepCode(), model.label()); 15 | } 16 | 17 | @Override 18 | protected void afterStep(CancelOrderStep step, OrderMain model) { 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/step/SubmitOrderStep.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.domain.step; 2 | 3 | import org.example.cp.oms.domain.model.OrderMain; 4 | import io.github.dddplus.step.IRevokableDomainStep; 5 | import org.example.cp.oms.spec.exception.OrderException; 6 | import org.example.cp.oms.spec.Steps; 7 | 8 | import javax.validation.constraints.NotNull; 9 | 10 | public abstract class SubmitOrderStep implements IRevokableDomainStep { 11 | 12 | @Override 13 | public String activityCode() { 14 | return Steps.SubmitOrder.Activity; 15 | } 16 | 17 | @Override 18 | public void rollback(@NotNull OrderMain model, @NotNull OrderException cause) { 19 | // 默认不回滚,子类可以通过覆盖实现对应步骤的回滚逻辑 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/step/SubmitOrderStepsExec.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.domain.step; 2 | 3 | import io.github.dddplus.runtime.StepsExecTemplate; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.example.cp.oms.domain.model.OrderMain; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | @Slf4j 10 | public class SubmitOrderStepsExec extends StepsExecTemplate { 11 | 12 | @Override 13 | protected void beforeStep(SubmitOrderStep step, OrderMain model) { 14 | log.info("step:{}.{} before:{}", step.activityCode(), step.stepCode(), model.label()); 15 | } 16 | 17 | @Override 18 | protected void afterStep(SubmitOrderStep step, OrderMain model) { 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/step/cancelorder/BasicStep.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.domain.step.cancelorder; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.example.cp.oms.spec.exception.OrderException; 5 | import org.example.cp.oms.domain.model.OrderMain; 6 | import org.example.cp.oms.domain.step.CancelOrderStep; 7 | import org.example.cp.oms.spec.Steps; 8 | import io.github.dddplus.annotation.Step; 9 | 10 | import javax.validation.constraints.NotNull; 11 | 12 | @Step(value = "cancelBasicStep") 13 | @Slf4j 14 | public class BasicStep extends CancelOrderStep { 15 | 16 | @Override 17 | public void execute(@NotNull OrderMain model) throws OrderException { 18 | 19 | } 20 | 21 | @Override 22 | public String stepCode() { 23 | return Steps.CancelOrder.BasicStep; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/step/cancelorder/StateStep.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.domain.step.cancelorder; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.example.cp.oms.spec.exception.OrderException; 5 | import org.example.cp.oms.domain.model.OrderMain; 6 | import org.example.cp.oms.domain.step.CancelOrderStep; 7 | import org.example.cp.oms.spec.Steps; 8 | import io.github.dddplus.annotation.Step; 9 | 10 | import javax.validation.constraints.NotNull; 11 | 12 | @Step(value = "cancelStateStep", name = "订单状态校验") 13 | @Slf4j 14 | public class StateStep extends CancelOrderStep { 15 | 16 | @Override 17 | public void execute(@NotNull OrderMain model) throws OrderException { 18 | 19 | } 20 | 21 | @Override 22 | public String stepCode() { 23 | return Steps.CancelOrder.StateStep; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/step/submitorder/BasicStep.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.domain.step.submitorder; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import io.github.dddplus.annotation.Step; 5 | import io.github.dddplus.runtime.DDD; 6 | import io.github.dddplus.step.ReviseStepsException; 7 | import org.example.cp.oms.domain.ability.PresortAbility; 8 | import org.example.cp.oms.domain.ability.ReviseStepsAbility; 9 | import org.example.cp.oms.domain.model.OrderMain; 10 | import org.example.cp.oms.domain.step.SubmitOrderStep; 11 | import org.example.cp.oms.spec.Steps; 12 | import org.example.cp.oms.spec.exception.OrderException; 13 | 14 | import javax.validation.constraints.NotNull; 15 | import java.util.List; 16 | 17 | @Step(value = "submitBasicStep") 18 | @Slf4j 19 | public class BasicStep extends SubmitOrderStep { 20 | 21 | @Override 22 | public void execute(@NotNull OrderMain model) throws OrderException { 23 | model.setStep(this.stepCode()); 24 | 25 | // 动态决定后续步骤:决定后续步骤这个行为,也抽象为扩展点,不同场景进行实现,以实现动态步骤编排的业务多态性 26 | List revisedSteps = DDD.findAbility(ReviseStepsAbility.class).revisedSteps(model); 27 | if (revisedSteps != null) { 28 | log.info("重新编排步骤:{}", revisedSteps); 29 | 30 | // 通过异常,来改变后续步骤 31 | throw new ReviseStepsException().withSubsequentSteps(revisedSteps); 32 | } 33 | 34 | log.info("presorting..."); 35 | DDD.findAbility(PresortAbility.class).presort(model); 36 | } 37 | 38 | @Override 39 | public void rollback(@NotNull OrderMain model, @NotNull OrderException cause) { 40 | log.info("will rollback now..."); 41 | } 42 | 43 | @Override 44 | public String stepCode() { 45 | return Steps.SubmitOrder.BasicStep; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/step/submitorder/BroadcastStep.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.domain.step.submitorder; 2 | 3 | import org.example.cp.oms.spec.exception.OrderException; 4 | import org.example.cp.oms.domain.facade.mq.IMessageProducer; 5 | import org.example.cp.oms.domain.model.OrderMain; 6 | import org.example.cp.oms.domain.step.SubmitOrderStep; 7 | import org.example.cp.oms.spec.Steps; 8 | import io.github.dddplus.annotation.Step; 9 | 10 | import javax.annotation.Resource; 11 | import javax.validation.constraints.NotNull; 12 | 13 | @Step(value = "submitMqStep") 14 | public class BroadcastStep extends SubmitOrderStep { 15 | 16 | @Resource 17 | private IMessageProducer messageProducer; 18 | 19 | @Override 20 | public void execute(@NotNull OrderMain model) throws OrderException { 21 | messageProducer.produce(model); 22 | } 23 | 24 | @Override 25 | public String stepCode() { 26 | return Steps.SubmitOrder.BroadcastStep; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/step/submitorder/PersistStep.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.domain.step.submitorder; 2 | 3 | import org.example.cp.oms.domain.ability.AssignOrderNoAbility; 4 | import org.example.cp.oms.domain.ability.CustomModelAbility; 5 | import org.example.cp.oms.spec.exception.OrderException; 6 | import org.example.cp.oms.domain.facade.repository.IOrderRepository; 7 | import org.example.cp.oms.domain.model.OrderMain; 8 | import org.example.cp.oms.domain.step.SubmitOrderStep; 9 | import org.example.cp.oms.spec.Steps; 10 | import io.github.dddplus.annotation.Step; 11 | import io.github.dddplus.runtime.DDD; 12 | 13 | import javax.annotation.Resource; 14 | import javax.validation.constraints.NotNull; 15 | 16 | @Step(value = "submitPersistStep") 17 | public class PersistStep extends SubmitOrderStep { 18 | 19 | @Resource 20 | private IOrderRepository orderRepository; 21 | 22 | @Override 23 | public void execute(@NotNull OrderMain model) throws OrderException { 24 | // 分配订单号:不同场景下,订单号规则不同 25 | DDD.findAbility(AssignOrderNoAbility.class).assignOrderNo(model); 26 | 27 | // 处理个性化字段 28 | DDD.findAbility(CustomModelAbility.class).explain(model); 29 | 30 | // 落库 31 | orderRepository.persist(model); 32 | } 33 | 34 | @Override 35 | public String stepCode() { 36 | return Steps.SubmitOrder.PersistStep; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/step/submitorder/PresortStep.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.domain.step.submitorder; 2 | 3 | import io.github.dddplus.annotation.Step; 4 | import org.example.cp.oms.domain.model.OrderMain; 5 | import org.example.cp.oms.domain.step.SubmitOrderStep; 6 | import org.example.cp.oms.spec.Steps; 7 | import org.example.cp.oms.spec.exception.OrderException; 8 | 9 | import javax.validation.constraints.NotNull; 10 | 11 | @Step(value = "submitPresortStep", name = "预分拣步骤") 12 | public class PresortStep extends SubmitOrderStep { 13 | 14 | @Override 15 | public void execute(@NotNull OrderMain model) throws OrderException { 16 | // TODO 把预分拣扩展点从BasicStep移动到这里 17 | } 18 | 19 | @Override 20 | public String stepCode() { 21 | return Steps.SubmitOrder.PresortStep; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/step/submitorder/ProductStep.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.domain.step.submitorder; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.example.cp.oms.spec.exception.OrderException; 5 | import org.example.cp.oms.domain.model.OrderMain; 6 | import org.example.cp.oms.domain.step.SubmitOrderStep; 7 | import org.example.cp.oms.spec.Steps; 8 | import io.github.dddplus.annotation.Step; 9 | 10 | import javax.validation.constraints.NotNull; 11 | 12 | @Step(value = "submitProductStep", name = "订单里产品校验", tags = Steps.Tags.Product) 13 | @Slf4j 14 | public class ProductStep extends SubmitOrderStep { 15 | 16 | @Override 17 | public void execute(@NotNull OrderMain model) throws OrderException { 18 | } 19 | 20 | @Override 21 | public String stepCode() { 22 | return Steps.SubmitOrder.ProductStep; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-domain/src/main/java/org/example/cp/oms/domain/step/submitorder/StockStep.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.domain.step.submitorder; 2 | 3 | import io.github.dddplus.annotation.Step; 4 | import org.example.cp.oms.domain.model.OrderMain; 5 | import org.example.cp.oms.domain.step.SubmitOrderStep; 6 | import org.example.cp.oms.spec.Steps; 7 | import org.example.cp.oms.spec.exception.OrderException; 8 | import org.example.oms.d.stock.spec.service.IStockService; 9 | 10 | import javax.annotation.Resource; 11 | import javax.validation.constraints.NotNull; 12 | 13 | @Step(value = "stockStep") 14 | public class StockStep extends SubmitOrderStep { 15 | 16 | // 演示:通过库存支撑域来为订单核心域提供方便的服务 17 | @Resource 18 | private IStockService stockService; 19 | 20 | @Override 21 | public void execute(@NotNull OrderMain model) throws OrderException { 22 | stockService.occupyStock(model); 23 | } 24 | 25 | @Override 26 | public String stepCode() { 27 | return Steps.SubmitOrder.StockStep; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-infrastructure/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.example 6 | order-center-cp 7 | 0.0.1 8 | 9 | 10 | cp-oc-infrastructure 11 | DDDplus :: Demo :: CP :: Infrastructure 12 | 13 | 14 | 1.3.1.Final 15 | 16 | 17 | 18 | 19 | org.example 20 | cp-oc-domain 21 | ${project.version} 22 | 23 | 24 | 25 | org.springframework 26 | spring-tx 27 | ${spring.version} 28 | 29 | 30 | org.mapstruct 31 | mapstruct 32 | ${mapstruct.version} 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-infrastructure/src/main/java/org/example/cp/oms/infra/cache/RedisClient.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.infra.cache; 2 | 3 | import org.example.cp.oms.domain.facade.cache.IRedisClient; 4 | import org.springframework.stereotype.Component; 5 | 6 | @Component 7 | public class RedisClient implements IRedisClient { 8 | } 9 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-infrastructure/src/main/java/org/example/cp/oms/infra/dao/OrderItemDao.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.infra.dao; 2 | 3 | import org.example.cp.oms.infra.po.OrderItemData; 4 | 5 | import java.util.List; 6 | 7 | public interface OrderItemDao { 8 | 9 | List itemsOfOrder(Long orderId); 10 | } 11 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-infrastructure/src/main/java/org/example/cp/oms/infra/dao/OrderMainDao.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.infra.dao; 2 | 3 | import org.example.cp.oms.infra.po.OrderMainData; 4 | 5 | public interface OrderMainDao { 6 | 7 | void insert(OrderMainData orderMainData); 8 | 9 | OrderMainData getById(Long id); 10 | } 11 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-infrastructure/src/main/java/org/example/cp/oms/infra/dao/mock/MockOrderItemDao.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.infra.dao.mock; 2 | 3 | import org.example.cp.oms.infra.dao.OrderItemDao; 4 | import org.example.cp.oms.infra.po.OrderItemData; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.util.List; 8 | 9 | // 实际项目,可以使用MyBatis/Hibernate/JPA等 10 | @Component 11 | public class MockOrderItemDao implements OrderItemDao { 12 | 13 | @Override 14 | public List itemsOfOrder(Long orderId) { 15 | return null; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-infrastructure/src/main/java/org/example/cp/oms/infra/dao/mock/MockOrderMainDao.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.infra.dao.mock; 2 | 3 | import org.example.cp.oms.infra.dao.OrderMainDao; 4 | import org.example.cp.oms.infra.po.OrderMainData; 5 | import org.springframework.stereotype.Component; 6 | 7 | // 实际项目,可以使用MyBatis/Hibernate/JPA等 8 | @Component 9 | public class MockOrderMainDao implements OrderMainDao { 10 | @Override 11 | public void insert(OrderMainData orderMainData) { 12 | 13 | } 14 | 15 | @Override 16 | public OrderMainData getById(Long id) { 17 | return null; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-infrastructure/src/main/java/org/example/cp/oms/infra/lock/RedisLock.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.infra.lock; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.example.cp.oms.domain.facade.cache.IRedisClient; 5 | import org.example.cp.oms.spec.model.vo.LockEntry; 6 | 7 | import java.util.concurrent.TimeUnit; 8 | import java.util.concurrent.locks.Condition; 9 | import java.util.concurrent.locks.Lock; 10 | 11 | @Slf4j 12 | class RedisLock implements Lock { 13 | 14 | private final IRedisClient redisClient; 15 | private final LockEntry lockEntry; 16 | 17 | RedisLock(IRedisClient redisClient, LockEntry lockEntry) { 18 | this.redisClient = redisClient; 19 | this.lockEntry = lockEntry; 20 | } 21 | 22 | @Override 23 | public void lock() { 24 | 25 | } 26 | 27 | @Override 28 | public void lockInterruptibly() throws InterruptedException { 29 | 30 | } 31 | 32 | @Override 33 | public boolean tryLock() { 34 | log.info("这里通过 IRedisClient 实现分布式锁, {}", lockEntry); 35 | return tryLock(lockEntry.getLeaseTime(), lockEntry.getTimeUnit()); 36 | } 37 | 38 | @Override 39 | public boolean tryLock(long time, TimeUnit unit) { 40 | return true; 41 | } 42 | 43 | @Override 44 | public void unlock() { 45 | 46 | } 47 | 48 | @Override 49 | public Condition newCondition() { 50 | return null; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-infrastructure/src/main/java/org/example/cp/oms/infra/lock/RedisLockFactory.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.infra.lock; 2 | 3 | import org.example.cp.oms.domain.facade.cache.IRedisClient; 4 | import org.example.cp.oms.domain.facade.lock.IRedisLockFactory; 5 | import org.example.cp.oms.spec.model.vo.LockEntry; 6 | import org.springframework.stereotype.Component; 7 | 8 | import java.util.concurrent.locks.Lock; 9 | 10 | @Component 11 | public class RedisLockFactory implements IRedisLockFactory { 12 | 13 | @Override 14 | public Lock create(IRedisClient redisClient, LockEntry lockEntry) { 15 | return new RedisLock(redisClient, lockEntry); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-infrastructure/src/main/java/org/example/cp/oms/infra/manager/IOrderManager.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.infra.manager; 2 | 3 | import org.example.cp.oms.infra.po.OrderMainData; 4 | 5 | public interface IOrderManager { 6 | 7 | void insert(OrderMainData orderMainData); 8 | } 9 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-infrastructure/src/main/java/org/example/cp/oms/infra/manager/impl/OrderManager.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.infra.manager.impl; 2 | 3 | import org.example.cp.oms.infra.dao.OrderMainDao; 4 | import org.example.cp.oms.infra.manager.IOrderManager; 5 | import org.example.cp.oms.infra.po.OrderMainData; 6 | import org.springframework.stereotype.Component; 7 | import org.springframework.transaction.annotation.Transactional; 8 | 9 | import javax.annotation.Resource; 10 | 11 | @Component 12 | public class OrderManager implements IOrderManager { 13 | 14 | @Resource 15 | private OrderMainDao orderMainDao; 16 | 17 | @Override 18 | @Transactional(rollbackFor = Exception.class) 19 | public void insert(OrderMainData orderMainData) { 20 | orderMainDao.insert(orderMainData); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-infrastructure/src/main/java/org/example/cp/oms/infra/mq/MessageProducer.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.infra.mq; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.example.cp.oms.domain.facade.mq.IMessageProducer; 5 | import org.example.cp.oms.domain.model.OrderMain; 6 | import org.springframework.stereotype.Component; 7 | 8 | import javax.validation.constraints.NotNull; 9 | 10 | @Component 11 | @Slf4j 12 | public class MessageProducer implements IMessageProducer { 13 | 14 | @Override 15 | public void produce(@NotNull OrderMain orderModel) { 16 | log.info("已经发送给MQ:{}", orderModel); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-infrastructure/src/main/java/org/example/cp/oms/infra/po/OrderItemData.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.infra.po; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class OrderItemData { 7 | private Long orderId; 8 | } 9 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-infrastructure/src/main/java/org/example/cp/oms/infra/po/OrderMainData.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.infra.po; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class OrderMainData { 7 | private Long orderId; 8 | 9 | // 预留字段,中台负责存储,前台负责业务 10 | private String x1; 11 | private String x2; 12 | private String x3; 13 | private String x4; 14 | private String x5; 15 | } 16 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-infrastructure/src/main/java/org/example/cp/oms/infra/repository/OrderRepository.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.infra.repository; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.example.cp.oms.domain.facade.repository.IOrderRepository; 5 | import org.example.cp.oms.domain.model.OrderMain; 6 | import org.example.cp.oms.domain.model.OrderModelCreator; 7 | import org.example.cp.oms.infra.dao.OrderItemDao; 8 | import org.example.cp.oms.infra.dao.OrderMainDao; 9 | import org.example.cp.oms.infra.manager.IOrderManager; 10 | import org.example.cp.oms.infra.po.OrderItemData; 11 | import org.example.cp.oms.infra.po.OrderMainData; 12 | import org.example.cp.oms.infra.translator.Data2Model; 13 | import org.example.cp.oms.infra.translator.Model2Data; 14 | import org.springframework.stereotype.Repository; 15 | import org.springframework.transaction.annotation.Transactional; 16 | 17 | import javax.annotation.Resource; 18 | import javax.validation.constraints.NotNull; 19 | import java.util.List; 20 | 21 | @Repository 22 | @Slf4j 23 | public class OrderRepository implements IOrderRepository { 24 | 25 | @Resource 26 | private IOrderManager orderManager; 27 | 28 | @Resource 29 | private OrderItemDao orderItemDao; 30 | 31 | @Resource 32 | private OrderMainDao orderMainDao; 33 | 34 | @Override 35 | public void persist(@NotNull OrderMain orderModel) { 36 | log.info("落库:{}", orderModel); 37 | 38 | if (true) { 39 | return; 40 | } 41 | 42 | OrderMainData orderMainData = Model2Data.instance.translate(orderModel); 43 | orderManager.insert(orderMainData); 44 | } 45 | 46 | @Override 47 | @Transactional(readOnly = true) 48 | public OrderMain getOrder(@NotNull Long orderId) { 49 | // 数据库里拿出主档、明细档数据 50 | OrderMainData orderMainData = orderMainDao.getById(orderId); 51 | List orderItemDataList = orderItemDao.itemsOfOrder(orderId); 52 | 53 | // 通过MapStruct转换为creator这个契约对象,再创建领域模型 54 | OrderModelCreator creator = Data2Model.instance.translate(orderMainData, orderItemDataList); 55 | return OrderMain.createWith(creator); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-infrastructure/src/main/java/org/example/cp/oms/infra/resource/StockRpc.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.infra.resource; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.example.cp.oms.spec.resource.IStockRpc; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | @Slf4j 9 | public class StockRpc implements IStockRpc { 10 | @Override 11 | public boolean preOccupyStock(String sku) { 12 | // 真实场景,会通过RPC/RESTful接口调用“库存中心”的服务接口 13 | // 这里仅仅是mock 14 | log.info("预占库存:{}", sku); 15 | return true; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-infrastructure/src/main/java/org/example/cp/oms/infra/translator/Data2Model.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.infra.translator; 2 | 3 | import org.example.cp.oms.domain.model.OrderModelCreator; 4 | import org.example.cp.oms.infra.po.OrderItemData; 5 | import org.example.cp.oms.infra.po.OrderMainData; 6 | import org.mapstruct.Context; 7 | import org.mapstruct.Mapper; 8 | import org.mapstruct.factory.Mappers; 9 | 10 | import java.util.List; 11 | 12 | @Mapper 13 | public interface Data2Model { 14 | 15 | Data2Model instance = Mappers.getMapper(Data2Model.class); 16 | 17 | OrderModelCreator translate(OrderMainData orderMainData, @Context List orderItemData); 18 | } 19 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-infrastructure/src/main/java/org/example/cp/oms/infra/translator/Model2Data.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.infra.translator; 2 | 3 | import org.example.cp.oms.infra.po.OrderMainData; 4 | import io.github.dddplus.IBaseTranslator; 5 | import org.example.cp.oms.domain.model.OrderMain; 6 | import org.mapstruct.Mapper; 7 | import org.mapstruct.ReportingPolicy; 8 | import org.mapstruct.factory.Mappers; 9 | 10 | @Mapper( 11 | unmappedSourcePolicy = ReportingPolicy.WARN, 12 | unmappedTargetPolicy = ReportingPolicy.WARN, 13 | typeConversionPolicy = ReportingPolicy.ERROR 14 | ) 15 | public interface Model2Data extends IBaseTranslator { 16 | 17 | Model2Data instance = Mappers.getMapper(Model2Data.class); 18 | 19 | @Override 20 | OrderMainData translate(OrderMain orderModel); 21 | } 22 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-infrastructure/src/main/resources/mapper/OrderMainMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-main/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.example 7 | order-center-cp 8 | 0.0.1 9 | 10 | 11 | cp-oc-main 12 | DDDplus :: Demo :: CP :: Main 13 | 14 | 15 | 16 | org.example 17 | cp-oc-controller 18 | ${project.version} 19 | 20 | 21 | 22 | 23 | org.example 24 | order-center-pattern 25 | ${project.version} 26 | 27 | 28 | 29 | 30 | com.google.guava 31 | guava 32 | ${guava.version} 33 | 34 | 35 | 36 | 37 | org.example 38 | order-center-stock-infrastructure 39 | ${project.version} 40 | 41 | 42 | 43 | 44 | log4j-core 45 | org.apache.logging.log4j 46 | 2.16.0 47 | 48 | 49 | org.apache.logging.log4j 50 | log4j-slf4j-impl 51 | 2.13.2 52 | 53 | 54 | org.apache.logging.log4j 55 | log4j-jcl 56 | 2.13.2 57 | 58 | 59 | 60 | 61 | com.fasterxml.jackson.core 62 | jackson-databind 63 | ${jackson.version} 64 | 65 | 66 | com.fasterxml.jackson.core 67 | jackson-annotations 68 | ${jackson.version} 69 | 70 | 71 | com.fasterxml.jackson.core 72 | jackson-core 73 | ${jackson.version} 74 | 75 | 76 | 77 | 78 | org.eclipse.jetty 79 | jetty-servlet 80 | ${jetty.version} 81 | 82 | 83 | org.eclipse.jetty 84 | jetty-server 85 | ${jetty.version} 86 | 87 | 88 | org.eclipse.jetty 89 | jetty-webapp 90 | ${jetty.version} 91 | 92 | 93 | 94 | 95 | dddplus-demo 96 | 97 | 98 | org.apache.maven.plugins 99 | maven-shade-plugin 100 | 2.1 101 | 102 | 103 | package 104 | 105 | shade 106 | 107 | 108 | false 109 | 110 | 111 | org.example.cp.oms.OrderServer 112 | 113 | 114 | META-INF/spring.handlers 115 | 116 | 117 | META-INF/spring.schemas 118 | 119 | 120 | META-INF/spring.tooling 121 | 122 | 123 | 124 | 125 | 126 | 127 | *:* 128 | 129 | META-INF/*.SF 130 | META-INF/*.DSA 131 | META-INF/*.RSA 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | demo 145 | 146 | true 147 | 148 | 149 | 150 | 151 | org.example 152 | order-center-bp-isv 153 | ${project.version} 154 | 155 | 156 | org.example 157 | order-center-bp-ka 158 | ${project.version} 159 | 160 | 161 | 162 | 163 | plugin 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-main/src/main/java/org/example/cp/oms/OrderServer.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms; 2 | 3 | import io.github.dddplus.runtime.registry.Container; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.eclipse.jetty.server.Server; 6 | import org.eclipse.jetty.servlet.ServletContextHandler; 7 | import org.eclipse.jetty.servlet.ServletHolder; 8 | import org.springframework.web.context.ContextLoaderListener; 9 | import org.springframework.web.context.WebApplicationContext; 10 | import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; 11 | import org.springframework.web.servlet.DispatcherServlet; 12 | 13 | import java.io.IOException; 14 | 15 | /** 16 | * 完整演示的入口:在IDE下启动:会在本机启动Jetty server. 17 | * 18 | * HTTP Controller入口在cp-oc-controller module 的 OrderController 19 | * 20 | * 下单: 21 | * curl -XPOST localhost:9090/order 22 | * 热加载: 23 | * curl localhost:9090/reload 24 | * 25 | * 查看日志,了解服务端的执行过程. 26 | */ 27 | @Slf4j 28 | public class OrderServer { 29 | private static final int DEFAULT_PORT = 9090; 30 | private static final String CONTEXT_PATH = "/"; 31 | private static final String MAPPING_URL = "/*"; 32 | 33 | private static final String CONFIG_LOCATION = "org.example.cp.oms.config"; 34 | private static final String PLUGIN_LOCATION = "org.example.cp.oms.plugin"; 35 | 36 | public static void main(String[] args) throws Exception { 37 | int port = DEFAULT_PORT; 38 | String config = CONFIG_LOCATION; 39 | if (args.length > 0) { 40 | try { 41 | port = Integer.valueOf(args[0]); 42 | } catch (NumberFormatException ignored) { 43 | log.error("Invalid arg", ignored); 44 | } 45 | 46 | if (args.length > 1) { 47 | // Plugin dynamic loading 48 | config = PLUGIN_LOCATION; 49 | log.info("Using config:{}", config); 50 | } 51 | } 52 | 53 | new OrderServer().startJetty(port, config); 54 | } 55 | 56 | private void startJetty(int port, String config) throws Exception { 57 | log.info("Starting server at port {}", port); 58 | Server server = new Server(port); 59 | server.setHandler(getServletContextHandler(getContext(config))); 60 | server.start(); 61 | log.info("Server started at port {}", port); 62 | log.info("active plugins: {}", Container.getInstance().getActivePlugins()); 63 | log.info("模拟下订单,执行命令:curl -XPOST localhost:9090/order?type=isv"); 64 | log.info("模拟热加载,执行命令:curl localhost:9090/reload?plugin=isv"); 65 | log.info("Ready to accept requests!"); 66 | server.join(); 67 | } 68 | 69 | private static ServletContextHandler getServletContextHandler(WebApplicationContext context) throws IOException { 70 | ServletContextHandler contextHandler = new ServletContextHandler(); 71 | contextHandler.setErrorHandler(null); 72 | contextHandler.setContextPath(CONTEXT_PATH); 73 | contextHandler.addServlet(new ServletHolder(new DispatcherServlet(context)), MAPPING_URL); 74 | contextHandler.addEventListener(new ContextLoaderListener(context)); 75 | return contextHandler; 76 | } 77 | 78 | private static WebApplicationContext getContext(String config) { 79 | AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); 80 | context.setConfigLocation(config); 81 | return context; 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-main/src/main/java/org/example/cp/oms/config/AppConfig.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.config; 2 | 3 | import org.springframework.context.annotation.ComponentScan; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | @Configuration 7 | @ComponentScan(basePackages = {"io.github.dddplus", "org.example.cp", "org.example.bp", "org.example.oms"}) 8 | public class AppConfig { 9 | } 10 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-main/src/main/java/org/example/cp/oms/plugin/PluginConfig.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.plugin; 2 | 3 | import org.springframework.context.annotation.ComponentScan; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | @Configuration 7 | @ComponentScan(basePackages = {"io.github.dddplus", "org.example.cp", "org.example.oms"}) 8 | public class PluginConfig { 9 | } 10 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-main/src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %X{tid} %d %-5p [%c] [%L] %m%n 5 | info 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | ${patternLayout} 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-spec/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.example 6 | order-center-cp 7 | 0.0.1 8 | 9 | 10 | cp-oc-spec 11 | DDDplus :: Demo :: CP :: Spec 12 | 中台为前台的赋能和扩展机制输出 13 | 14 | 15 | 16 | 17 | io.github.dddplus 18 | dddplus-runtime 19 | ${dddplus.version} 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-spec/src/main/java/org/example/cp/oms/spec/DomainAbilities.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.spec; 2 | 3 | /** 4 | * 所有的领域扩展能力,统一定义在此. 5 | */ 6 | public interface DomainAbilities { 7 | 8 | String decideSteps = "步骤编排"; 9 | } 10 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-spec/src/main/java/org/example/cp/oms/spec/Patterns.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.spec; 2 | 3 | /** 4 | * 所有的业务模式,统一定义在此. 5 | */ 6 | public interface Patterns { 7 | 8 | /** 9 | * 海尔模式. 10 | */ 11 | String Hair = "hair"; 12 | 13 | /** 14 | * 家电模式. 15 | */ 16 | String HomeAppliance = "home"; 17 | 18 | /** 19 | * 冷链B2B模式. 20 | */ 21 | String ColdChainB2B = "ccb2b"; 22 | 23 | /** 24 | * 冷链B2C模式. 25 | */ 26 | String ColdChainB2C = "ccb2c"; 27 | 28 | /** 29 | * 业务模式标签在此统一定义. 30 | */ 31 | interface Tags { 32 | String B2B = "B2B"; 33 | String B2C = "B2C"; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-spec/src/main/java/org/example/cp/oms/spec/Steps.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.spec; 2 | 3 | /** 4 | * 订单中台统一定义所有的活动和步骤. 5 | */ 6 | public interface Steps { 7 | 8 | interface SubmitOrder { 9 | String Activity = "submitOrder"; 10 | 11 | String BasicStep = "basic"; 12 | String ProductStep = "product"; 13 | String PresortStep = "presort"; 14 | String PersistStep = "persist"; 15 | String StockStep = "stock"; 16 | String BroadcastStep = "mq"; 17 | } 18 | 19 | interface CancelOrder { 20 | String Activity = "cancelOrder"; 21 | 22 | String BasicStep = "basic"; 23 | String StateStep = "state"; 24 | String PersistStep = "persist"; 25 | } 26 | 27 | /** 28 | * 所有步骤标签,统一在此定义. 29 | */ 30 | interface Tags { 31 | String Product = "产品相关"; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-spec/src/main/java/org/example/cp/oms/spec/exception/OrderErrorReason.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.spec.exception; 2 | 3 | public interface OrderErrorReason { 4 | 5 | enum SubmitOrder implements OrderErrorSpec { 6 | OrderConcurrentNotAllowed("101", "同一个订单不允许并发"), 7 | InvalidExtenalNo("102", "非法的外部单号"), 8 | ProductEmpty("103", "产品为空"), 9 | ; 10 | 11 | private final String code; 12 | private final String message; 13 | 14 | SubmitOrder(String code, String message) { 15 | this.code = code; 16 | this.message = message; 17 | } 18 | 19 | @Override 20 | public String code() { 21 | return code; 22 | } 23 | } 24 | 25 | enum CancelOrder implements OrderErrorSpec { 26 | OrderConcurrentNotAllowed("201", "同一个订单不允许并发"), 27 | ; 28 | 29 | private final String code; 30 | private final String message; 31 | 32 | CancelOrder(String code, String message) { 33 | this.code = code; 34 | this.message = message; 35 | } 36 | 37 | @Override 38 | public String code() { 39 | return code; 40 | } 41 | } 42 | 43 | enum Custom implements OrderErrorSpec { 44 | Custom("999", "前台自定义"), 45 | ; 46 | 47 | private final String code; 48 | private final String message; 49 | 50 | Custom(String code, String message) { 51 | this.code = code; 52 | this.message = message; 53 | } 54 | 55 | @Override 56 | public String code() { 57 | return code; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-spec/src/main/java/org/example/cp/oms/spec/exception/OrderErrorSpec.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.spec.exception; 2 | 3 | public interface OrderErrorSpec { 4 | String code(); 5 | } 6 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-spec/src/main/java/org/example/cp/oms/spec/exception/OrderException.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.spec.exception; 2 | 3 | import javax.validation.constraints.NotNull; 4 | 5 | /** 6 | * 前中台统一的错误码机制. 7 | * 8 | *

错误码,在domain层是以异常形式抛出的,因为异常有穿透能力,方便研发使用

9 | *

在application层,统一转换为Response的errorCode

10 | */ 11 | public class OrderException extends RuntimeException { 12 | /** 13 | * 中台定义的统一错误码. 14 | */ 15 | protected OrderErrorSpec errorReason; 16 | 17 | /** 18 | * (业务前台)个性化消息. 19 | *

20 | *

例如,业务前台要求它抛出的错误消息,中台不要再加工,要原封不动地输出

21 | */ 22 | protected String custom; 23 | 24 | public OrderException(@NotNull OrderErrorSpec errorReason) { 25 | super(); 26 | this.errorReason = errorReason; 27 | } 28 | 29 | /** 30 | * 设置(业务前台)个性化消息. 31 | * 32 | * @param custom 个性化消息 33 | */ 34 | public OrderException withCustom(String custom) { 35 | this.custom = custom; 36 | return this; 37 | } 38 | 39 | public String getCustom() { 40 | return custom; 41 | } 42 | 43 | /** 44 | * 是否有个性化信息. 45 | */ 46 | public boolean hasCustom() { 47 | return custom != null; 48 | } 49 | 50 | public String code() { 51 | return errorReason.code(); 52 | } 53 | 54 | @Override 55 | public String getMessage() { 56 | if (hasCustom()) { 57 | return custom; 58 | } 59 | 60 | return code(); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-spec/src/main/java/org/example/cp/oms/spec/ext/IAssignOrderNoExt.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.spec.ext; 2 | 3 | import org.example.cp.oms.spec.model.IOrderMain; 4 | import io.github.dddplus.ext.IDomainExtension; 5 | 6 | import javax.validation.constraints.NotNull; 7 | 8 | /** 9 | * 生成、分配订单号扩展点. 10 | */ 11 | public interface IAssignOrderNoExt extends IDomainExtension { 12 | 13 | void assignOrderNo(@NotNull IOrderMain model); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-spec/src/main/java/org/example/cp/oms/spec/ext/IPostPersistExt.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.spec.ext; 2 | 3 | import io.github.dddplus.ext.IDomainExtension; 4 | import org.example.cp.oms.spec.model.IOrderMain; 5 | 6 | import javax.validation.constraints.NotNull; 7 | 8 | /** 9 | * 落库后的处理扩展点. 10 | */ 11 | public interface IPostPersistExt extends IDomainExtension { 12 | 13 | void afterPersist(@NotNull IOrderMain model); 14 | } 15 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-spec/src/main/java/org/example/cp/oms/spec/ext/IPresortExt.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.spec.ext; 2 | 3 | import io.github.dddplus.ext.IDomainExtension; 4 | import org.example.cp.oms.spec.model.IOrderMain; 5 | 6 | import javax.validation.constraints.NotNull; 7 | 8 | /** 9 | * 预分拣扩展点. 10 | *

11 | *

根据配送地址获取配送站点信息,进而获取波次配置

12 | */ 13 | public interface IPresortExt extends IDomainExtension { 14 | 15 | void presort(@NotNull IOrderMain model); 16 | } 17 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-spec/src/main/java/org/example/cp/oms/spec/ext/IReviseStepsExt.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.spec.ext; 2 | 3 | import org.example.cp.oms.spec.model.IOrderMain; 4 | import io.github.dddplus.ext.IDomainExtension; 5 | 6 | import javax.validation.constraints.NotNull; 7 | import java.util.List; 8 | 9 | public interface IReviseStepsExt extends IDomainExtension { 10 | 11 | List reviseSteps(@NotNull IOrderMain model); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-spec/src/main/java/org/example/cp/oms/spec/ext/ISensitiveWordsExt.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.spec.ext; 2 | 3 | import io.github.dddplus.ext.IDomainExtension; 4 | import org.example.cp.oms.spec.model.IOrderMain; 5 | 6 | import javax.validation.constraints.NotNull; 7 | 8 | /** 9 | * 敏感词信息获取. 10 | */ 11 | public interface ISensitiveWordsExt extends IDomainExtension { 12 | 13 | String[] extract(@NotNull IOrderMain model); 14 | } 15 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-spec/src/main/java/org/example/cp/oms/spec/ext/ISerializableIsolationExt.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.spec.ext; 2 | 3 | import org.example.cp.oms.spec.model.IOrderMain; 4 | import org.example.cp.oms.spec.model.vo.LockEntry; 5 | import io.github.dddplus.ext.IDomainExtension; 6 | 7 | import javax.validation.constraints.NotNull; 8 | 9 | /** 10 | * 订单串行化隔离的扩展点声明,即防并发锁. 11 | */ 12 | public interface ISerializableIsolationExt extends IDomainExtension { 13 | 14 | /** 15 | * 获取防并发锁的信息. 16 | * 17 | * @param model 18 | * @return lock entry object. if null, 不需要防并发 19 | */ 20 | LockEntry createLockEntry(@NotNull IOrderMain model); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-spec/src/main/java/org/example/cp/oms/spec/model/IOrderMain.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.spec.model; 2 | 3 | import io.github.dddplus.api.RequestProfile; 4 | import org.example.cp.oms.spec.model.vo.IOrderItemDelegate; 5 | import org.example.cp.oms.spec.model.vo.IProductDelegate; 6 | import io.github.dddplus.model.IDomainModel; 7 | import lombok.NonNull; 8 | 9 | /** 10 | * 中台为业务前台输出的订单聚合根:以接口的方式控制visibility. 11 | * 12 | *

订单模型可能有N个方法,但只想输出给前台N-M个方法,通过这个机制就能容易地看到收益

13 | */ 14 | public interface IOrderMain extends IDomainModel { 15 | 16 | /** 17 | * 获取订单里包含的产品信息. 18 | */ 19 | IProductDelegate productDelegate(); 20 | 21 | IOrderItemDelegate itemDelegate(); 22 | 23 | /** 24 | * 获取当前的请求参数. 25 | */ 26 | RequestProfile requestProfile(); 27 | 28 | /** 29 | * 获取订单来源. 30 | */ 31 | String getSource(); 32 | 33 | /** 34 | * 获取客户编号. 35 | */ 36 | String getCustomerNo(); 37 | 38 | /** 39 | * 客户提供的订单号. 40 | */ 41 | String customerProvidedOrderNo(); 42 | 43 | /** 44 | * 分配订单号. 45 | * 46 | * @param who 47 | * @param orderNo 48 | */ 49 | void assignOrderNo(@NonNull Object who, String orderNo); 50 | 51 | String currentStep(); 52 | 53 | String currentActivity(); 54 | 55 | /** 56 | * 是否冷链业务. 57 | */ 58 | boolean isColdChain(); 59 | 60 | boolean isB2B(); 61 | 62 | /** 63 | * 为预留字段x1赋值. 64 | */ 65 | void setX1(String x1); 66 | 67 | /** 68 | * 为预留字段x2赋值. 69 | */ 70 | void setX2(String x2); 71 | } 72 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-spec/src/main/java/org/example/cp/oms/spec/model/vo/IOrderItem.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.spec.model.vo; 2 | 3 | public interface IOrderItem { 4 | 5 | String getSku(); 6 | Integer getQuantity(); 7 | 8 | } 9 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-spec/src/main/java/org/example/cp/oms/spec/model/vo/IOrderItemDelegate.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.spec.model.vo; 2 | 3 | import java.util.List; 4 | 5 | public interface IOrderItemDelegate { 6 | List getItems(); 7 | } 8 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-spec/src/main/java/org/example/cp/oms/spec/model/vo/IProduct.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.spec.model.vo; 2 | 3 | /** 4 | * 订单包含的服务产品信息. 5 | * 6 | *

例如:仓内加工,货到付款,保价等,都是额外的服务产品.

7 | */ 8 | public interface IProduct { 9 | 10 | /** 11 | * 产品标识码. 12 | */ 13 | String code(); 14 | } 15 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-spec/src/main/java/org/example/cp/oms/spec/model/vo/IProductDelegate.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.spec.model.vo; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * 订单的服务产品管理器. 7 | * 8 | *

它管理某个订单的服务产品.

9 | *

DDD里喜欢使用带有业务属性的VO,而不是直接使用Java primitive types,是很有道理的.

10 | */ 11 | public interface IProductDelegate { 12 | List getProducts(); 13 | } 14 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-spec/src/main/java/org/example/cp/oms/spec/model/vo/LockEntry.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.spec.model.vo; 2 | 3 | import lombok.Getter; 4 | import lombok.ToString; 5 | 6 | import javax.validation.constraints.NotNull; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | @ToString 10 | public class LockEntry { 11 | 12 | private final String key; 13 | private String prefix; 14 | 15 | @Getter 16 | private final TimeUnit timeUnit; 17 | 18 | @Getter 19 | private final long leaseTime; 20 | 21 | public LockEntry(@NotNull String key, long leaseTime, @NotNull TimeUnit timeUnit) { 22 | this.key = key; 23 | this.timeUnit = timeUnit; 24 | this.leaseTime = leaseTime; 25 | } 26 | 27 | public LockEntry withPrefix(String prefix) { 28 | this.prefix = prefix; 29 | return this; 30 | } 31 | 32 | public String lockKey() { 33 | if (prefix != null) { 34 | return prefix + ":" + key; 35 | } 36 | 37 | return key; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-spec/src/main/java/org/example/cp/oms/spec/resource/IStockRpc.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.spec.resource; 2 | 3 | // 这是个RPC资源的示例 4 | // Redis、JDBC、MQ等资源,同理:spec jar里声明,infrastructure jar里封装实现 5 | // 这样,Plugin所有的外部依赖都通过resource package由中台输出 6 | public interface IStockRpc { 7 | 8 | /** 9 | * 调用库存中心进行预占库存的操作. 10 | * 11 | * @param sku SKU number 12 | * @return true if successful 13 | */ 14 | boolean preOccupyStock(String sku); 15 | } 16 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-spec/src/main/java/org/example/cp/oms/spec/resource/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 中台对{@code Plugin}输出的所有资源. 3 | *

4 | *

资源,包括但不限于:RPC、Redis、JDBC、etc

5 | */ 6 | package org.example.cp.oms.spec.resource; -------------------------------------------------------------------------------- /order-center-cp/cp-oc-test/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.example 6 | order-center-cp 7 | 0.0.1 8 | 9 | 10 | cp-oc-test 11 | DDDplus :: Demo :: CP :: Test 12 | 13 | 14 | 15 | org.example 16 | cp-oc-controller 17 | ${project.version} 18 | 19 | 20 | 21 | io.github.dddplus 22 | dddplus-plugin 23 | 0.1.0 24 | 25 | 26 | io.github.dddplus 27 | dddplus-enforce 28 | ${dddplus.version} 29 | test 30 | 31 | 32 | io.github.dddplus 33 | dddplus-unit 34 | ${dddplus.version} 35 | test 36 | 37 | 38 | 39 | 40 | org.example 41 | order-center-stock-infrastructure 42 | ${project.version} 43 | 44 | 45 | 46 | 47 | com.google.guava 48 | guava 49 | ${guava.version} 50 | 51 | 52 | 53 | 54 | 55 | 56 | org.slf4j 57 | slf4j-api 58 | ${slf4j.version} 59 | 60 | 61 | log4j-core 62 | org.apache.logging.log4j 63 | 2.17.1 64 | 65 | 66 | org.apache.logging.log4j 67 | log4j-slf4j-impl 68 | 2.13.2 69 | 70 | 71 | org.apache.logging.log4j 72 | log4j-jcl 73 | 2.13.2 74 | 75 | 76 | 77 | 78 | junit 79 | junit 80 | ${junit.version} 81 | 82 | 83 | org.springframework 84 | spring-test 85 | ${spring.version} 86 | 87 | 88 | 89 | 90 | 91 | demo 92 | 93 | true 94 | 95 | 96 | 97 | org.example 98 | order-center-bp-isv 99 | ${project.version} 100 | 101 | 102 | org.example 103 | order-center-bp-ka 104 | ${project.version} 105 | 106 | 107 | org.example 108 | order-center-pattern 109 | ${project.version} 110 | 111 | 112 | 113 | 114 | 115 | 116 | plugin 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-test/src/test/java/org/example/cp/oms/ExampleTest.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms; 2 | 3 | import io.github.dddplus.api.RequestProfile; 4 | import org.example.cp.oms.spec.exception.OrderException; 5 | import org.example.cp.oms.domain.model.OrderMain; 6 | import org.example.cp.oms.domain.model.OrderModelCreator; 7 | import org.example.cp.oms.domain.service.SubmitOrder; 8 | import org.junit.Ignore; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.springframework.test.context.ContextConfiguration; 12 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 13 | 14 | import javax.annotation.Resource; 15 | 16 | import static org.junit.Assert.assertEquals; 17 | import static org.junit.Assert.assertTrue; 18 | import static org.junit.Assert.fail; 19 | 20 | @RunWith(SpringJUnit4ClassRunner.class) 21 | @ContextConfiguration(locations = {"classpath*:spring-test.xml"}) 22 | @Ignore 23 | public class ExampleTest { 24 | 25 | @Resource 26 | private SubmitOrder submitOrder; 27 | 28 | // 演示的入口,展示如何把这个例子完整地跑起来 29 | // 需要运行在 maven profile:demo 下 30 | @Test 31 | public void demoSubmitOrder() throws OrderException { 32 | // prepare the domain model 33 | RequestProfile requestProfile = new RequestProfile(); 34 | // 演示个性化字段:站点联系人号码是ISV前台场景才会需要的字段,其他场景不需要 35 | requestProfile.getExt().put("_station_contact_", "139100988343"); 36 | OrderModelCreator creator = new OrderModelCreator(); 37 | creator.setSource("ISV"); // IsvPartner 会触发ISV前台相关的扩展点 38 | creator.setCustomerNo("home"); // HomeAppliancePattern 会触发家电相关的扩展点 39 | creator.setExternalNo("20200987655"); 40 | creator.setRequestProfile(requestProfile); 41 | OrderMain orderModel = OrderMain.createWith(creator); 42 | 43 | // ISV业务前台的下单执行: 44 | // SerializableIsolationExt -> DecideStepsExt -> BasicStep(PresortExt) -> PersistStep(AssignOrderNoExt, CustomModelAbility) -> BroadcastStep 45 | // 查看日志,了解具体执行情况 46 | submitOrder.submit(orderModel); 47 | } 48 | 49 | // 下面是验证失败的场景:ISV前台入参里没有传递必填的“站点联系人号码” 50 | @Test 51 | public void failureCase() { 52 | // prepare the domain model 53 | RequestProfile requestProfile = new RequestProfile(); 54 | OrderModelCreator creator = new OrderModelCreator(); 55 | creator.setSource("ISV"); // IsvPartner 会触发ISV前台相关的扩展点 56 | creator.setCustomerNo("home"); // HomeAppliancePattern 会触发家电相关的扩展点 57 | creator.setExternalNo("20200987655"); 58 | creator.setRequestProfile(requestProfile); 59 | OrderMain orderModel = OrderMain.createWith(creator); 60 | try { 61 | submitOrder.submit(orderModel); 62 | fail(); 63 | } catch (OrderException expected) { 64 | assertEquals("109", expected.getCustom()); 65 | assertTrue(expected.hasCustom()); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-test/src/test/java/org/example/cp/oms/PluginMechanismTest.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms; 2 | 3 | import io.github.dddplus.api.RequestProfile; 4 | import io.github.dddplus.plugin.IPlugin; 5 | import io.github.dddplus.runtime.registry.Container; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.example.cp.oms.domain.model.OrderMain; 8 | import org.example.cp.oms.domain.model.OrderModelCreator; 9 | import org.example.cp.oms.domain.service.SubmitOrder; 10 | import org.junit.Ignore; 11 | import org.junit.Test; 12 | import org.springframework.context.ApplicationContext; 13 | import org.springframework.context.support.ClassPathXmlApplicationContext; 14 | 15 | import java.util.Collections; 16 | import java.util.concurrent.TimeUnit; 17 | 18 | import static io.github.dddplus.testing.LogAssert.assertContains; 19 | import static org.junit.Assert.assertEquals; 20 | 21 | @Slf4j 22 | @Ignore 23 | public class PluginMechanismTest { 24 | private static final String localKaJar = "../../order-center-bp-ka/target/order-center-bp-ka-0.0.1.jar"; 25 | private static final String localIsvJar = "../../order-center-bp-isv/target/order-center-bp-isv-0.0.1.jar"; 26 | private static final String localFreshJar = "../../order-center-bp-fresh/target/order-center-bp-fresh-0.0.1.jar"; 27 | 28 | // 需要运行在 profile:plugin 下,运行前需要mvn package为Plugin打包 29 | @Test 30 | public void dynamicLoadPlugins() throws Throwable { 31 | ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-test.xml"); 32 | applicationContext.start(); 33 | 34 | for (int i = 0; i < 5; i++) { 35 | // 同一个jar,load多次,模拟热更新,然后下单验证:走ISV前台逻辑 36 | log.info(String.join("", Collections.nCopies(50, String.valueOf(i + 1)))); 37 | Container.getInstance().loadPartnerPlugin("isv", "v1", localIsvJar, true); 38 | submitOrder(applicationContext, "ISV"); 39 | 40 | // 通过日志验证执行正确性 41 | assertContains( 42 | // 验证 AutoLoggerAspect 被创建 43 | "Spring created instance AutoLoggerAspect!", "AutoLoggerAspect 注册 Spring lifecycle ok", 44 | // IsvPartner在动态加载时自动创建实例 45 | "ISV new instanced", 46 | // isv.PluginListener 被调用 47 | "ISV Jar loaded, ", 48 | // @AutoLogger 49 | "DecideStepsExt.decideSteps 入参", 50 | // isv.DecideStepsExt.decideSteps 调用了中台的 stockService.preOccupyStock 51 | "预占库存:SKU From ISV", 52 | // ISV的步骤编排 53 | "steps [basic, persist, mq]", 54 | // @AutoLogger 55 | "org.example.bp.oms.isv.extension.PresortExt.presort 入参", 56 | // isv.PresortExt 57 | "ISV里预分拣的结果:1", "count(a): 2", "仓库号:WH009", 58 | // 加载properties资源,并且有中文 59 | "加载资源文件成功!站点名称:北京市海淀区中关村中路1号", 60 | // @AutoLogger 61 | "org.example.bp.oms.isv.extension.CustomModelExt.explain 入参:", 62 | // CustomModel,扩展属性机制 63 | "站点联系人号码:139100988343,保存到x2字段", 64 | "已经发送给MQ" 65 | ); 66 | } 67 | 68 | log.info(String.join("", Collections.nCopies(50, "="))); 69 | 70 | // 加载KA插件,并给KA下单 71 | Container.getInstance().loadPartnerPlugin("ka", "v1", localKaJar, true); 72 | submitOrder(applicationContext, "KA"); 73 | assertContains( 74 | "KA 预占库存 GSM098", 75 | "KA的锁TTL大一些", 76 | "steps [basic, persist]" 77 | ); 78 | 79 | // 目前已经加载了2个Plugin Jar 80 | assertEquals(2, Container.getInstance().getActivePlugins().size()); 81 | for (IPlugin plugin : Container.getInstance().getActivePlugins().values()) { 82 | log.info("Plugin: {}", plugin.getCode()); 83 | } 84 | 85 | log.info("sleeping 2m,等待修改bp-isv里逻辑后发布新jar..."); 86 | TimeUnit.MINUTES.sleep(2); // 等待手工发布新jar 87 | log.info("2m is up, go!"); 88 | Container.getInstance().loadPartnerPlugin("isv", "v2", localIsvJar, true); 89 | submitOrder(applicationContext, "ISV"); // 重新提交订单,看看是否新jar逻辑生效 90 | 91 | applicationContext.stop(); 92 | } 93 | 94 | @Test 95 | public void dynamicLoadPartnerWithoutSpring() throws Throwable { 96 | // 加载中台容器 97 | ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-test.xml"); 98 | applicationContext.start(); 99 | 100 | for (int i = 0; i < 5; i++) { 101 | Container.getInstance().loadPartnerPlugin("fresh", "v1", localFreshJar, false); 102 | submitOrder(applicationContext, "Fresh"); 103 | 104 | assertContains( 105 | "Fresh里预分拣的结果:2", 106 | "我,Fresh,要发个MQ通知我的下游!", 107 | "RPC调用库存中心系统,返回值是OK", 108 | "Fresh steps: [basic, stock, persist]" 109 | ); 110 | } 111 | 112 | applicationContext.stop(); 113 | } 114 | 115 | private void submitOrder(ApplicationContext applicationContext, String source) { 116 | // prepare the domain model 117 | RequestProfile requestProfile = new RequestProfile(); 118 | requestProfile.getExt().put("_station_contact_", "139100988343"); 119 | OrderModelCreator creator = new OrderModelCreator(); 120 | creator.setRequestProfile(requestProfile); 121 | creator.setSource(source); 122 | creator.setCustomerNo("home"); // HomeAppliancePattern 123 | creator.setExternalNo("20200987655"); 124 | OrderMain orderModel = OrderMain.createWith(creator); 125 | 126 | // call the domain service 127 | SubmitOrder submitOrder = (SubmitOrder) applicationContext.getBean("submitOrder"); 128 | // Partner(ISV)的下单执行: 129 | // SerializableIsolationExt -> DecideStepsExt -> BasicStep(PresortExt) -> PersistStep(AssignOrderNoExt) -> BroadcastStep 130 | // Partner(KA)的下单执行: 131 | // SerializableIsolationExt -> DecideStepsExt -> BasicStep -> PersistStep(AssignOrderNoExt) 132 | submitOrder.submit(orderModel); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-test/src/test/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d [%t] %-5p [%c] [%L] %m%n 5 | info 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | ${patternLayout} 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /order-center-cp/cp-oc-test/src/test/resources/spring-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /order-center-cp/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.example 6 | dddplus-demo 7 | 0.0.1 8 | 9 | 10 | order-center-cp 11 | pom 12 | DDDplus :: Demo :: CP 13 | 14 | 15 | cp-oc-controller 16 | cp-oc-domain 17 | cp-oc-infrastructure 18 | cp-oc-spec 19 | cp-oc-main 20 | cp-oc-test 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /order-center-domain-stock/order-center-stock-domain/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.example 6 | order-center-domain-stock 7 | 0.0.1 8 | 9 | 10 | order-center-stock-domain 11 | DDDplus :: Demo :: Domain :: Stock :: Domain 12 | 13 | 14 | 15 | org.example 16 | order-center-stock-spec 17 | 0.0.1 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /order-center-domain-stock/order-center-stock-domain/src/main/java/org/example/oms/d/stock/domain/facade/rpc/IRemoteStockRpc.java: -------------------------------------------------------------------------------- 1 | package org.example.oms.d.stock.domain.facade.rpc; 2 | 3 | public interface IRemoteStockRpc { 4 | 5 | void doOccupy(String sku, Integer quantity); 6 | } 7 | -------------------------------------------------------------------------------- /order-center-domain-stock/order-center-stock-domain/src/main/java/org/example/oms/d/stock/domain/service/StockService.java: -------------------------------------------------------------------------------- 1 | package org.example.oms.d.stock.domain.service; 2 | 3 | import io.github.dddplus.annotation.DomainService; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.example.cp.oms.spec.model.IOrderMain; 6 | import org.example.cp.oms.spec.model.vo.IOrderItem; 7 | import org.example.oms.d.stock.domain.facade.rpc.IRemoteStockRpc; 8 | import org.example.oms.d.stock.spec.StockDomain; 9 | import org.example.oms.d.stock.spec.service.IStockService; 10 | 11 | import javax.annotation.Resource; 12 | 13 | @DomainService(domain = StockDomain.CODE) 14 | @Slf4j 15 | public class StockService implements IStockService { 16 | 17 | @Resource 18 | private IRemoteStockRpc remoteStockRpc; 19 | 20 | @Override 21 | public void occupyStock(IOrderMain orderMain) { 22 | log.info("会通过infrastructure层调用库存中心的RPC接口,执行预占库存动作"); 23 | log.info("这里的逻辑,主要是根据不同业务场景组织库存中心RPC的入参,并对返回结果进行处理"); 24 | 25 | for (IOrderItem item : orderMain.itemDelegate().getItems()) { 26 | // 生产环境,看到会批量调用,这里只是演示 27 | remoteStockRpc.doOccupy(item.getSku(), item.getQuantity()); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /order-center-domain-stock/order-center-stock-infrastructure/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.example 6 | order-center-domain-stock 7 | 0.0.1 8 | 9 | 10 | order-center-stock-infrastructure 11 | DDDplus :: Demo :: Domain :: Stock :: Infrastructure 12 | 13 | 14 | 15 | org.example 16 | order-center-stock-domain 17 | ${project.version} 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /order-center-domain-stock/order-center-stock-infrastructure/src/main/java/org/example/oms/d/stock/infra/rpc/RemoteStockRpc.java: -------------------------------------------------------------------------------- 1 | package org.example.oms.d.stock.infra.rpc; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.example.oms.d.stock.domain.facade.rpc.IRemoteStockRpc; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | @Slf4j 9 | public class RemoteStockRpc implements IRemoteStockRpc { 10 | 11 | @Override 12 | public void doOccupy(String sku, Integer quantity) { 13 | log.info("RPC调用库存中心系统,返回值是OK!"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /order-center-domain-stock/order-center-stock-spec/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.example 6 | order-center-domain-stock 7 | 0.0.1 8 | 9 | 10 | order-center-stock-spec 11 | DDDplus :: Demo :: Domain :: Stock :: Spec 12 | 13 | 14 | 15 | 16 | 17 | org.example 18 | cp-oc-spec 19 | ${project.version} 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /order-center-domain-stock/order-center-stock-spec/src/main/java/org/example/oms/d/stock/spec/StockDomain.java: -------------------------------------------------------------------------------- 1 | package org.example.oms.d.stock.spec; 2 | 3 | import io.github.dddplus.annotation.Domain; 4 | 5 | @Domain(code = StockDomain.CODE, name = "库存支撑域") 6 | public class StockDomain { 7 | public static final String CODE = "stock"; 8 | } 9 | -------------------------------------------------------------------------------- /order-center-domain-stock/order-center-stock-spec/src/main/java/org/example/oms/d/stock/spec/service/IStockService.java: -------------------------------------------------------------------------------- 1 | package org.example.oms.d.stock.spec.service; 2 | 3 | import io.github.dddplus.model.IDomainService; 4 | import org.example.cp.oms.spec.model.IOrderMain; 5 | 6 | // 库存相关的服务,都收敛在库存支撑域,通过领域服务提供给订单核心域调用:JVM内调用, not RPC 7 | public interface IStockService extends IDomainService { 8 | 9 | /** 10 | * 预占预存. 11 | * 12 | * @param orderMain core domain提供的领域模型聚合根 13 | */ 14 | void occupyStock(IOrderMain orderMain); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /order-center-domain-stock/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.example 6 | dddplus-demo 7 | 0.0.1 8 | 9 | 10 | order-center-domain-stock 11 | pom 12 | DDDplus :: Demo :: Domain :: Stock 13 | 订单的支撑域:库存 14 | 15 | 16 | order-center-stock-spec 17 | order-center-stock-domain 18 | order-center-stock-infrastructure 19 | 20 | 21 | 22 | 23 | io.github.dddplus 24 | dddplus-runtime 25 | ${dddplus.version} 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /order-center-pattern/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.example 6 | dddplus-demo 7 | 0.0.1 8 | 9 | 10 | order-center-pattern 11 | DDDplus :: Demo :: CP :: Patterns 12 | 13 | 14 | 15 | org.example 16 | cp-oc-spec 17 | ${project.version} 18 | provided 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /order-center-pattern/src/main/java/org/example/cp/oms/pattern/ColdChainB2BPattern.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.pattern; 2 | 3 | import io.github.dddplus.annotation.Pattern; 4 | import io.github.dddplus.ext.IIdentityResolver; 5 | import org.example.cp.oms.spec.Patterns; 6 | import org.example.cp.oms.spec.model.IOrderMain; 7 | 8 | import javax.validation.constraints.NotNull; 9 | 10 | @Pattern(code = ColdChainB2BPattern.CODE, name = "冷链B2B模式", tags = Patterns.Tags.B2B) 11 | public class ColdChainB2BPattern implements IIdentityResolver { 12 | public static final String CODE = Patterns.ColdChainB2B; 13 | 14 | @Override 15 | public boolean match(@NotNull IOrderMain model) { 16 | return model.isB2B() && model.isColdChain(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /order-center-pattern/src/main/java/org/example/cp/oms/pattern/HairPattern.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.pattern; 2 | 3 | import io.github.dddplus.annotation.Pattern; 4 | import org.example.cp.oms.spec.Patterns; 5 | import org.example.cp.oms.spec.model.IOrderMain; 6 | import io.github.dddplus.ext.IIdentityResolver; 7 | 8 | import javax.validation.constraints.NotNull; 9 | 10 | @Pattern(code = HairPattern.CODE, name = "海尔业务模式") 11 | public class HairPattern implements IIdentityResolver { 12 | public static final String CODE = Patterns.Hair; 13 | 14 | @Override 15 | public boolean match(@NotNull IOrderMain model) { 16 | if (model.getCustomerNo() == null) { 17 | return false; 18 | } 19 | 20 | return model.getCustomerNo().equals(CODE); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /order-center-pattern/src/main/java/org/example/cp/oms/pattern/HomeAppliancePattern.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.pattern; 2 | 3 | import io.github.dddplus.annotation.Pattern; 4 | import org.example.cp.oms.spec.Patterns; 5 | import org.example.cp.oms.spec.model.IOrderMain; 6 | import io.github.dddplus.ext.IIdentityResolver; 7 | 8 | import javax.validation.constraints.NotNull; 9 | 10 | @Pattern(code = HomeAppliancePattern.CODE, name = "家用电器行业模式") 11 | public class HomeAppliancePattern implements IIdentityResolver { 12 | public static final String CODE = Patterns.HomeAppliance; 13 | 14 | @Override 15 | public boolean match(@NotNull IOrderMain model) { 16 | if (model.getCustomerNo() == null) { 17 | return false; 18 | } 19 | 20 | return model.getCustomerNo().equals(CODE); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /order-center-pattern/src/main/java/org/example/cp/oms/pattern/extension/coldchain_b2b/SerializableIsolationExt.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.pattern.extension.coldchain_b2b; 2 | 3 | import io.github.dddplus.annotation.Extension; 4 | import org.example.cp.oms.pattern.ColdChainB2BPattern; 5 | import org.example.cp.oms.spec.ext.ISerializableIsolationExt; 6 | import org.example.cp.oms.spec.model.IOrderMain; 7 | import org.example.cp.oms.spec.model.vo.LockEntry; 8 | 9 | import javax.validation.constraints.NotNull; 10 | import java.util.concurrent.TimeUnit; 11 | 12 | @Extension(code = ColdChainB2BPattern.CODE, value = "ccb2bSerializableIsolationExt", name = "冷链B2B模式下的防并发机制") 13 | public class SerializableIsolationExt implements ISerializableIsolationExt { 14 | 15 | @Override 16 | public LockEntry createLockEntry(@NotNull IOrderMain model) { 17 | return new LockEntry(model.customerProvidedOrderNo(), 19, TimeUnit.MINUTES); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /order-center-pattern/src/main/java/org/example/cp/oms/pattern/extension/hair/ReviseStepsExt.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.pattern.extension.hair; 2 | 3 | import io.github.dddplus.annotation.Extension; 4 | import org.example.cp.oms.pattern.HairPattern; 5 | import org.example.cp.oms.spec.Steps; 6 | import org.example.cp.oms.spec.ext.IReviseStepsExt; 7 | import org.example.cp.oms.spec.model.IOrderMain; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | @Extension(code = HairPattern.CODE, value = "hairReviseStepsExt") 13 | public class ReviseStepsExt implements IReviseStepsExt { 14 | 15 | @Override 16 | public List reviseSteps(IOrderMain model) { 17 | if (Steps.SubmitOrder.Activity.equals(model.currentActivity())) { 18 | if (model.currentStep().equals(Steps.SubmitOrder.BasicStep)) { 19 | List subsequentSteps = new ArrayList<>(); 20 | return subsequentSteps; // 没有后续步骤了:跳过PersistStep 21 | } 22 | } 23 | 24 | List result = new ArrayList<>(); 25 | return result; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /order-center-pattern/src/main/java/org/example/cp/oms/pattern/extension/hair/SerializableIsolationExt.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.pattern.extension.hair; 2 | 3 | import io.github.dddplus.annotation.Extension; 4 | import org.example.cp.oms.pattern.HairPattern; 5 | import org.example.cp.oms.spec.ext.ISerializableIsolationExt; 6 | import org.example.cp.oms.spec.model.IOrderMain; 7 | import org.example.cp.oms.spec.model.vo.LockEntry; 8 | 9 | import javax.validation.constraints.NotNull; 10 | import java.util.concurrent.TimeUnit; 11 | 12 | @Extension(code = HairPattern.CODE, value = "hairSerializableIsolationExt") 13 | public class SerializableIsolationExt implements ISerializableIsolationExt { 14 | 15 | @Override 16 | public LockEntry createLockEntry(@NotNull IOrderMain model) { 17 | return new LockEntry(model.customerProvidedOrderNo(), 1, TimeUnit.HOURS); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /order-center-pattern/src/main/java/org/example/cp/oms/pattern/extension/home_appliance/AssignOrderNoExt.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.pattern.extension.home_appliance; 2 | 3 | import org.example.cp.oms.pattern.HomeAppliancePattern; 4 | import io.github.dddplus.annotation.Extension; 5 | import org.example.cp.oms.spec.ext.IAssignOrderNoExt; 6 | import org.example.cp.oms.spec.model.IOrderMain; 7 | 8 | import javax.validation.constraints.NotNull; 9 | 10 | @Extension(code = HomeAppliancePattern.CODE, value = "homeAssignOrderNoExt") 11 | public class AssignOrderNoExt implements IAssignOrderNoExt { 12 | public static final String HOME_ORDER_NO = "SO9987012"; 13 | 14 | @Override 15 | public void assignOrderNo(@NotNull IOrderMain model) { 16 | model.assignOrderNo(this, HOME_ORDER_NO); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /order-center-pattern/src/main/java/org/example/cp/oms/pattern/extension/home_appliance/PresortExt.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.pattern.extension.home_appliance; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import io.github.dddplus.annotation.Extension; 5 | import org.example.cp.oms.pattern.HomeAppliancePattern; 6 | import org.example.cp.oms.spec.ext.IPresortExt; 7 | import org.example.cp.oms.spec.model.IOrderMain; 8 | 9 | import javax.validation.constraints.NotNull; 10 | 11 | @Slf4j 12 | @Extension(code = HomeAppliancePattern.CODE, value = "homePresort") 13 | public class PresortExt implements IPresortExt { 14 | 15 | @Override 16 | public void presort(@NotNull IOrderMain model) { 17 | log.info("家电的预分拣执行了"); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /order-center-pattern/src/main/java/org/example/cp/oms/pattern/extension/home_appliance/SerializableIsolationExt.java: -------------------------------------------------------------------------------- 1 | package org.example.cp.oms.pattern.extension.home_appliance; 2 | 3 | import io.github.dddplus.annotation.Extension; 4 | import org.example.cp.oms.pattern.HomeAppliancePattern; 5 | import org.example.cp.oms.spec.ext.ISerializableIsolationExt; 6 | import org.example.cp.oms.spec.model.IOrderMain; 7 | import org.example.cp.oms.spec.model.vo.LockEntry; 8 | 9 | import javax.validation.constraints.NotNull; 10 | import java.util.concurrent.TimeUnit; 11 | 12 | @Extension(code = HomeAppliancePattern.CODE, value = "homeSerializableIsolationExt") 13 | public class SerializableIsolationExt implements ISerializableIsolationExt { 14 | 15 | @Override 16 | public LockEntry createLockEntry(@NotNull IOrderMain model) { 17 | return new LockEntry(model.customerProvidedOrderNo(), 5, TimeUnit.MINUTES); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | org.example 5 | dddplus-demo 6 | 0.0.1 7 | pom 8 | DDDplus :: Demo 9 | Demonstrate how to use cp-ddd-framework 10 | 11 | 12 | order-center-cp 13 | order-center-pattern 14 | order-center-bp-ka 15 | order-center-bp-isv 16 | order-center-bp-fresh 17 | order-center-domain-stock 18 | 19 | 20 | 21 | 22 | 1.8 23 | true 24 | UTF-8 25 | 26 | 1.1.2 27 | 28 | 29 | 1.18.8 30 | 19.0 31 | 4.13.1 32 | 1.9.1 33 | 1.1.0.Final 34 | 1.7.25 35 | 4.3.12.RELEASE 36 | 9.4.12.v20180830 37 | 2.4.5 38 | 39 | 40 | 41 | 42 | org.projectlombok 43 | lombok 44 | ${lombok.version} 45 | provided 46 | 47 | 48 | 49 | org.slf4j 50 | slf4j-api 51 | ${slf4j.version} 52 | 53 | 54 | 55 | 56 | 57 | ossrh-snapshots 58 | https://oss.sonatype.org/content/repositories/snapshots 59 | 60 | 61 | 62 | 63 | package 64 | 65 | 66 | org.apache.maven.plugins 67 | maven-compiler-plugin 68 | 3.8.1 69 | 70 | ${jdk.version} 71 | ${jdk.version} 72 | ${project.build.sourceEncoding} 73 | 74 | 75 | 76 | org.apache.maven.plugins 77 | maven-surefire-plugin 78 | 2.21.0 79 | 80 | false 81 | 82 | 83 | 84 | 85 | 86 | 87 | --------------------------------------------------------------------------------