在DDD领域驱动设计中应用层的Command和Query是CQRS命令查询职责分离模式的核心概念它们的设计讲究直接影响代码的可维护性和业务表达的清晰度。下面系统梳理一下关键要点。核心理念为什么要在应用层区分Command和QueryCQRS的核心原则来自Bertrand Meyer提出的命令查询分离原则CQSCommand命令一个方法如果修改了系统状态它就是Command。它不应该返回业务数据只返回操作结果成功/失败/异步已接收。Query查询一个方法如果返回了数据它就是Query。它不应该通过直接或间接的手段修改系统状态。在DDD应用层中这不仅仅是理论原则更落地为具体的对象设计规范。Command对象的设计讲究Command代表调用方明确想让系统执行的操作指令预期会对系统产生副作用写操作。设计要点语义化命名Command的名字必须能清晰表达意图而非动作。例如PlaceOrderCommand下单指令比CreateOrderCommand创建订单更有业务含义。封装操作所需的全部参数把散落的多个参数收拢到一个Command对象中避免接口签名膨胀。不包含业务逻辑Command本身是Value Object只携带数据不包含规则。可以包含校验逻辑Command上可以做基础的数据格式校验如非空、范围但业务规则校验应交给领域层。代码示例// 好的设计语义清晰参数内聚publicclassPlaceOrderCommand{NotNullprivateLonguserId;NotNullprivateLongitemId;Min(1)privateIntegerquantity;privateStringchannel;// 渠道}// 不好的设计参数散落无语义ResultOrderDOcheckout(LonguserId,LongitemId,Integerquantity,Stringchannel);Query对象的设计讲究Query代表调用方明确想查询的数据需求预期对系统完全不产生副作用只读操作。设计要点封装查询条件包括过滤条件、分页参数、排序规则等统一收拢到一个Query对象中。命名体现查询意图如OrderListQuery、UserDetailQuery让人一看就知道查什么。可以省略的情况当仅通过单一ID查询时可以不创建Query对象直接传ID即可。代码示例// 好的设计publicclassOrderListQuery{privateLongsellerId;privateLongitemId;privateOrderStatusEnumstatus;privateintcurrentPage;privateintpageSize;}// 不好的设计一个查询条件一个方法接口膨胀ListOrderDOqueryByItemId(LongitemId);ListOrderDOqueryBySellerId(LongsellerId,intpage,intsize);ListOrderDOqueryByStatus(OrderStatusEnumstatus);Command vs Query vs DTO 的区别这是很多人容易混淆的地方对比维度Command / QueryDTO角色应用服务的输入应用服务的输出语义携带明确的意图纯粹的数据容器是否包含逻辑可包含基础校验不包含任何逻辑贫血对象数量特征理论上可以无限多每个代表不同意图通常与展示场景对应应用层服务的设计规范基于Command和Query的区分应用服务ApplicationService的接口设计应遵循以下规范publicinterfaceOrderApplicationService{// Command写操作入参是Command对象OrderDTOplaceOrder(ValidPlaceOrderCommandcommand);// Command写操作voidcancelOrder(ValidCancelOrderCommandcommand);// Query读操作入参是Query对象ListOrderDTOqueryOrders(OrderListQueryquery);// Query单一ID查询可以省略Query对象OrderDTOgetOrder(LongorderId);}关键规则应用服务的入参只能是一个Command、Query或Event对象单一ID查询除外应用服务本身不包含业务逻辑只负责流程编排Command方法不应返回业务数据Query方法不应修改状态为什么要这样设计解决接口膨胀问题传统写法中每增加一个查询条件就要新增一个方法导致接口无限膨胀。用Query对象封装后新增查询条件只需在Query对象中加字段接口签名不变。提升代码的语义表达力placeOrder(PlaceOrderCommand cmd)比createOrder(Long userId, Long itemId, Integer quantity)更能表达业务意图。代码即文档。为CQRS架构打下基础当系统复杂度上升时Command和Query的分离可以自然演进为物理上的读写分离Command侧走领域模型通过聚合根处理业务逻辑保证ACIDQuery侧绕过领域模型直接查询为读取优化的数据源甚至可以用Elasticsearch等异构存储三种CQRS架构方案的选择根据业务复杂度可以选择不同层次的CQRS实现方案存储适用场景复杂度共享存储/共享模型同一数据库、同一模型简单业务读写需求差异不大低共享存储/分离模型同一数据库、不同模型中等复杂度查询需要扁平化数据中分离存储/分离模型不同数据库如MySQL ES高复杂度读写性能要求差异大高实际项目中大多数团队会优先落地读写分离版CQRS方案2而不是上Event Sourcing全家桶。如果你的目标是让系统边界清晰、查询性能更好、代码职责更干净从读写分离开始通常是更稳妥的选择。总结应用层Command和Query的核心讲究可以浓缩为三句话Command表达意图封装写操作的全部参数语义清晰代表我要系统做什么Query表达需求封装读操作的全部条件代表我要看什么数据两者严格隔离Command不改返回值Query不改状态这条边界一旦守住系统就能自然地演进到CQRS架构