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

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