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

基于洋葱架构的.NET Core后端:邮件通知应置于基础设施层还是应用层?

关于洋葱架构中邮件通知分层的正确实践

你的当前实现不符合洋葱/整洁架构的设计原则,问题主要出在业务逻辑的拆分和依赖方向上,以下是具体分析和修正方案:

问题所在

  1. 业务逻辑泄露到表示层:"收到新消息时发送邮件"是明确的业务规则,属于"发送消息"用例的一部分,不应放在表示层(控制器)中。如果后续业务规则变更(比如部分用户免通知、新增短信通知),你需要修改控制器代码,违反了单一职责和开闭原则。
  2. 应用层用例不完整:UserService.SendMessage只完成了消息存储的核心逻辑,但完整用例应包含"发送消息+通知接收用户"的全流程,拆分到表示层会导致用例逻辑碎片化。
  3. 依赖关系不符合架构原则:洋葱架构要求内层(应用/领域层)定义抽象,外层(基础设施/表示层)实现或依赖这些抽象。你直接在表示层调用基础设施层的具体EmailService,会导致表示层与具体实现强耦合,难以扩展。

正确的分层实现方式

1. 应用层定义抽象

在应用层创建通知服务的抽象接口,这个接口代表业务用例需要的能力,而非具体实现:

// 应用层接口
public interface INotificationService
{
    Task NotifyUserAboutNewMessage(Message message);
}

2. 基础设施层实现抽象

在基础设施层编写具体的邮件发送实现,依赖应用层的抽象,负责与外部邮件系统交互:

// 基础设施层实现
public class EmailNotificationService : INotificationService
{
    private readonly IEmailClient _emailClient;

    public EmailNotificationService(IEmailClient emailClient)
    {
        _emailClient = emailClient;
    }

    public async Task NotifyUserAboutNewMessage(Message message)
    {
        // 具体的邮件发送逻辑,比如构造邮件内容、调用邮件客户端发送
        var email = new EmailMessage
        {
            To = message.RecipientEmail,
            Subject = "你收到了新消息",
            Content = $"来自{message.SenderName}的消息:{message.Content}"
        };
        await _emailClient.Send(email);
    }
}

3. 应用层封装完整用例

将"发送消息+通知用户"的完整业务逻辑封装在应用层的用例类(或服务)中,确保业务逻辑内聚:

// 应用层用例类
public class SendMessageUseCase
{
    private readonly IUserRepository _userRepository;
    private readonly INotificationService _notificationService;

    public SendMessageUseCase(IUserRepository userRepository, INotificationService notificationService)
    {
        _userRepository = userRepository;
        _notificationService = notificationService;
    }

    public async Task<MessageResult> Execute(Message message)
    {
        // 应用层验证逻辑(比如检查消息合法性、用户权限)
        var validationErrors = ValidateMessage(message);
        if (validationErrors.Any())
        {
            return MessageResult.Failure(validationErrors);
        }

        // 执行核心业务逻辑:保存消息
        var result = await _userRepository.SendMessage(message);

        // 执行通知逻辑(业务规则的一部分)
        await _notificationService.NotifyUserAboutNewMessage(message);

        return result;
    }

    private List<string> ValidateMessage(Message message)
    {
        var errors = new List<string>();
        if (string.IsNullOrWhiteSpace(message.Content))
        {
            errors.Add("消息内容不能为空");
        }
        if (message.RecipientId == Guid.Empty)
        {
            errors.Add("接收用户ID无效");
        }
        return errors;
    }
}

4. 表示层仅调用应用层用例

控制器只负责处理HTTP请求/响应、DTO转换,调用应用层的用例即可,不包含任何业务逻辑:

// 表示层控制器
[ApiController]
[Route("messages")]
public class MessagesController : ControllerBase
{
    private readonly SendMessageUseCase _sendMessageUseCase;
    private readonly IMapper _mapper;

    public MessagesController(SendMessageUseCase sendMessageUseCase, IMapper mapper)
    {
        _sendMessageUseCase = sendMessageUseCase;
        _mapper = mapper;
    }

    [HttpPost]
    public async Task<IActionResult> SendMessage(CreateMessageDto dto)
    {
        var message = _mapper.Map<Message>(dto);
        var result = await _sendMessageUseCase.Execute(message);

        if (!result.IsSuccess)
        {
            return BadRequest(result.Errors);
        }

        return Ok(result.MessageId);
    }
}

为什么这样符合洋葱架构

  • 依赖方向正确:基础设施层依赖应用层的抽象,应用层不依赖任何外层实现,符合洋葱架构"内层定义规则,外层提供实现"的核心原则。
  • 业务逻辑内聚:完整的用例逻辑封装在应用层,避免了逻辑碎片化,便于维护和修改。
  • 扩展性强:如果后续需要新增通知方式(比如短信、APP推送),只需在基础设施层新增INotificationService的实现,通过依赖注入替换即可,无需修改应用层和表示层代码。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 08:55:58