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

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
CQRS(Command Query Responsibility Segregation)是一种架构模式,用于分离应用程序的读取和写入操作。它的基本概念是将应用程序的命令(Command)和查询Query)分开处理,分别使用不同的模型进行处理。 在CQRS架构中,写操作使用命令模型(Command Model),负责处理应用程序的状态更新和业务逻辑。而读操作使用查询模型(Query Model),负责处理应用程序的数据查询和读取操作。这两个模型可以根据各自的需求进行优化和设计。 CQRS架构的主要目标是解决传统的CRUD(Create, Read, Update, Delete)模式在复杂领域中的不足。它可以带来以下好处: 1. 灵活性:CQRS允许读写操作使用不同的模型,可以针对每个操作类型进行优化,提高性能和可扩展性。 2. 扩展性:由于读写操作分离,可以根据需求独立扩展读和写的部分,避免了单一数据模型的性能瓶颈。 3. 高效性:通过针对特定查询进行优化,可以提高查询性能,满足更高的并发需求。 4. 松耦合:读写操作分离降低了系统各部分之间的耦合度,使得系统更易于维护和演化。 CQRS架构适用于一些场景,如: 1. 高并发读写:当应用程序需要处理大量的读写操作,并需要高性能和可扩展性时,CQRS可以将读写操作分离,并针对每个操作进行优化。 2. 复杂领域逻辑:当应用程序的业务逻辑非常复杂,并且读写操作之间存在较大的差异时,CQRS可以更好地组织和管理业务逻辑。 3. 实时报表和分析:当应用程序需要提供实时的报表和分析功能时,CQRS可以通过优化查询模型提供更好的性能和用户体验。 需要注意的是,CQRS架构增加了系统的复杂性,适用于复杂度较高的场景,对于简单的应用程序可能带来不必要的开销。因此,在选择采用CQRS架构时需要权衡利弊并结合实际需求进行决策。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值