A:
JPA Specification(Infrastructure 层):动态数据库查询 + DDD Specification(Domain 层):纯业务规则校验 ,这是我项目里面这样使用specs 的,不过ContentAggregateRepository.java public List<ContentAggregate> findByAuthor(UserId authorId) 这里用到了Domain ContentSpecifications的精确筛选;但是在ActivityAggregateRepository却没有用到ActivitySpecifications,是不是哪里代码写错了啊
Q:
📊 现状分析
✅ ContentAggregateRepository(正确示范)
@Override
public List<ContentAggregate> findByAuthor(UserId authorId) {
// Step 1: 使用 JPA Specification 在数据库层面粗筛
Specification<PostPO> dbSpec = PostPoSpecs.isNotDeleted()
.and(PostPoSpecs.byAuthor(authorId.getValue()))
.and(PostPoSpecs.isPublished());
List<PostPO> postPOs = postRepository.findAll(dbSpec);
// Step 2: 转换为聚合根并使用 Domain Specification 精筛
List<ContentAggregate> aggregates = postPOs.stream()
.map(contentConverter::poToAggregate)
.filter(ContentSpecifications.authorPublished(authorId.getValue())::isSatisfiedBy) // ✅ 使用 Domain Spec
.collect(Collectors.toList());
return aggregates;
}设计思路:
✅ 两层过滤:JPA Specification(粗筛) + Domain Specification(精筛)
✅ 职责清晰:数据库过滤 + 业务规则校验
❌ ActivityAggregateRepository(缺少 Domain Specification)
@Override
public ActivitySearchResult searchActivities(
ActivitySearchCriteria criteria,
int page,
int size) {
// ✅ Step 1: JPA Specification(数据库过滤)
Specification<ActivityPO> spec = ActivityPoSpecs.bySearchCriteria(criteria, now);
Page<ActivityPO> poPage = activityJpaRepository.findAll(spec, pageable);
// ✅ Step 2: PO → Aggregate 转换
List<ActivityAggregate> activities = poPage.getContent().stream()
.map(activityConverter::poToAggregate)
.toList();
// ❌ 缺少 Step 3: Domain Specification 精筛(业务规则校验)
return ActivitySearchResult.builder()...build();
}问题:
❌ 只有 JPA Specification,没有 Domain Specification
❌ 缺少业务规则的二次校验
❌ 没有充分利用 ActivitySpecifications
🔧 优化方案
方案 1:在 searchActivities 中增加 Domain Specification 精筛
适用场景:需要对搜索结果进行业务规则校验
@Override
public ActivitySearchResult searchActivities(
ActivitySearchCriteria criteria,
int page,
int size) {
LocalDateTime now = LocalDateTime.now();
// Step 1: JPA Specification(数据库层面粗筛)
Specification<ActivityPO> spec = ActivityPoSpecs.bySearchCriteria(criteria, now);
Sort sort = ActivityPoSpecs.buildSort(criteria);
Pageable pageable = PageRequest.of(page, size, sort);
Page<ActivityPO> poPage = activityJpaRepository.findAll(spec, pageable);
// Step 2: PO → Aggregate 转换
List<ActivityAggregate> activities = poPage.getContent().stream()
.map(activityConverter::poToAggregate)
.toList();
// Step 3: 【新增】Domain Specification 精筛(业务规则校验)
// 根据业务需求选择合适的 Domain Specification
ISpecification<ActivityAggregate> domainSpec = buildDomainSpecification(criteria);
List<ActivityAggregate> filteredActivities = activities.stream()
.filter(domainSpec::isSatisfiedBy) // 使用 Domain Spec 精筛
.toList();
// Step 4: 构建结果
return ActivitySearchResult.builder()
.activities(filteredActivities)
.total(poPage.getTotalElements()) // 注意:这里的 total 是数据库查询的总数
.page(page)
.size(size)
.build();
}
/**
* 根据搜索条件构建 Domain Specification
* <p>
* 用于业务规则的二次校验
*/
private ISpecification<ActivityAggregate> buildDomainSpecification(ActivitySearchCriteria criteria) {
// 基础规格:已发布
ISpecification<ActivityAggregate> spec = ActivitySpecifications.isPublished();
// 如果需要有名额的活动
if (Boolean.TRUE.equals(criteria.getHasAvailableSlots())) {
spec = spec.and(ActivitySpecifications.hasAvailableSlots());
}
// 如果需要可报名的活动(更严格的业务规则)
// 例如:已发布 + 有名额 + 未开始 + 未过报名截止时间
if (Boolean.TRUE.equals(criteria.getOnlyRegistrable())) {
spec = spec.and(ActivitySpecifications.isRegistrable());
}
// 如果需要免费活动
if (Boolean.TRUE.equals(criteria.getOnlyFree())) {
spec = spec.and(ActivitySpecifications.isFree());
}
return spec;
}方案 2:在特定查询方法中使用 Domain Specification
适用场景:某些查询需要复杂的业务规则校验
/**
* 查询推荐活动(使用 Domain Specification 精筛)
* <p>
* 业务规则:
* - 已发布 + 有名额 + 可报名 + 未满员 + 评分高
*/
@Override
public List<ActivityAggregate> findRecommendedActivities(String city, int limit) {
log.debug("查询推荐活动 - 城市: {}, 限制: {}", city, limit);
try {
LocalDateTime now = LocalDateTime.now();
// Step 1: JPA Specification(数据库层面粗筛)
Specification<ActivityPO> dbSpec = ActivityPoSpecs.isNotDeleted()
.and(ActivityPoSpecs.isPublished())
.and(ActivityPoSpecs.inCity(city))
.and(ActivityPoSpecs.hasAvailableSlots());
List<ActivityPO> activityPOs = activityJpaRepository.findAll(dbSpec);
// Step 2: 转换为聚合根
List<ActivityAggregate> aggregates = activityPOs.stream()
.map(activityConverter::poToAggregate)
.toList();
// Step 3: 【使用 Domain Specification 精筛】
// 业务规则:可报名 + 高质量
ISpecification<ActivityAggregate> domainSpec = ActivitySpecifications.isRegistrable()
.and(ActivitySpecifications.hasAvailableSlots());
List<ActivityAggregate> recommendedActivities = aggregates.stream()
.filter(domainSpec::isSatisfiedBy) // Domain 层业务规则过滤
.limit(limit)
.toList();
log.debug("找到 {} 个推荐活动", recommendedActivities.size());
return recommendedActivities;
} catch (Exception e) {
log.error("查询推荐活动失败 - 城市: {}", city, e);
throw new ActivityRepositoryException("查询推荐活动失败", e);
}
}方案 3:在 findBySpecification 中已经实现(保持现状)
你的 ActivityAggregateRepository 已经实现了 findBySpecification 方法:
@Override
public List<ActivityAggregate> findBySpecification(ISpecification<ActivityAggregate> specification) {
// Step 1: 数据库层面基础过滤
Specification<ActivityPO> baseSpec = ActivityPoSpecs.isNotDeleted();
List<ActivityPO> allActivities = activityJpaRepository.findAll(baseSpec);
// Step 2: Domain Specification 精筛
List<ActivityAggregate> aggregates = allActivities.stream()
.map(activityConverter::poToAggregate)
.filter(specification::isSatisfiedBy) // ✅ 使用 Domain Spec
.toList();
return aggregates;
}这个方法已经正确使用了 Domain Specification!
🎯 建议的最佳实践
两种使用模式
模式 1:简单查询(只用 JPA Specification)
适用场景:查询条件简单,不需要复杂业务规则校验
// 示例:按城市查询活动
@Override
public List<ActivityAggregate> findByCity(String city) {
Specification<ActivityPO> spec = ActivityPoSpecs.isNotDeleted()
.and(ActivityPoSpecs.inCity(city));
List<ActivityPO> activityPOs = activityJpaRepository.findAll(spec);
return activityPOs.stream()
.map(activityConverter::poToAggregate)
.toList();
}模式 2:复杂查询(JPA Spec + Domain Spec)
适用场景:需要业务规则校验、推荐算法、复杂筛选
// 示例:查询推荐活动(需要业务规则校验)
@Override
public List<ActivityAggregate> findRecommendedActivities(String city) {
// Step 1: 数据库层面粗筛
Specification<ActivityPO> dbSpec = ActivityPoSpecs.isNotDeleted()
.and(ActivityPoSpecs.isPublished())
.and(ActivityPoSpecs.inCity(city));
List<ActivityPO> activityPOs = activityJpaRepository.findAll(dbSpec);
// Step 2: 转换
List<ActivityAggregate> aggregates = activityPOs.stream()
.map(activityConverter::poToAggregate)
.toList();
// Step 3: Domain Specification 精筛(业务规则)
ISpecification<ActivityAggregate> domainSpec = ActivitySpecifications.isRegistrable()
.and(ActivitySpecifications.hasAvailableSlots());
return aggregates.stream()
.filter(domainSpec::isSatisfiedBy)
.toList();
}📋 总结
你的项目现状
| Repository | JPA Specification | Domain Specification | 状态 |
|---|---|---|---|
| ContentAggregateRepository | ✅ 使用 | ✅ 使用(精筛) | ✅ 正确 |
| ActivityAggregateRepository | ✅ 使用 | ⚠️ 部分使用(只在 findBySpecification 中) | ⚠️ 可优化 |
建议
✅ 保持 findBySpecification 方法(已经正确使用 Domain Spec)
⚠️ 考虑在 searchActivities 中增加 Domain Spec 精筛(如果需要业务规则校验)
✅ 在推荐、筛选等场景中使用 Domain Spec(参考 ContentAggregateRepository)
何时使用 Domain Specification?
✅ 需要复杂业务规则校验(如:可报名、推荐算法)
✅ 需要在内存中进行二次筛选(如:根据用户画像推荐)
✅ 需要组合多个业务规则(如:高质量内容 = 推荐 + 热门 + 活跃)
❌ 简单的数据库查询(直接用 JPA Spec 即可)
你的代码没有写错,只是 ActivityAggregateRepository 可以参考 ContentAggregateRepository 的模式,在某些场景下增加 Domain Specification 的使用! 🎯