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

RegisterUserUseCase违反SRP的解决方案及控制器调用可行性探讨

注册流程用例的SRP问题与解决方案

问题背景

现有四个业务用例:

  • CreateUser Use Case:处理用户创建逻辑
  • SendEmail Use Case:处理邮件发送逻辑
  • InviteUser Use Case:处理用户邀请逻辑
  • RegisterUser Use Case:协调注册流程,按需调用其他用例

当前RegisterUserUseCase通过构造函数注入另外三个用例的实现,这种设计违反了单一职责原则(SRP),需要明确解决方案,同时确认是否应该在控制器中直接调用这些用例。

当前实现的问题

RegisterUserUseCase现在承担了两个冲突的职责:

  1. 核心的用户注册流程(调用CreateUser创建用户)
  2. 耦合了邮件发送、用户邀请的触发逻辑,直接依赖具体用例的实现

这种设计会导致:

  • 代码臃肿,后续新增注册后的操作(比如发送短信通知)需要直接修改RegisterUserUseCase,违反开闭原则
  • 依赖关系复杂,测试时需要模拟多个用例,维护成本高

解决方案

最合理的方案是采用事件驱动架构,将流程协调逻辑从RegisterUserUseCase中剥离,让每个用例只专注于自身核心职责:

1. 定义领域事件

创建UserRegisteredEvent,用于传递用户注册完成后的相关数据:

// domain/events/UserRegisteredEvent.ts
class UserRegisteredEvent {
  constructor(
    public readonly user: any,
    public readonly registrationData: any
  ) {}
}

2. 重构RegisterUserUseCase

让它只负责核心的用户创建,并在完成后发布注册事件,不再依赖其他用例:

// RegisterUserUseCase.ts
import { CreateUserUseCase } from './CreateUserUseCase';
import { UserRegisteredEvent } from '../domain/events/UserRegisteredEvent';
import { EventPublisher } from '../domain/events/EventPublisher';

class RegisterUserUseCase {
  constructor(
    private createUserUseCase: CreateUserUseCase,
    private eventPublisher: EventPublisher
  ) {}

  async execute(registrationData: any) {
    const user = await this.createUserUseCase.execute(registrationData.user);
    
    // 仅发布事件,不处理后续逻辑
    await this.eventPublisher.publish(new UserRegisteredEvent(user, registrationData));
    
    return user;
  }
}

export { RegisterUserUseCase };

3. 编写事件监听器

将邮件发送、用户邀请逻辑拆分为独立的事件监听器,订阅UserRegisteredEvent并执行对应操作:

邮件发送监听器

// listeners/UserRegisteredSendEmailListener.ts
import { SendEmailUseCase } from '../useCases/SendEmailUseCase';
import { UserRegisteredEvent } from '../domain/events/UserRegisteredEvent';

class UserRegisteredSendEmailListener {
  constructor(private sendEmailUseCase: SendEmailUseCase) {}

  async handle(event: UserRegisteredEvent) {
    const { user, registrationData } = event;
    if (registrationData.sendWelcomeEmail) {
      await this.sendEmailUseCase.execute({
        to: user.email,
        subject: 'Welcome!',
        body: 'Thank you for registering!'
      });
    }
  }
}

export { UserRegisteredSendEmailListener };

用户邀请监听器

// listeners/UserRegisteredInviteUserListener.ts
import { InviteUserUseCase } from '../useCases/InviteUserUseCase';
import { UserRegisteredEvent } from '../domain/events/UserRegisteredEvent';

class UserRegisteredInviteUserListener {
  constructor(private inviteUserUseCase: InviteUserUseCase) {}

  async handle(event: UserRegisteredEvent) {
    const { user, registrationData } = event;
    if (registrationData.invite) {
      await this.inviteUserUseCase.execute({
        userId: user.id,
        ...registrationData.invite
      });
    }
  }
}

export { UserRegisteredInviteUserListener };

4. 实现事件发布器

负责管理事件与监听器的映射,当事件发布时自动触发对应监听器:

// domain/events/EventPublisher.ts
import { UserRegisteredEvent } from './UserRegisteredEvent';
import { UserRegisteredSendEmailListener } from '../../listeners/UserRegisteredSendEmailListener';
import { UserRegisteredInviteUserListener } from '../../listeners/UserRegisteredInviteUserListener';
import { SendEmailUseCase } from '../../useCases/SendEmailUseCase';
import { InviteUserUseCase } from '../../useCases/InviteUserUseCase';

class EventPublisher {
  private listeners: Record<string, any[]> = {
    UserRegisteredEvent: [
      new UserRegisteredSendEmailListener(new SendEmailUseCase()),
      new UserRegisteredInviteUserListener(new InviteUserUseCase())
    ]
  };

  async publish(event: any) {
    const eventType = event.constructor.name;
    const eventListeners = this.listeners[eventType];
    if (eventListeners) {
      for (const listener of eventListeners) {
        await listener.handle(event);
      }
    }
  }
}

export { EventPublisher };

是否应该在控制器中直接调用这些用例?

绝对不可以。控制器的核心职责是处理HTTP请求、参数校验、返回响应,不应该包含业务流程逻辑。如果直接在控制器中调用多个用例:

  • 会让控制器变得臃肿,违反SRP
  • 业务逻辑分散在控制器中,难以复用和维护
  • 后续修改注册流程需要改动控制器,增加维护成本

正确的做法是控制器只调用RegisterUserUseCase的execute方法,业务流程的协调由事件驱动或专门的编排器处理。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 20:42:11