作者:付政委
感恩2019,迎接2020
微信公众号:bugstack虫洞栈 | 关注获取源码 沉淀、分享、成长,专注于原创专题案例,以最易学习编程的方式分享知识,让自己和他人都能有所收获。目前已完成的专题有;Netty4.x实战专题案例、用Java实现JVM、基于JavaAgent的全链路监控、手写RPC框架、架构设计专题案例[Ing]等。 你用剑?、我用刀?,好的代码都很烧?,望你不吝出招?!
前言介绍
随着项目需求的变化,或者说从小公司跳槽了互联网。需求变化了、承载的用户体量增多了,整体系统的架构也随着改变了。就像你做毕业设计的时候,可能只为了完成功能即可,一个单体的MVC结构足可以让你毕业。但!现在你长大了,为了可以承载几百、几千、几亿的用户体量,你开始发现原来还有这么多套路在里面。对于爱学习的人,肯定蠢蠢欲动的想研究研究了(不研究也写不了代码,能抗住揍不)!
在我们的技术栈中RPC框架有;Dubbo、Motan、Tars、gRPC等等,而且每个公司可能还有自己的RPC,如果想深入了解那么可以参照《手写RPC框架第三章《RPC中间件》》。对于一个程序猿来说还是要从多家的框架中吸取营养,精进技术。
本章节我们主要将Dubbo技术与DDD的架构融合,搭建出分布式架构体系。随着一点点的深入,本案例没有引入过多的过技术栈,比如;Mq、ES、分库分表等,这些会随着后续的章节陆续完善。当前章节尽可能简单的体现核心内容;
- 分布式框架下父类文件定义,统一版本标准
- RPC框架需要接口信息描述性Jar对外发布,结合领域驱动设计进行定义
- 尝试使用Dubbo的广播模式,进行发布和使用,简化系统调试
- 感受领域驱动设计的魅力,能落地才有机会使用
好! 那么,最后在开始之前,再问一个小问题。
实现了Serializable接口的类,怎么自动生成serialVersionUID(总不能自己乱编呀)
答:其实在Idea中已经提供了这样自动生成功能,只需要配置上即可;File -> Settings -> Editor -> Inspections -> 搜索 Serialization issues ,找到 Serializable class without 'serialVersionUID' ->打上勾,Apply->OK 效果如图;
微信公众号:bugstack虫洞栈 & 配置方式
工程环境
- JDK1.8
- Maven 3.2.3
- Spring 4.3.24.RELEASE Mybatis 3.3.0
- Mysql 5.6 dbcp2
- Dubbo 2.6.6
- Redis 2.9.0
工程模型
代码语言:javascript复制itstack-demo-frame-parent 父类工程
1itstack-demo-frame-parent
2├── itstack-demo-frame-parent
3│ ├── src
4│ │ └── main
5│ │ └── java
6│ │ └── org.itstack.demo.frame.common
7│ │ ├── constants
8│ │ │ └── Constants.java
9│ │ └── domain
10│ │ ├── PageRequest.java
11│ │ └── Result.java
12│ └── pom.xml
13└── pom.xml
代码语言:javascript复制itstack-demo-frame-dcs 分布式框架
1itstack-demo-frame-dcs
2├── itstack-demo-frame-dcs-ddd
3│ └── src
4│ ├── main
5│ │ ├── java
6│ │ │ └── org.itstack.demo
7│ │ │ ├── application
8│ │ │ │ └── UserService.java
9│ │ │ ├── domain
10│ │ │ │ ├── model
11│ │ │ │ │ ├── aggregates
12│ │ │ │ │ │ └── UserInfoCollect.java
13│ │ │ │ │ ├── req
14│ │ │ │ │ │ └── UserReq.java
15│ │ │ │ │ └── vo
16│ │ │ │ │ └── UserInfo.java
17│ │ │ │ ├── repository
18│ │ │ │ │ └── IUserRepository.java
19│ │ │ │ └── service
20│ │ │ │ └── UserServiceImpl.java
21│ │ │ ├── infrastructure
22│ │ │ │ ├── common
23│ │ │ │ │ ├── EasyResult.java
24│ │ │ │ │ └── PageRequest.java
25│ │ │ │ ├── dao
26│ │ │ │ │ └── IUserDao.java
27│ │ │ │ ├── po
28│ │ │ │ │ └── User.java
29│ │ │ │ └── repository
30│ │ │ │ └── UserRepository.java
31│ │ │ └── interfaces
32│ │ │ └── UserController.java
33│ │ ├── resources
34│ │ │ ├── mapper
35│ │ │ ├── props
36│ │ │ ├── spring
37│ │ │ ├── logback.xml
38│ │ │ ├── mybatis-config.xml
39│ │ │ └── spring-config.xml
40│ │ └── webapp
41│ │ ├── page
42│ │ ├── res
43│ │ ├── WEB-INF
44│ │ ├── index.html
45│ │ └── res_layui.html
46│ └── test
47│ └── java
48│ └── org.itstack.demo.test
49│ └── ApiTest.java
50│
51└── itstack-demo-frame-dcs-rpc
52 └── src
53 └── main
54 └── java
55 └── org.itstack.demo.rpc
56 ├── dto
57 │ └── UserDto.java
58 ├── req
59 │ └── UserReq.java
60 ├── res
61 │ └── UserRes.java
62 └── IUserRpc.java
代码语言:javascript复制itstack-demo-frame-dcs-test RPC测试工程
1itstack-demo-frame-dcs-test
2└── src
3 ├── main
4 │ ├── java
5 │ │ └── org.itstack.demo.interfaces
6 │ │ └── UserController.java
7 │ ├── resources
8 │ │ ├── spring
9 │ │ ├── logback.xml
10 │ │ └── spring-config.xml
11 │ └── webapp
12 │ ├── page
13 │ ├── res
14 │ ├── WEB-INF
15 │ ├── index.html
16 │ └── res_layui.html
17 └── test
18 └── java
19 └── org.itstack.demo.test
20 └── ApiTest.java
以下对工程模块进行介绍,整体源码获取,可以关注公众号:bugstack虫洞栈,回复:框架搭建
一、父类工程
- 父类工程如果没有定义也是可以工作的,但是随着系统量的增加和复杂度提高后,会越来越难以维护各个版本和升级,所以需要;
- 定义通用common,使各个服务工程都有统一的;异常枚举、分页类、返回对象等
- 定义POM配置,协调各个组件版本保持统一;减少jar冲突、维护统一版本、方便升级
1<groupId>org.itstack.demo</groupId>
2<artifactId>itstack-demo-frame-parent</artifactId>
3<version>1.0.0-RELEASE</version>
4<modules>
5 <module>itstack-demo-frame-common</module>
6</modules>
7<packaging>pom</packaging>
8<name>itstack-demo-frame-parent</name>
9<description>itstack Demo Project Dependencies</description>
10<properties>
11 <!-- Base -->
12 <jdk.version>1.8</jdk.version>
13 <sourceEncoding>UTF-8</sourceEncoding>
14 <!-- Spring -->
15 <spring.version>4.3.24.RELEASE</spring.version>
16 <servlet-api.version>2.5</servlet-api.version>
17 <spring.redis.version>1.8.4.RELEASE</spring.redis.version>
18 <!-- DB:mysql、mybatis-->
19 <mysql.version>5.1.20</mysql.version>
20 <mybatis.version>3.3.0</mybatis.version>
21 <mybatis_spring.version>1.2.3</mybatis_spring.version>
22 <!-- JSON -->
23 <fastjson.version>1.2.60</fastjson.version>
24 <jackson.version>2.5.4</jackson.version>
25 <!-- Junit -->
26 <junit.version>4.12</junit.version>
27 <!-- Common -->
28 <commons-dbcp2.version>2.6.0</commons-dbcp2.version>
29 <commons-lang3.version>3.8.1</commons-lang3.version>
30 <!-- 日志 -->
31 <slf4j.version>1.7.7</slf4j.version>
32 <logback.version>1.0.9</logback.version>
33 <!-- 其他服务 -->
34 <dubbo.version>2.6.6</dubbo.version>
35 <zookeeper.version>3.4.14</zookeeper.version>
36 <netty.version>4.1.36.Final</netty.version>
37 <redis.version>2.9.0</redis.version>
38 <scheduler.version>2.3.2</scheduler.version>
39</properties>
二、分布式框架
- Dubbo结合领域驱动设计,由RPC定义接口描述信息,单独出一个模块便于外部调用方进行引用
- 领域驱动设计方面知识已经在https://bugstack.cn提供很多思路,可以参考
- 整体架构模型功能定义如下;
微信公众号:bugstack虫洞栈 & 分布式框架功能定义
application应用层
应用层是比较薄的一层,不做具体逻辑开发。本工程里只包括服务的定义,具体逻辑有领域层实现。
代码语言:javascript复制UserService.java & 服务定义
1public interface UserService {
2
3 UserInfoCollect queryUserInfoList(UserReq req);
4
5}
domain领域层
领域层是整个工程的核心服务层,这里负责处理具体的核心功能,完成领域服务。domain下可以有多个领域,每个领域里包括;聚合、请求对象、业务对象、仓储、服务。
代码语言:javascript复制UserServiceImpl.java & 服务实现
1@Service("userService")
2public class UserServiceImpl implements UserService {
3
4 @Resource(name = "userRepository")
5 private IUserRepository userRepository;
6
7 @Override
8 public UserInfoCollect queryUserInfoList(UserReq req) {
9 return userRepository.queryUserInfoList(req);
10 }
11
12}
代码语言:javascript复制IUserRepository.java & 仓库定义
1public interface IUserRepository {
2
3 UserInfoCollect queryUserInfoList(UserReq req);
4
5}
infrastructure基础层
实现领域层仓储定义,数据库操作为非业务属性的功能操作,在仓储实现层进行组合装配DAO&Redis&Cache等。
代码语言:javascript复制UserDBRepository.java & 仓库实现
1@Repository("userDBRepository")
2public class UserDBRepository implements IUserRepository {
3
4 @Resource
5 private IUserDao userDao;
6 @Resource
7 private Redis redis;
8
9 @Override
10 public UserInfoCollect queryUserInfoList(UserReq req) {
11 Long count = userDao.queryUserInfoCount(req);
12 List<User> userList = userDao.queryUserInfoList(req);
13 List<UserInfo> userInfoList = new ArrayList<>();
14 userList.forEach(user -> {
15 UserInfo userInfo = new UserInfo();
16 userInfo.setUserId(user.getId());
17 userInfo.setName(user.getName());
18 userInfo.setAge(user.getAge());
19 userInfo.setAddress(user.getAddress());
20 userInfo.setEntryTime(user.getEntryTime());
21 userInfo.setStatus(user.getStatus());
22 userInfoList.add(userInfo);
23 });
24 UserInfoCollect userInfoCollect = new UserInfoCollect(count, userInfoList);
25 if (StringUtils.isNoneBlank(req.getName())) {
26 redis.set(req.getName(), JSON.toJSONString(userInfoCollect));
27 }
28 return userInfoCollect;
29 }
30
31}
interfaces接口层
- 实现rpc定义接口对外提供api,目前这一层比较简单只需要进行接口使用即可
- 如果是对外部提供服务接口,那么可以使用DTO方式进行转换,避免污染到业务类
- assembler 是对DTO对象的转换类,可以封装的更加精致一些
代码语言:javascript复制UserRpc.java & RPC接口实现
1@Service("userRpc")
2public class UserRpc implements IUserRpc {
3
4 @Resource
5 private UserService userService;
6
7 @Override
8 public UserRes queryUserInfoList(UserReq req) {
9 UserInfoCollect userInfoCollect = userService.queryUserInfoList(UserAssembler.buildUserReq(req));
10 return UserAssembler.buildUserInfoCollect(userInfoCollect);
11 }
12
13}
rpc对外提供服务层
服务接口定义,rpc框架需要对外提供接口描述jar包,因此单独提取出来是最方面处理的。不要让这一层引用其他层的逻辑代码。
代码语言:javascript复制IUserRpc.java & 接口定义
1public interface IUserRpc {
2
3 UserRes queryUserInfoList(UserReq req);
4
5}
父类配置
这一层是整个工程的最外层POM文件,引入父类的定义配置
代码语言:javascript复制 1<?xml version="1.0" encoding="UTF-8"?>
2<project xmlns="http://maven.apache.org/POM/4.0.0"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5 <modelVersion>4.0.0</modelVersion>
6
7 <!-- 框架统一服务定义 -->
8 <parent>
9 <groupId>org.itstack.demo</groupId>
10 <artifactId>itstack-demo-frame-parent</artifactId>
11 <version>1.0.0-RELEASE</version>
12 </parent>
13
14 <artifactId>itstack-demo-frame-dcs</artifactId>
15 <packaging>pom</packaging>
16 <version>1.0.0-SNAPSHOT</version>
17
18 <modules>
19 <module>itstack-demo-frame-dcs-ddd</module>
20 <module>itstack-demo-frame-dcs-rpc</module>
21 </modules>
22
23 ...
24
25</project>
Dubbo配置信息
- dubbo 2.6.x 版本可以使用广播方式进行服务暴漏,也就省去了zookeeper注册中心。对于一些中小服务来言,就更加方便了。
- 广播地址:address="multicast://224.5.6.7:1234"
- Netty服务端的端口:
1<?xml version="1.0" encoding="UTF-8"?>
2<beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
5 xsi:schemaLocation="http://www.springframework.org/schema/beans
6 http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
7 http://dubbo.apache.org/schema/dubbo
8 http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
9
10 <!-- 提供方应用信息,用于计算依赖关系 -->
11 <dubbo:application name="itstack-demo-frame-dcs"/>
12
13 <!-- 使用multicast广播注册中心暴露服务地址 -->
14 <dubbo:registry address="multicast://224.5.6.7:1234"/>
15
16 <!-- 用dubbo协议在20880端口暴露服务 -->
17 <dubbo:protocol name="dubbo" port="20880"/>
18
19 <!-- 声明需要暴露的服务接口 -->
20 <dubbo:service interface="org.itstack.demo.rpc.IUserRpc" ref="userRpc"/>
21
22</beans>
数据库表配置(itstack.sql)
代码语言:javascript复制1DROP TABLE user;
2CREATE TABLE user ( id bigint(11) NOT NULL AUTO_INCREMENT, name varchar(32), age int(4), address varchar(128), entryTime datetime, remark varchar(64), createTime datetime, updateTime datetime, status int(4) DEFAULT '0', PRIMARY KEY (id), INDEX idx_name (name) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
3insert into user (id, name, age, address, entryTime, remark, createTime, updateTime, status) values (1, '水水', 18, '吉林省榆树市黑林镇尹家村5组', '2019-12-22 00:00:00', '无', '2019-12-22 00:00:00', '2019-12-22 00:00:00', 0);
4insert into user (id, name, age, address, entryTime, remark, createTime, updateTime, status) values (2, '豆豆', 18, '辽宁省大连市清河湾司马道407路', '2019-12-22 00:00:00', '无', '2019-12-22 00:00:00', '2019-12-22 00:00:00', 1);
5insert into user (id, name, age, address, entryTime, remark, createTime, updateTime, status) values (3, '花花', 19, '辽宁省大连市清河湾司马道407路', '2019-12-22 00:00:00', '无', '2019-12-22 00:00:00', '2019-12-22 00:00:00', 0);
三、RPC测试工程
这一层就很简单了,添加好dubbo配置,引用RPC接口定义POM,调用服务端接口返回数据即可
代码语言:javascript复制pom.xml 引用RPC定义接口
1<dependency>
2 <groupId>org.itstack.demo</groupId>
3 <artifactId>itstack-demo-frame-dcs-rpc</artifactId>
4 <version>1.0.0-SNAPSHOT</version>
5</dependency>
代码语言:javascript复制spring-config-dubbo-consumer.xml & dubbo配置
1<?xml version="1.0" encoding="UTF-8"?>
2<beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
5 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
6
7 <!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->
8 <dubbo:application name="itstack-demo-frame-dcs-test" />
9
10 <!-- 使用multicast广播注册中心暴露发现服务地址 -->
11 <dubbo:registry address="multicast://224.5.6.7:1234" />
12
13 <!-- 生成远程服务代理,可以和本地bean一样使用demoService -->
14 <dubbo:reference id="userRpc" interface="org.itstack.demo.rpc.IUserRpc" />
15
16</beans>
代码语言:javascript复制ApiTest.java & 单元测试类
1@RunWith(SpringJUnit4ClassRunner.class)
2@ContextConfiguration("classpath:spring-config.xml")
3public class ApiTest {
4
5 private Logger logger = LoggerFactory.getLogger(ApiTest.class);
6
7 @Resource
8 private IUserRpc userRpc;
9
10 @Test
11 public void test_queryUserInfoList() {
12 UserReq req = new UserReq();
13 req.setName("豆豆");
14 req.setPage("1", "5");
15 UserRes res = userRpc.queryUserInfoList(req);
16 logger.info("rn测试结果 req:{} res:{}", JSON.toJSONString(req), JSON.toJSONString(res));
17 }
18
19}
测试验证
- 启动Redis配置服务(可以下载win版本),因为本案例使用到Redis。
- tomcat中启动itstack-demo-frame-dcs
- 启动单元测试调用itstack-demo-frame-dcs-test
12019-12-29 09:20:43.268 [DubboMulticastRegistryReceiver] INFO com.alibaba.dubbo.registry.multicast.MulticastRegistry[387] - [DUBBO] Notify urls for subscribe url consumer://127.0.0.1/org.itstack.demo.rpc.IUserRpc?application=itstack-demo-frame-dcs-test&category=providers,configurators,routers&dubbo=2.0.2&interface=org.itstack.demo.rpc.IUserRpc&methods=queryUserInfoList&pid=14416&revision=1.0.0-SNAPSHOT&side=consumer×tamp=1577582442523, urls: [dubbo://127.0.0.1:20880/org.itstack.demo.rpc.IUserRpc?anyhost=true&application=itstack-demo-frame-dcs&bean.name=org.itstack.demo.rpc.IUserRpc&dubbo=2.0.2&generic=false&interface=org.itstack.demo.rpc.IUserRpc&methods=queryUserInfoList&pid=15048&revision=1.0.0-SNAPSHOT&side=provider×tamp=1577582403854], dubbo version: 2.6.6, current host: 127.0.0.1
22019-12-29 09:20:43.397 [DubboMulticastRegistryReceiver] INFO com.alibaba.dubbo.remoting.transport.AbstractClient[282] - [DUBBO] Successed connect to server /127.0.0.1:20880 from NettyClient 127.0.0.1 using dubbo version 2.6.6, channel is NettyChannel [channel=[id: 0x82d694ae, L:/127.0.0.1:65193 - R:/127.0.0.1:20880]], dubbo version: 2.6.6, current host: 127.0.0.1
32019-12-29 09:20:43.398 [DubboMulticastRegistryReceiver] INFO com.alibaba.dubbo.remoting.transport.AbstractClient[91] - [DUBBO] Start NettyClient JRA1W11T0247/127.0.0.1 connect to the server /127.0.0.1:20880, dubbo version: 2.6.6, current host: 127.0.0.1
42019-12-29 09:20:43.449 [main] INFO com.alibaba.dubbo.registry.multicast.MulticastRegistry[387] - [DUBBO] Notify urls for subscribe url consumer://127.0.0.1/org.itstack.demo.rpc.IUserRpc?application=itstack-demo-frame-dcs-test&category=providers,configurators,routers&dubbo=2.0.2&interface=org.itstack.demo.rpc.IUserRpc&methods=queryUserInfoList&pid=14416&revision=1.0.0-SNAPSHOT&side=consumer×tamp=1577582442523, urls: [dubbo://127.0.0.1:20880/org.itstack.demo.rpc.IUserRpc?anyhost=true&application=itstack-demo-frame-dcs&bean.name=org.itstack.demo.rpc.IUserRpc&dubbo=2.0.2&generic=false&interface=org.itstack.demo.rpc.IUserRpc&methods=queryUserInfoList&pid=15048&revision=1.0.0-SNAPSHOT&side=provider×tamp=1577582403854], dubbo version: 2.6.6, current host: 127.0.0.1
52019-12-29 09:20:43.454 [main] INFO com.alibaba.dubbo.config.AbstractConfig[429] - [DUBBO] Refer dubbo service org.itstack.demo.rpc.IUserRpc from url multicast://224.5.6.7:1234/com.alibaba.dubbo.registry.RegistryService?anyhost=true&application=itstack-demo-frame-dcs-test&bean.name=org.itstack.demo.rpc.IUserRpc&check=false&dubbo=2.0.2&generic=false&interface=org.itstack.demo.rpc.IUserRpc&methods=queryUserInfoList&pid=14416®ister.ip=127.0.0.1&remote.timestamp=1577582403854&revision=1.0.0-SNAPSHOT&side=consumer×tamp=1577582442523, dubbo version: 2.6.6, current host: 127.0.0.1
6十二月 29, 2019 9:20:43 上午 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping register
7信息: Mapped "{[/api/user/queryUserInfoList],methods=[GET]}" onto public org.itstack.demo.rpc.res.UserRes org.itstack.demo.controller.UserController.queryUserInfoList(java.lang.String,java.lang.String,java.lang.String)
8十二月 29, 2019 9:20:43 上午 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter initControllerAdviceCache
9信息: Looking for @ControllerAdvice: org.springframework.context.support.GenericApplicationContext@4f51b3e0: startup date [Sun Dec 29 09:20:41 CST 2019]; root of context hierarchy
10十二月 29, 2019 9:20:43 上午 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter initControllerAdviceCache
11信息: Looking for @ControllerAdvice: org.springframework.context.support.GenericApplicationContext@4f51b3e0: startup date [Sun Dec 29 09:20:41 CST 2019]; root of context hierarchy
12十二月 29, 2019 9:20:44 上午 org.springframework.web.servlet.handler.SimpleUrlHandlerMapping registerHandler
13信息: Mapped URL path [/**] onto handler 'org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler#0'
142019-12-29 09:20:45.157 [main] INFO org.itstack.demo.test.ApiTest[31] -
15测试结果 req:{"name":"豆豆","pageEnd":5,"pageStart":0} res:{"count":1,"list":[{"name":"豆豆","status":1}],"result":{"code":"0000","info":"成功"}}
16十二月 29, 2019 9:20:45 上午 org.springframework.context.support.GenericApplicationContext doClose
17信息: Closing org.springframework.context.support.GenericApplicationContext@4f51b3e0: startup date [Sun Dec 29 09:20:41 CST 2019]; root of context hierarchy
182019-12-29 09:20:45.159 [DubboShutdownHook] INFO com.alibaba.dubbo.config.DubboShutdownHook[56] - [DUBBO] Run shutdown hook now., dubbo version: 2.6.6, current host: 127.0.0.1
192019-12-29 09:20:45.160 [Thread-1] INFO com.alibaba.dubbo.registry.support.AbstractRegistryFactory[64] - [DUBBO] Close all registries [multicast://224.5.6.7:1234/com.alibaba.dubbo.registry.RegistryService?application=itstack-demo-frame-dcs-test&dubbo=2.0.2&interface=com.alibaba.dubbo.registry.RegistryService&pid=14416×tamp=1577582442547], dubbo version: 2.6.6, current host: 127.0.0.1
20
综上总结
- 平常这种框架的开发可能也很多,但是往往不总结沉淀下来,也就没有办法从全局去学习。学会的只是开发功能,肯定满足不了你的成长快乐!
- 当DDD集合dubbo后,我也想过试图将四层分为四个模块开发。但是每一层衔接定义实现,会导致循环引用,除非改变整体的结构。但如果改变了就不太符合目前的DDD了,又是贫血模型。
- 架构的学习还是需要从多种架构模式中吸取营养,好的架构会让整个开发都变得舒服顺畅,如果总是在一坨一坨的东西里开发,日久生情就危险了!(巧克力味的shi,和shi味的巧克力你吃哪个)