You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

仓储层混入领域逻辑如何规避?分层架构下用户查询方案抉择

方案选择与优化思路

一、现有方案对比及推荐

先明确分层架构的核心职责边界:

  • Service层:负责封装业务逻辑,定义“什么是未活跃用户”这类业务规则
  • Repository层:仅处理数据持久层操作,只关心如何从数据库取数据,不理解业务含义

方案1的问题

通用查询方法看似让仓储层“纯净”,但自定义的参数结构({ user: Partial<User>; dateTreshold: Date })会增加维护成本:后续如果有更多复杂查询条件,这个方法的参数会越来越臃肿,还容易出现参数格式不统一的问题,反而降低了扩展性。

方案2的问题

把“未活跃用户”的判定规则(isArchived=true、超过一个月未登录)硬编码在仓储层,违反了单一职责原则。仓储层不该知道业务规则——如果后续业务调整(比如改成两个月未活跃才算),你得改动仓储层代码,而仓储层本应只和数据库交互,和业务逻辑无关。

推荐优化方案

结合两者的优点,让仓储层提供基于数据库原生查询条件的通用查询方法,业务规则完全由Service层组装:

UserRepository.ts

@Injectable()
export class UserRepository {
  constructor(private readonly prisma: PrismaService) { }

  async findAll(where: Prisma.UserWhereInput): Promise<User[]> {
    const users = await this.prisma.user.findMany({ where });
    return users.map(user => new User(user));
  }
}

UserService.ts

@Injectable()
export class UserService {
  constructor(private readonly userRepository: UserRepository) {}
  
  async getInactiveUsers(): Promise<User[]> {
    // 计算一个月前的日期
    const oneMonthAgo = new Date();
    oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1);
    
    // 组装业务规则对应的查询条件
    const queryConditions: Prisma.UserWhereInput = {
      isArchived: true,
      lastVisitedDate: { lt: oneMonthAgo }
    };
    
    return this.userRepository.findAll(queryConditions);
  }
}

这个方案的优势:

  • 仓储层只做数据查询,不涉及任何业务逻辑,职责清晰
  • Service层完全掌控业务规则,后续调整只需要修改Service,不会影响数据访问层
  • 复用Prisma原生的UserWhereInput,不需要自定义复杂的参数结构,降低维护成本

二、其他实现思路

1. 抽离查询对象(Query Object)

如果后续有多个类似的复杂查询,可以把查询条件的组装逻辑抽成独立的查询对象,让Service层更简洁:

// 定义查询对象
class InactiveUserQuery {
  static build(): Prisma.UserWhereInput {
    const oneMonthAgo = new Date();
    oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1);
    
    return {
      isArchived: true,
      lastVisitedDate: { lt: oneMonthAgo }
    };
  }
}

// Service层调用
@Injectable()
export class UserService {
  constructor(private readonly userRepository: UserRepository) {}
  
  async getInactiveUsers(): Promise<User[]> {
    const conditions = InactiveUserQuery.build();
    return this.userRepository.findAll(conditions);
  }
}

这种方式适合查询逻辑复用的场景,能让Service层专注于业务流程,而不是条件组装。

2. 避免内存过滤的误区

别想着把所有用户查出来在Service层过滤——数据量小的时候没问题,但用户量上去后,这种做法会严重影响性能。一定要让数据库做过滤,也就是把条件传给仓储层,让Prisma生成高效的SQL查询。

内容的提问来源于stack exchange,提问作者eugenedrvnk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 20:35:12