架構框架搭建(二)《Dubbo分佈式領域驅動設計架構框體》

微信公衆號:bugstack蟲洞棧 | bugstack.cn
沉澱、分享、成長,專一於原創專題案例,以最易學習編程的方式分享知識,讓本身和他人都能有所收穫。目前已完成的專題有;Netty4.x實戰專題案例、用Java實現JVM、基於JavaAgent的全鏈路監控、手寫RPC框架、架構設計專題案例[Ing]等。
你用劍🗡、我用刀🔪,好的代碼都很燒😏,望你不吝出招💨!html

前言介紹

隨着項目需求的變化,或者說從小公司跳槽了互聯網。需求變化了、承載的用戶體量增多了,總體系統的架構也隨着改變了。就像你作畢業設計的時候,可能只爲了完成功能便可,一個單體的MVC結構足可讓你畢業。但!如今你長大了,爲了能夠承載幾百、幾千、幾億的用戶體量,你開始發現原來還有這麼多套路在裏面。對於愛學習的人,確定蠢蠢欲動的想研究研究了(不研究也寫不了代碼,能抗住揍不)!java

在咱們的技術棧中RPC框架有;Dubbo、Motan、Tars、gRPC等等,並且每一個公司可能還有本身的RPC,若是想深刻了解那麼能夠參照《手寫RPC框架第三章《RPC中間件》》。對於一個程序猿來講仍是要從多家的框架中吸收養分,精進技術。mysql

本章節咱們主要將Dubbo技術與DDD的架構融合,搭建出分佈式架構體系。隨着一點點的深刻,本案例沒有引入過多的過技術棧,好比;Mq、ES、分庫分表等,這些會隨着後續的章節陸續完善。當前章節儘量簡單的體現核心內容;web

  • 分佈式框架下父類文件定義,統一版本標準
  • RPC框架須要接口信息描述性Jar對外發布,結合領域驅動設計進行定義
  • 嘗試使用Dubbo的廣播模式,進行發佈和使用,簡化系統調試
  • 感覺領域驅動設計的魅力,能落地纔有機會使用

好! 那麼,最後在開始以前,再問一個小問題。redis

實現了Serializable接口的類,怎麼自動生成serialVersionUID(總不能本身亂編呀)spring

答:其實在Idea中已經提供了這樣自動生成功能,只須要配置上便可;File -> Settings -> Editor -> Inspections -> 搜索 Serialization issues ,找到 Serializable class without 'serialVersionUID' ->打上勾,Apply->OK 效果如圖;sql

微信公衆號:bugstack蟲洞棧 & 配置方式

工程環境

  1. JDK1.8
  2. Maven 3.2.3
  3. Spring 4.3.24.RELEASE + Mybatis 3.3.0
  4. Mysql 5.6 + dbcp2
  5. Dubbo 2.6.6
  6. Redis 2.9.0

工程模型

itstack-demo-frame-parent 父類工程數據庫

itstack-demo-frame-parent
├── itstack-demo-frame-parent
│	├── src
│	│	└── main
│	│	    └── java
│	│	        └── org.itstack.demo.frame.common
│	│	            ├── constants	
│	│	            │	└── Constants.java	
│	│	            └── domain
│	│	             	├── PageRequest.java
│	│	             	└── Result.java
│	└──	pom.xml
└──	pom.xml
複製代碼

itstack-demo-frame-dcs 分佈式框架apache

itstack-demo-frame-dcs
├──	itstack-demo-frame-dcs-ddd
│	└── src
│		├── main
│		│   ├── java
│		│   │   └── org.itstack.demo
│		│   │       ├── application	
│		│   │       │	└── UserService.java	
│		│   │       ├── domain
│		│   │       │	├── model
│		│   │       │	│   ├── aggregates
│		│   │       │	│   │   └── UserInfoCollect.java
│		│   │       │	│   ├── req
│		│   │       │	│   │   └── UserReq.java		
│		│   │       │	│   └── vo
│		│   │       │	│       └── UserInfo.java	
│		│   │       │	├── repository
│		│   │       │	│   └── IUserRepository.java	
│		│   │       │	└── service	
│		│   │       │	    └── UserServiceImpl.java	
│		│   │       ├── infrastructure
│		│   │       │	├── common
│		│   │       │	│   ├── EasyResult.java
│		│   │       │	│   └── PageRequest.java
│		│   │       │	├── dao
│		│   │       │	│   └── IUserDao.java	
│		│   │       │	├── po
│		│   │       │	│   └── User.java		
│		│   │       │	└── repository
│		│   │       │	    └── UserRepository.java	
│		│   │       └── interfaces
│		│   │        	└── UserController.java
│		│   ├── resources	
│		│   │   ├── mapper
│		│   │   ├── props	
│		│   │   ├── spring
│		│   │   ├── logback.xml
│		│   │   ├── mybatis-config.xml
│		│   │   └── spring-config.xml
│		│   └── webapp
│		│       ├── page
│		│       ├── res
│		│       ├── WEB-INF
│		│       ├── index.html
│		│       └── res_layui.html
│		└── test
│			└── java
│				└── org.itstack.demo.test
│					└── ApiTest.java
│					
└── itstack-demo-frame-dcs-rpc
	└── src
		└── main
		    └── java
		        └── org.itstack.demo.rpc
		            ├── dto	
		            │	└── UserDto.java
		            ├── req	
		            │	└── UserReq.java
		            ├── res	
		            │	└── UserRes.java	
		            └── IUserRpc.java					
複製代碼

itstack-demo-frame-dcs-test RPC測試工程編程

itstack-demo-frame-dcs-test
└── src
    ├── main
    │   ├── java
    │   │   └── org.itstack.demo.interfaces
    │   │       └── UserController.java
    │   ├── resources		
    │   │   ├── spring
    │   │   ├── logback.xml
    │   │   └── spring-config.xml
    │   └── webapp
    │       ├── page
    │       ├── res
    │       ├── WEB-INF
    │       ├── index.html
    │       └── res_layui.html
    └── test
         └── java
             └── org.itstack.demo.test
                 └── ApiTest.java
複製代碼

如下對工程模塊進行介紹,總體源碼獲取,能夠關注公衆號:bugstack蟲洞棧,回覆:框架搭建

1、父類工程

  • 父類工程若是沒有定義也是能夠工做的,可是隨着系統量的增長和複雜度提升後,會愈來愈難以維護各個版本和升級,因此須要;
  • 定義通用common,使各個服務工程都有統一的;異常枚舉、分頁類、返回對象等
  • 定義POM配置,協調各個組件版本保持統一;減小jar衝突、維護統一版本、方便升級
<groupId>org.itstack.demo</groupId>
<artifactId>itstack-demo-frame-parent</artifactId>
<version>1.0.0-RELEASE</version>
<modules>
    <module>itstack-demo-frame-common</module>
</modules>
<packaging>pom</packaging>
<name>itstack-demo-frame-parent</name>
<description>itstack Demo Project Dependencies</description>
<properties>
    <!-- Base -->
    <jdk.version>1.8</jdk.version>
    <sourceEncoding>UTF-8</sourceEncoding>
    <!-- Spring -->
    <spring.version>4.3.24.RELEASE</spring.version>
    <servlet-api.version>2.5</servlet-api.version>
    <spring.redis.version>1.8.4.RELEASE</spring.redis.version>
    <!-- DB:mysql、mybatis-->
    <mysql.version>5.1.20</mysql.version>
    <mybatis.version>3.3.0</mybatis.version>
    <mybatis_spring.version>1.2.3</mybatis_spring.version>
    <!-- JSON -->
    <fastjson.version>1.2.60</fastjson.version>
    <jackson.version>2.5.4</jackson.version>
    <!-- Junit -->
    <junit.version>4.12</junit.version>
    <!-- Common -->
    <commons-dbcp2.version>2.6.0</commons-dbcp2.version>
    <commons-lang3.version>3.8.1</commons-lang3.version>
    <!-- 日誌 -->
    <slf4j.version>1.7.7</slf4j.version>
    <logback.version>1.0.9</logback.version>
    <!-- 其餘服務 -->
    <dubbo.version>2.6.6</dubbo.version>
    <zookeeper.version>3.4.14</zookeeper.version>
    <netty.version>4.1.36.Final</netty.version>
    <redis.version>2.9.0</redis.version>
    <scheduler.version>2.3.2</scheduler.version>
</properties>
複製代碼

2、分佈式框架

  • Dubbo結合領域驅動設計,由RPC定義接口描述信息,單獨出一個模塊便於外部調用方進行引用
  • 領域驅動設計方面知識已經在https://bugstack.cn提供不少思路,能夠參考
  • 總體架構模型功能定義以下;

微信公衆號:bugstack蟲洞棧 & 分佈式框架功能定義

application應用層

應用層是比較薄的一層,不作具體邏輯開發。本工程裏只包括服務的定義,具體邏輯有領域層實現。

UserService.java & 服務定義

public interface UserService {

    UserInfoCollect queryUserInfoList(UserReq req);

}
複製代碼

domain領域層

領域層是整個工程的核心服務層,這裏負責處理具體的核心功能,完成領域服務。domain下能夠有多個領域,每一個領域裏包括;聚合、請求對象、業務對象、倉儲、服務。

UserServiceImpl.java & 服務實現

@Service("userService")
public class UserServiceImpl implements UserService {

    @Resource(name = "userRepository")
    private IUserRepository userRepository;

    @Override
    public UserInfoCollect queryUserInfoList(UserReq req) {
        return userRepository.queryUserInfoList(req);
    }

}
複製代碼

IUserRepository.java & 倉庫定義

public interface IUserRepository {

    UserInfoCollect queryUserInfoList(UserReq req);

}
複製代碼

infrastructure基礎層

實現領域層倉儲定義,數據庫操做爲非業務屬性的功能操做,在倉儲實現層進行組合裝配DAO&Redis&Cache等。

UserDBRepository.java & 倉庫實現

@Repository("userDBRepository")
public class UserDBRepository implements IUserRepository {

    @Resource
    private IUserDao userDao;
    @Resource
    private Redis redis;

    @Override
    public UserInfoCollect queryUserInfoList(UserReq req) {
        Long count = userDao.queryUserInfoCount(req);
        List<User> userList = userDao.queryUserInfoList(req);
        List<UserInfo> userInfoList = new ArrayList<>();
        userList.forEach(user -> {
            UserInfo userInfo = new UserInfo();
            userInfo.setUserId(user.getId());
            userInfo.setName(user.getName());
            userInfo.setAge(user.getAge());
            userInfo.setAddress(user.getAddress());
            userInfo.setEntryTime(user.getEntryTime());
            userInfo.setStatus(user.getStatus());
            userInfoList.add(userInfo);
        });
        UserInfoCollect userInfoCollect = new UserInfoCollect(count, userInfoList);
        if (StringUtils.isNoneBlank(req.getName())) {
            redis.set(req.getName(), JSON.toJSONString(userInfoCollect));
        }
        return userInfoCollect;
    }

}
複製代碼

interfaces接口層

  • 實現rpc定義接口對外提供api,目前這一層比較簡單隻須要進行接口使用便可
  • 若是是對外部提供服務接口,那麼可使用DTO方式進行轉換,避免污染到業務類
  • assembler 是對DTO對象的轉換類,能夠封裝的更加精緻一些

UserRpc.java & RPC接口實現

@Service("userRpc")
public class UserRpc implements IUserRpc {

    @Resource
    private UserService userService;

    @Override
    public UserRes queryUserInfoList(UserReq req) {
        UserInfoCollect userInfoCollect = userService.queryUserInfoList(UserAssembler.buildUserReq(req));
        return UserAssembler.buildUserInfoCollect(userInfoCollect);
    }

}
複製代碼

rpc對外提供服務層

服務接口定義,rpc框架須要對外提供接口描述jar包,所以單獨提取出來是最方面處理的。不要讓這一層引用其餘層的邏輯代碼。

IUserRpc.java & 接口定義

public interface IUserRpc {

    UserRes queryUserInfoList(UserReq req);

}

複製代碼

父類配置

這一層是整個工程的最外層POM文件,引入父類的定義配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!-- 框架統一服務定義 -->
    <parent>
        <groupId>org.itstack.demo</groupId>
        <artifactId>itstack-demo-frame-parent</artifactId>
        <version>1.0.0-RELEASE</version>
    </parent>

    <artifactId>itstack-demo-frame-dcs</artifactId>
    <packaging>pom</packaging>
    <version>1.0.0-SNAPSHOT</version>
    
    <modules>
        <module>itstack-demo-frame-dcs-ddd</module>
        <module>itstack-demo-frame-dcs-rpc</module>
    </modules>
	
	...
	
</project>
複製代碼

Dubbo配置信息

  • dubbo 2.6.x 版本可使用廣播方式進行服務暴漏,也就省去了zookeeper註冊中心。對於一些中小服務來言,就更加方便了。
  • 廣播地址:address="multicast://224.5.6.7:1234"
  • Netty服務端的端口:<dubbo:protocol name="dubbo" port="20880"/>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       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">

    <!-- 提供方應用信息,用於計算依賴關係 -->
    <dubbo:application name="itstack-demo-frame-dcs"/>

    <!-- 使用multicast廣播註冊中心暴露服務地址 -->
    <dubbo:registry address="multicast://224.5.6.7:1234"/>

    <!-- 用dubbo協議在20880端口暴露服務 -->
    <dubbo:protocol name="dubbo" port="20880"/>

    <!-- 聲明須要暴露的服務接口 -->
    <dubbo:service interface="org.itstack.demo.rpc.IUserRpc" ref="userRpc"/>

</beans>
複製代碼

數據庫表配置(itstack.sql)

DROP TABLE user;
CREATE 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;
insert 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);
insert 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);
insert 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);
複製代碼

3、RPC測試工程

這一層就很簡單了,添加好dubbo配置,引用RPC接口定義POM,調用服務端接口返回數據便可

pom.xml 引用RPC定義接口

<dependency>
    <groupId>org.itstack.demo</groupId>
    <artifactId>itstack-demo-frame-dcs-rpc</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>
複製代碼

spring-config-dubbo-consumer.xml & dubbo配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       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">

    <!-- 消費方應用名,用於計算依賴關係,不是匹配條件,不要與提供方同樣 -->
    <dubbo:application name="itstack-demo-frame-dcs-test"  />

    <!-- 使用multicast廣播註冊中心暴露發現服務地址 -->
    <dubbo:registry address="multicast://224.5.6.7:1234" />

    <!-- 生成遠程服務代理,能夠和本地bean同樣使用demoService -->
    <dubbo:reference id="userRpc" interface="org.itstack.demo.rpc.IUserRpc" />

</beans>
複製代碼

ApiTest.java & 單元測試類

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-config.xml")
public class ApiTest {

    private Logger logger = LoggerFactory.getLogger(ApiTest.class);

    @Resource
    private IUserRpc userRpc;

    @Test
    public void test_queryUserInfoList() {
        UserReq req = new UserReq();
        req.setName("豆豆");
        req.setPage("1", "5");
        UserRes res = userRpc.queryUserInfoList(req);
        logger.info("\r\n測試結果 req:{} res:{}", JSON.toJSONString(req), JSON.toJSONString(res));
    }

}
複製代碼

測試驗證

  1. 啓動Redis配置服務(能夠下載win版本),由於本案例使用到Redis。
  2. tomcat中啓動itstack-demo-frame-dcs
  3. 啓動單元測試調用itstack-demo-frame-dcs-test
2019-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&timestamp=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&timestamp=1577582403854], dubbo version: 2.6.6, current host: 127.0.0.1
2019-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
2019-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
2019-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&timestamp=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&timestamp=1577582403854], dubbo version: 2.6.6, current host: 127.0.0.1
2019-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&register.ip=127.0.0.1&remote.timestamp=1577582403854&revision=1.0.0-SNAPSHOT&side=consumer&timestamp=1577582442523, dubbo version: 2.6.6, current host: 127.0.0.1
十二月 29, 2019 9:20:43 上午 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping register
信息: 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)
十二月 29, 2019 9:20:43 上午 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter initControllerAdviceCache
信息: Looking for @ControllerAdvice: org.springframework.context.support.GenericApplicationContext@4f51b3e0: startup date [Sun Dec 29 09:20:41 CST 2019]; root of context hierarchy
十二月 29, 2019 9:20:43 上午 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter initControllerAdviceCache
信息: Looking for @ControllerAdvice: org.springframework.context.support.GenericApplicationContext@4f51b3e0: startup date [Sun Dec 29 09:20:41 CST 2019]; root of context hierarchy
十二月 29, 2019 9:20:44 上午 org.springframework.web.servlet.handler.SimpleUrlHandlerMapping registerHandler
信息: Mapped URL path [/**] onto handler 'org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler#0' 2019-12-29 09:20:45.157 [main] INFO org.itstack.demo.test.ApiTest[31] - 測試結果 req:{"name":"豆豆","pageEnd":5,"pageStart":0} res:{"count":1,"list":[{"name":"豆豆","status":1}],"result":{"code":"0000","info":"成功"}} 十二月 29, 2019 9:20:45 上午 org.springframework.context.support.GenericApplicationContext doClose 信息: Closing org.springframework.context.support.GenericApplicationContext@4f51b3e0: startup date [Sun Dec 29 09:20:41 CST 2019]; root of context hierarchy 2019-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 2019-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&timestamp=1577582442547], dubbo version: 2.6.6, current host: 127.0.0.1 複製代碼

綜上總結

  • 日常這種框架的開發可能也不少,可是每每不總結沉澱下來,也就沒有辦法從全局去學習。學會的只是開發功能,確定知足不了你的成長快樂!
  • 當DDD集合dubbo後,我也想過試圖將四層分爲四個模塊開發。可是每一層銜接定義實現,會致使循環引用,除非改變總體的結構。但若是改變了就不太符合目前的DDD了,又是貧血模型。
  • 架構的學習仍是須要從多種架構模式中吸收養分,好的架構會讓整個開發都變得舒服順暢,若是老是在一坨一坨的東西里開發,日久生情就危險了!(巧克力味的shi,和shi味的巧克力你吃哪一個)

微信公衆號:bugstack蟲洞棧,歡迎關注&獲取源碼
相關文章
相關標籤/搜索