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

做者:小傅哥
博客:https://bugstack.cnhtml

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

1、前言

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

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

2、開發目標

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

3、服務架構

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

  • 應用層{application}mysql

    • 應用服務位於應用層。用來表述應用和用戶行爲,負責服務的組合、編排和轉發,負責處理業務用例的執行順序以及結果的拼裝。
    • 應用層的服務包括應用服務和領域事件相關服務。
    • 應用服務可對微服務內的領域服務以及微服務外的應用服務進行組合和編排,或者對基礎層如文件、緩存等數據直接操做造成應用服務,對外提供粗粒度的服務。
    • 領域事件服務包括兩類:領域事件的發佈和訂閱。經過事件總線和消息隊列實現異步數據傳輸,實現微服務之間的解耦。
  • 領域層{domain}web

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

    • 基礎服務位於基礎層。爲各層提供資源服務(如數據庫、緩存等),實現各層的解耦,下降外部資源變化對業務邏輯的影響。
    • 基礎服務主要爲倉儲服務,經過依賴反轉的方式爲各層提供基礎資源服務,領域服務和應用服務調用倉儲服務接口,利用倉儲實現持久化數據對象或直接訪問基礎資源。
  • 接口層{interfaces}spring

    • 接口服務位於用戶接口層,用於處理用戶發送的Restful請求和解析用戶輸入的配置文件等,並將信息傳遞給應用層。

4、開發環境

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

5、代碼示例

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落地sql

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);
    }

}

6、綜上總結

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

7、推薦閱讀

相關文章
相關標籤/搜索