Java開發架構篇:初識領域驅動設計DDD落地

小傅哥發表於2020-05-19

作者:小傅哥
部落格:https://bugstack.cn

沉澱、分享、成長,讓自己和他人都能有所收穫!

一、前言

DDD(Domain-Driven Design 領域驅動設計)是由Eric Evans最先提出,目的是對軟體所涉及到的領域進行建模,以應對系統規模過大時引起的軟體複雜性的問題。整個過程大概是這樣的,開發團隊和領域專家一起通過 通用語言(Ubiquitous Language)去理解和消化領域知識,從領域知識中提取和劃分為一個一個的子領域(核心子域,通用子域,支撐子域),並在子領域上建立模型,再重複以上步驟,這樣周而復始,構建出一套符合當前領域的模型。

微信公眾號:bugstack蟲洞棧 | DDD概述

二、開發目標

依靠領域驅動設計的設計思想,通過事件風暴建立領域模型,合理劃分領域邏輯和物理邊界,建立領域物件及服務矩陣和服務架構圖,定義符合DDD分層架構思想的程式碼結構模型,保證業務模型與程式碼模型的一致性。通過上述設計思想、方法和過程,指導團隊按照DDD設計思想完成微服務設計和開發。
1、拒絕泥球小單體、拒絕汙染功能與服務、拒絕一加功能排期一個月
2、架構出高可用極易符合網際網路高速迭代的應用服務
3、物料化、組裝化、可編排的服務,提高人效

三、服務架構

微信公眾號:bugstack蟲洞棧 | 服務架構

  • 應用層{application}

    • 應用服務位於應用層。用來表述應用和使用者行為,負責服務的組合、編排和轉發,負責處理業務用例的執行順序以及結果的拼裝。
    • 應用層的服務包括應用服務和領域事件相關服務。
    • 應用服務可對微服務內的領域服務以及微服務外的應用服務進行組合和編排,或者對基礎層如檔案、快取等資料直接操作形成應用服務,對外提供粗粒度的服務。
    • 領域事件服務包括兩類:領域事件的釋出和訂閱。通過事件匯流排和訊息佇列實現非同步資料傳輸,實現微服務之間的解耦。
  • 領域層{domain}

    • 領域服務位於領域層,為完成領域中跨實體或值物件的操作轉換而封裝的服務,領域服務以與實體和值物件相同的方式參與實施過程。
    • 領域服務對同一個實體的一個或多個方法進行組合和封裝,或對多個不同實體的操作進行組合或編排,對外暴露成領域服務。領域服務封裝了核心的業務邏輯。實體自身的行為在實體類內部實現,向上封裝成領域服務暴露。
    • 為隱藏領域層的業務邏輯實現,所有領域方法和服務等均須通過領域服務對外暴露。
    • 為實現微服務內聚合之間的解耦,原則上禁止跨聚合的領域服務呼叫和跨聚合的資料相互關聯。
  • 基礎層{infrastructrue}

    • 基礎服務位於基礎層。為各層提供資源服務(如資料庫、快取等),實現各層的解耦,降低外部資源變化對業務邏輯的影響。
    • 基礎服務主要為倉儲服務,通過依賴反轉的方式為各層提供基礎資源服務,領域服務和應用服務呼叫倉儲服務介面,利用倉儲實現持久化資料物件或直接訪問基礎資源。
  • 介面層{interfaces}

    • 介面服務位於使用者介面層,用於處理使用者傳送的Restful請求和解析使用者輸入的配置檔案等,並將資訊傳遞給應用層。

四、開發環境

  1. jdk1.8【jdk1.7以下只能部分支援netty】
  2. springboot 2.0.6.RELEASE
  3. idea + maven

五、程式碼示例

itstack-demo-ddd-01
└── src
    ├── main
    │   ├── java
    │   │   └── org.itstack.demo
    │   │       ├── application
    │   │       │    ├── event
    │   │       │    │   └── ApplicationRunner.java    
    │   │       │    └── service
    │   │       │        └── UserService.java    
    │   │       ├── domain
    │   │       │    ├── model
    │   │       │    │   ├── aggregates
    │   │       │    │   │   └── UserRichInfo.java    
    │   │       │    │   └── vo
    │   │       │    │       ├── UserInfo.java    
    │   │       │    │       └── UserSchool.java    
    │   │       │    ├── repository
    │   │       │    │   └── IuserRepository.java    
    │   │       │    └── service
    │   │       │        └── UserServiceImpl.java    
    │   │       ├── infrastructure
    │   │       │    ├── dao
    │   │       │    │   ├── impl
    │   │       │    │   │   └── UserDaoImpl.java    
    │   │       │    │   └── UserDao.java    
    │   │       │    ├── po
    │   │       │    │   └── UserEntity.java    
    │   │       │    ├── repository
    │   │       │    │   ├── mysql
    │   │       │    │   │   └── UserMysqlRepository.java
    │   │       │    │   ├── redis
    │   │       │    │   │   └── UserRedisRepository.java        
    │   │       │    │   └── UserRepository.java    
    │   │       │    └── util
    │   │       │        └── RdisUtil.java
    │   │       ├── interfaces
    │   │       │    ├── dto
    │   │       │    │    └── UserInfoDto.java    
    │   │       │    └── facade
    │   │       │        └── DDDController.java
    │   │       └── DDDApplication.java
    │   ├── resources    
    │   │   └── application.yml
    │   └── webapp    
    │       └── WEB-INF
    │            └── index.jsp    
    └── test
         └── java
             └── org.itstack.demo.test
                 └── ApiTest.java

演示部分重點程式碼塊,完整程式碼下載關注公眾號;bugstack蟲洞棧 | 回覆DDD落地

application/UserService.java | 應用層使用者服務,領域層服務做具體實現
/**
 * 應用層使用者服務
 * 蟲洞棧:https://bugstack.cn
 * 公眾號:bugstack蟲洞棧 | 歡迎關注並獲取更多專題案例原始碼
 * Create by fuzhengwei on @2019
 */
public interface UserService {

    UserRichInfo queryUserInfoById(Long id);

}
domain/repository/IuserRepository.java | 領域層資源庫,由基礎層實現
/**
 * 蟲洞棧:https://bugstack.cn
 * 公眾號:bugstack蟲洞棧 | 歡迎關注並獲取更多專題案例原始碼
 * Create by fuzhengwei on @2019
 */
public interface IUserRepository {

    void save(UserEntity userEntity);

    UserEntity query(Long id);

}
domain/service/UserServiceImpl.java | 應用層實現類,應用層是很薄的一層可以只做服務編排
/**
 * 蟲洞棧:https://bugstack.cn
 * 公眾號:bugstack蟲洞棧 | 歡迎關注並獲取更多專題案例原始碼
 * Create by fuzhengwei on @2019
 */
@Service("userService")
public class UserServiceImpl implements UserService {

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

    @Override
    public UserRichInfo queryUserInfoById(Long id) {
        
        // 查詢資源庫
        UserEntity userEntity = userRepository.query(id);

        UserInfo userInfo = new UserInfo();
        userInfo.setName(userEntity.getName());

        // TODO 查詢學校資訊,外部介面
        UserSchool userSchool_01 = new UserSchool();
        userSchool_01.setSchoolName("振華高階實驗中學");

        UserSchool userSchool_02 = new UserSchool();
        userSchool_02.setSchoolName("東北電力大學");

        List<UserSchool> userSchoolList = new ArrayList<>();
        userSchoolList.add(userSchool_01);
        userSchoolList.add(userSchool_02);

        UserRichInfo userRichInfo = new UserRichInfo();
        userRichInfo.setUserInfo(userInfo);
        userRichInfo.setUserSchoolList(userSchoolList);

        return userRichInfo;
    }

}
infrastructure/po/UserEntity.java | 資料庫物件類
/**
 * 資料庫實體物件;使用者實體
 * 蟲洞棧:https://bugstack.cn
 * 公眾號:bugstack蟲洞棧 | 歡迎關注並獲取更多專題案例原始碼
 * Create by fuzhengwei on @2019
 */
public class UserEntity {

    private Long id;
    private String name;

    get/set ...
}
infrastructrue/repository/UserRepository.java | 領域層定義介面,基礎層資源庫實現
/**
 * 蟲洞棧:https://bugstack.cn
 * 公眾號:bugstack蟲洞棧 | 歡迎關注並獲取更多專題案例原始碼
 * Create by fuzhengwei on @2019
 */
@Repository("userRepository")
public class UserRepository implements IUserRepository {

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

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

    @Override
    public void save(UserEntity userEntity) {
        //儲存到DB
        userMysqlRepository.save(userEntity);

        //儲存到Redis
        userRedisRepository.save(userEntity);
    }

    @Override
    public UserEntity query(Long id) {

        UserEntity userEntityRedis = userRedisRepository.query(id);
        if (null != userEntityRedis) return userEntityRedis;

        UserEntity userEntityMysql = userMysqlRepository.query(id);
        if (null != userEntityMysql){
            //儲存到Redis
            userRedisRepository.save(userEntityMysql);
            return userEntityMysql;
        }

        // 查詢為NULL
        return null;
    }

}
interfaces/dto/UserInfoDto.java | DTO物件類,隔離資料庫類
/**
 * 蟲洞棧:https://bugstack.cn
 * 公眾號:bugstack蟲洞棧 | 歡迎關注並獲取更多專題案例原始碼
 * Create by fuzhengwei on @2019
 */
public class UserInfoDto {

    private Long id;        // ID

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

}
interfaces/facade/DDDController.java | 門面介面
/**
 * 蟲洞棧:https://bugstack.cn
 * 公眾號:bugstack蟲洞棧 | 歡迎關注並獲取更多專題案例原始碼
 * Create by fuzhengwei on @2019
 */
@Controller
public class DDDController {

    @Resource(name = "userService")
    private UserService userService;

    @RequestMapping("/index")
    public String index(Model model) {
        return "index";
    }

    @RequestMapping("/api/user/queryUserInfo")
    @ResponseBody
    public ResponseEntity queryUserInfo(@RequestBody UserInfoDto request) {
        return new ResponseEntity<>(userService.queryUserInfoById(request.getId()), HttpStatus.OK);
    }

}

六、綜上總結

  • 以上基於DDD一個基本入門的結構演示完成,實際開發可以按照此模式進行調整。
  • 目前這個架構分層還不能很好的進行分離,以及層級關係的引用還不利於擴充套件。
  • 後續會持續完善以及可以組合搭建RPC框架等,讓整個架構更利於網際網路開發。

七、推薦閱讀

相關文章