DDD+CQRS架構如何優雅實現查詢

何爲DDD

DDD全稱爲Domain-Driven Design,它是一種軟件設計過程中的一套技術工作集,所以很多人稱它爲DDD-Lite,它包含了實體,值對象,領域,領域服務,領域事件,限界上下文等等諸多概念,就不在本文中給大家做介紹了,之後我會出一篇關於DDD介紹的文章。

何爲CQRS

CQRS全稱爲Cammand-Query Responsibility Segregation,它是一種將緊縮對象(或組件)設計原則和命令-查詢分離應用在架構模式中的結果。
在對象層面,這意味着:
1.如果一個方法修改了對象的狀態,該方法便是一個命令(Cammand),它不應該返回數據,在Java中,這樣的方法應該聲明爲void。
2.如果一個方法返回了數據,該方法便是一個查詢(Query),此時它不應該通過直接的或者間接的手段修改對象狀態,在Java中,這樣的方法應該返回的數據類型聲明。

上述的指導原則可謂非常的直接明瞭。下面是CQRS的示意圖。
CQRS命令與查詢示意圖
從上圖我們可以看出CQRS框架的好處是在於查詢與命令的分離,對於從資源庫查詢跨域多個聚合類型與實例數據時,能夠有效的降低組織數據的複雜性。
比如在應用端存在一個這樣的查詢,在訂單列表頁面根據客戶姓名查詢訂單並且分頁和按照客戶ID排序。
這是一個很常規的操作,如果不採用CQRS,這個查詢操作需要同時跨越訂單域和客戶域進行組織數據,這樣的查詢操作代碼顯得非常的臃腫。僞代碼如下

//1.通過客戶姓名過濾客戶對象
List<Customer> customers = customerService.findCustomersByCustomerName();
//2.統計客戶ids
List<Interger> ids = customers.stream.map(c -> c.id).collect(collectos.toList());
//3.查詢訂單通過ids
page<Order> orderPage = orderRepository.findOrderByCustomerIds(ids,pageable);

從上述代碼可以看出,隨着條件的增加,聚合的增加,代碼複雜度會越來越高。可讀性也越來越差。
如果是採用CQRS的模式,此查詢我們可以同時跨越訂單域和客戶域來組織數據,上述操作我們只要通過一條sql語句就可實現。
如果採用JPA+hibernate持久化數據方案,在查詢數據時需要些原生sql或者hql的形式,這種形式代碼的可讀性,可維護性很差。但是JPA對於DDD的支持是非常良好的,在命令操作中,採用DDD遠遠比採用mybatis等方案要優雅的多。所以我們既希望在查詢時可以像mybatis一樣組織查詢數據,也希望在命令操作時可以採用JPA的方式來持久化數據,發佈領域事件等。

JPA+TkMybatis實現CQRS

maven配置

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
            <version>2.1.5</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        ....

application.propertis配置

# database Config
spring.datasource.driverClassName = com.mysql.cj.jdbc.Driver
spring.datasource.url = jdbc:mysql://localhost:3306/jpa_batis?useUnicode=true&characterEncoding=utf-8
spring.datasource.username = ***
spring.datasource.password = ***

# Spring Data JPA Config
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.show-sql=true

# Mybatis Config
logging.level.com.orange.spring.jpabatis.mapper=debug
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.mapper-locations=classpath:mapping/*Mapper.xml

啓動入口函數

@SpringBootApplication
//這裏要導入tkmabatis的包,不要導錯包了
@MapperScan(value = {"com.orange.spring.jpabatis.mapper"})
public class SpringJpabatisApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringJpabatisApplication.class, args);
    }

}

接下來應用中訂單模塊可以同時有OrderRepository與OrderMapper兩個倉儲對象了,我們使用OrderRepository來操作對象持久化,OrderMapper來組織查詢數據。
測試

@RunWith(SpringRunner.class)
@Transactional
@Rollback
@SpringBootTest
public class SpringJpabatisApplicationTests {

    @Autowired
    private CompanyRepository companyRepository;

    @Autowired
    private CompanyMapper companyMapper;

    @Autowired
    private EmployeeRepository employeeRepository;

    @Test
    public void test1() {
        //jpa 查詢
        Optional<CompanyEntity> optionalCompanyEntity = companyRepository.findFirstByCompanyName("雲集網絡");
        Assert.assertTrue(optionalCompanyEntity.isPresent());
        //jpa保存
        CompanyEntity companyEntity = new CompanyEntity();
        companyEntity.setCompanyName("雲影網絡");
        Set<EmployeeEntity> set = new HashSet<>();
        EmployeeEntity employeeEntity1 = new EmployeeEntity();
        employeeEntity1.setEmployeeNo("23425");
        employeeEntity1.setEmployeeName("劉備");
        EmployeeEntity employeeEntity2 = new EmployeeEntity();
        employeeEntity2.setEmployeeNo("234256");
        employeeEntity2.setEmployeeName("劉備");
        set.add(employeeEntity1);
        set.add(employeeEntity2);
        companyEntity.setEmployeeEntitySet(set);
        companyRepository.save(companyEntity);
        //jpa查詢所有
        List<CompanyEntity> companyEntityList2 = companyRepository.findAll();

        //tkMybatis查詢
        List<CompanyEntity> list1 = companyMapper.getCompanyList();
        Assert.assertTrue(list1.size() > 0);
        CompanyDO companyDO = companyMapper.selectByPrimaryKey(1L);
        Assert.assertTrue(companyDO != null && companyDO.getCompanyName() != null);
        //tKMybatis保存  CQRS模型中不會用TkMybatis做保存操作
        CompanyDO company = new CompanyDO();
        company.setCompanyName("雲榮網絡");
        companyMapper.insert(company);
    }
 }

測試結果顯示JPA與TkMybatis的所有操作均能正常使用,並且兩者在同一個事物中。後續我會出一篇文章講關於JPA與TkMybatis事物原理。
源碼鏈接:https://github.com/dscxieyong/spring-jpa-mybatis.git

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章