RegisterUserUseCase违反SRP的解决方案及控制器调用可行性探讨
注册流程用例的SRP问题与解决方案
问题背景
现有四个业务用例:
CreateUser Use Case:处理用户创建逻辑SendEmail Use Case:处理邮件发送逻辑InviteUser Use Case:处理用户邀请逻辑RegisterUser Use Case:协调注册流程,按需调用其他用例
当前RegisterUserUseCase通过构造函数注入另外三个用例的实现,这种设计违反了单一职责原则(SRP),需要明确解决方案,同时确认是否应该在控制器中直接调用这些用例。
当前实现的问题
RegisterUserUseCase现在承担了两个冲突的职责:
- 核心的用户注册流程(调用CreateUser创建用户)
- 耦合了邮件发送、用户邀请的触发逻辑,直接依赖具体用例的实现
这种设计会导致:
- 代码臃肿,后续新增注册后的操作(比如发送短信通知)需要直接修改
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
相关产品推荐
相关产品推荐

