Provider 和 Service 在架构上的应用区别


Provider 和 Service 在架构上的应用区别

在软件架构设计中,ProviderService 是两个非常常见的概念。

很多项目里会出现类似这样的命名:

IEmailService
IEmailProvider
IPaymentService
IPaymentProvider
IStorageService
IStorageProvider

看起来都像是“提供某种能力”,但它们在架构职责上并不完全一样。

如果混用这两个概念,项目规模小的时候问题不明显;一旦系统变复杂,就容易出现职责混乱、依赖反转不清晰、业务逻辑分散等问题。

本文从架构角度,系统梳理 ProviderService 的区别。


一、先给结论

简单来说:

Service 更偏业务能力封装,Provider 更偏底层能力提供。

可以这样理解:

概念核心职责更关注什么
Service业务行为、业务流程、应用能力做什么
Provider技术能力、外部资源、基础设施能力从哪里来、怎么接入
Service面向业务用例业务语义
Provider面向实现细节技术适配
Service通常被应用层调用组织流程
Provider通常被 Service 或基础设施层调用屏蔽差异

一句话:

Service 是系统对外表达业务能力的入口,Provider 是系统对内接入具体能力的适配器。


二、什么是 Service?

Service 通常表示一个“服务能力”。

它更接近业务语义,用来封装一类明确的业务操作或应用操作。

例如:

public interface IOrderService
{
    Task CreateOrderAsync(CreateOrderRequest request);
}

这个接口表达的是:

创建订单。

它关注的是业务动作,而不是底层具体怎么保存、怎么通知、怎么调用第三方接口。

再比如:

public interface IEmailService
{
    Task SendWelcomeEmailAsync(string email);
}

这里的 EmailService 不只是简单地“发邮件”,它可能包含:

  • 选择邮件模板
  • 拼接业务变量
  • 记录发送日志
  • 调用底层邮件通道
  • 处理失败重试
  • 统一异常转换

所以 Service 往往不是单纯的工具类,而是带有一定业务语义的能力封装。


三、什么是 Provider?

Provider 通常表示一个“能力提供者”。

它更偏向基础设施、第三方服务、技术实现或资源访问。

例如:

public interface IEmailProvider
{
    Task SendAsync(string to, string subject, string body);
}

这个接口表达的是:

提供邮件发送能力。

它不关心欢迎邮件、验证码邮件、订单通知邮件这些业务场景,只负责把邮件发出去。

具体实现可以是:

public class ResendEmailProvider : IEmailProvider
{
    public Task SendAsync(string to, string subject, string body)
    {
        // 调用 Resend API
    }
}

也可以是:

public class SendGridEmailProvider : IEmailProvider
{
    public Task SendAsync(string to, string subject, string body)
    {
        // 调用 SendGrid API
    }
}

此时 Provider 的价值就体现出来了:

它屏蔽了不同第三方服务之间的接入差异。

业务层不需要知道你用的是 Resend、SendGrid、阿里云邮件推送,还是 SMTP。


四、Provider 和 Service 的核心区别

1. Service 关注业务语义

Service 的方法名通常更接近业务动作。

例如:

public interface IInquiryService
{
    Task SubmitInquiryAsync(SubmitInquiryRequest request);
}

这里表达的是“提交询盘”。

它可能内部完成:

  • 校验询盘内容
  • 保存询盘数据
  • 发送通知邮件
  • 推送到 CRM
  • 记录来源渠道
  • 返回提交结果

所以 Service 关注的是完整业务流程。


2. Provider 关注技术能力

Provider 的方法名通常更接近基础能力。

例如:

public interface IEmailProvider
{
    Task SendAsync(string to, string subject, string html);
}

它并不知道什么是询盘,也不知道什么是客户转化。

它只负责:

给我收件人、标题、内容,我帮你发出去。

Provider 不应该包含太多业务判断,否则它就开始变成 Service 了。


五、用一个实际场景理解

假设你正在开发一个“官网询盘系统”。

用户在官网提交表单后,系统需要:

  1. 保存询盘信息
  2. 给管理员发送邮件
  3. 给客户发送自动回复
  4. 记录来源页面
  5. 后续可能同步到 CRM

这时候可以这样设计:

public interface IInquiryService
{
    Task SubmitAsync(SubmitInquiryCommand command);
}

InquiryService 负责完整业务流程:

public class InquiryService : IInquiryService
{
    private readonly IInquiryRepository _inquiryRepository;
    private readonly IEmailService _emailService;

    public InquiryService(
        IInquiryRepository inquiryRepository,
        IEmailService emailService)
    {
        _inquiryRepository = inquiryRepository;
        _emailService = emailService;
    }

    public async Task SubmitAsync(SubmitInquiryCommand command)
    {
        var inquiry = Inquiry.Create(
            command.Name,
            command.Email,
            command.Message,
            command.SourceUrl
        );

        await _inquiryRepository.AddAsync(inquiry);

        await _emailService.SendInquiryNotificationAsync(inquiry);
        await _emailService.SendAutoReplyAsync(inquiry);
    }
}

然后 EmailService 负责邮件业务语义:

public interface IEmailService
{
    Task SendInquiryNotificationAsync(Inquiry inquiry);
    Task SendAutoReplyAsync(Inquiry inquiry);
}

EmailService 内部再调用底层 Provider

public class EmailService : IEmailService
{
    private readonly IEmailProvider _emailProvider;

    public EmailService(IEmailProvider emailProvider)
    {
        _emailProvider = emailProvider;
    }

    public async Task SendInquiryNotificationAsync(Inquiry inquiry)
    {
        var subject = "官网收到新的客户询盘";
        var body = $"客户:{inquiry.Name},邮箱:{inquiry.Email}";

        await _emailProvider.SendAsync("admin@example.com", subject, body);
    }

    public async Task SendAutoReplyAsync(Inquiry inquiry)
    {
        var subject = "我们已收到您的询盘";
        var body = $"您好 {inquiry.Name},我们会尽快与您联系。";

        await _emailProvider.SendAsync(inquiry.Email, subject, body);
    }
}

最后由具体 Provider 对接第三方服务:

public class ResendEmailProvider : IEmailProvider
{
    public async Task SendAsync(string to, string subject, string html)
    {
        // 调用 Resend API 发送邮件
    }
}

整体结构是:

Controller / Endpoint

InquiryService

EmailService

EmailProvider

Resend / SendGrid / SMTP

这就是比较清晰的职责边界。


六、Service 可以直接调用 Provider 吗?

可以,但要看场景。

如果业务很简单,例如只是一个轻量级项目:

ContactEndpoint → EmailProvider

也不是不可以。

但是当业务开始变复杂,比如需要模板、日志、重试、业务通知类型、不同邮件场景时,建议中间增加一层 EmailService

更推荐的结构是:

ContactEndpoint → ContactService → EmailService → EmailProvider

这样架构的扩展性会更好。


七、什么时候应该叫 Service?

以下情况更适合使用 Service

1. 有明确业务语义

例如:

IOrderService
IInquiryService
ICustomerService
IQuotationService

它们表达的是业务能力,而不是技术细节。


2. 需要组织多个步骤

例如提交询盘:

校验数据 → 保存数据库 → 发送邮件 → 记录日志 → 推送 CRM

这种完整流程更适合放在 Service 中。


3. 面向应用用例

例如:

SubmitInquiryService
CreateOrderService
GenerateQuotationService

这些都是应用层能力,适合用 Service 或 UseCase 表达。


4. 需要沉淀业务规则

例如:

CustomerService.CalculateCustomerLevel()
OrderService.CancelOrder()
FreightService.CalculateEstimate()

只要里面包含业务判断,就更接近 Service。


八、什么时候应该叫 Provider?

以下情况更适合使用 Provider

1. 接入第三方平台

例如:

IEmailProvider
ISmsProvider
IPaymentProvider
IStorageProvider
IAiProvider

这些都属于外部能力接入。


2. 屏蔽不同实现差异

例如:

ResendEmailProvider
SendGridEmailProvider
AliyunSmsProvider
TencentSmsProvider
QdrantVectorProvider
OpenAIEmbeddingProvider

Provider 的核心价值是:

上层不依赖具体供应商。


3. 更偏基础设施层

Provider 通常位于 Infrastructure 层:

Infrastructure/
  Email/
    IEmailProvider.cs
    ResendEmailProvider.cs
  Storage/
    IStorageProvider.cs
    AliyunOssStorageProvider.cs
  Payment/
    IPaymentProvider.cs
    StripePaymentProvider.cs

4. 不包含业务流程

Provider 应尽量保持简单,不要写太多业务规则。

例如下面这种就不太推荐:

public class EmailProvider
{
    public Task SendInquiryNotificationAsync(Inquiry inquiry)
    {
        // 这里已经有业务语义了
    }
}

因为 SendInquiryNotificationAsync 明显是业务场景,不应该放在 Provider 中。

更合理的是:

EmailService.SendInquiryNotificationAsync()

EmailProvider.SendAsync()

九、Provider 和 Service 在分层架构中的位置

在常见的 Clean Architecture 或轻量 DDD 架构中,可以这样划分:

API / Presentation
  └── Controller / Endpoint

Application
  └── UseCases / Services

Domain
  └── Entities / ValueObjects / DomainServices

Infrastructure
  └── Providers / Repositories / ExternalClients

对应关系大致是:

层级适合放什么
API 层Controller、Endpoint
Application 层UseCase、Application Service
Domain 层Domain Service、Entity
Infrastructure 层Provider、Repository、第三方 Client
Shared/Common工具、基础抽象、通用模型

所以一般来说:

  • Service 更靠近 Application 或 Domain
  • Provider 更靠近 Infrastructure

十、Service 和 Provider 的命名建议

推荐命名方式

业务服务:

IInquiryService
ICustomerService
IOrderService
IQuotationService

应用用例:

SubmitInquiryUseCase
CreateOrderUseCase
GenerateQuotationUseCase

第三方能力提供者:

IEmailProvider
ISmsProvider
IStorageProvider
IPaymentProvider
IVectorStoreProvider

具体实现:

ResendEmailProvider
AliyunSmsProvider
AliyunOssStorageProvider
StripePaymentProvider
QdrantVectorStoreProvider

十一、不要把所有类都叫 Service

很多项目容易出现一个问题:

UserService
EmailService
SmsService
StorageService
PaymentService
JwtService
FileService
CacheService

这样命名看起来统一,但实际上会让业务服务和基础设施服务混在一起。

例如:

IStorageService

这个名字不一定错,但它表达不够清晰。

如果它只是对接阿里云 OSS、MinIO、S3,那么叫:

IStorageProvider

或者:

IFileStorageProvider

会更准确。

如果它封装的是业务文件逻辑,比如上传客户资料、生成合同文件、绑定业务单据,那么叫:

ICustomerFileService
IContractFileService

会更合适。


十二、一个简单判断标准

可以用下面几个问题判断应该叫 Service 还是 Provider。

如果答案是“是”,更适合叫 Service

  • 这个类是否表达业务动作?
  • 是否组织多个业务步骤?
  • 是否包含业务规则?
  • 是否面向某个应用场景?
  • 方法名是否像 CreateOrderSubmitInquirySendWelcomeEmail

如果答案是“是”,更适合叫 Provider

  • 这个类是否对接第三方?
  • 是否屏蔽技术实现差异?
  • 是否属于基础设施能力?
  • 是否可以替换不同供应商?
  • 方法名是否更通用,比如 SendAsyncUploadAsyncCreateAsync

十三、在 .NET 项目中的推荐结构

以一个官网询盘系统为例,可以这样组织:

YOUKEZAN.WEB.API/
  Endpoints/
    Contact/
      SubmitContactEndpoint.cs

  UseCases/
    Contact/
      SubmitContactUseCase.cs

  Domain/
    Inquiries/
      Inquiry.cs

  Infrastructure/
    Email/
      Abstractions/
        IEmailProvider.cs
      Providers/
        ResendEmailProvider.cs
      Services/
        EmailService.cs

如果你喜欢更清晰的应用层分组,也可以这样:

Application/
  Inquiries/
    SubmitInquiryUseCase.cs
    IInquiryService.cs

Infrastructure/
  Email/
    IEmailProvider.cs
    ResendEmailProvider.cs

推荐调用链:

SubmitContactEndpoint

SubmitContactUseCase

EmailService

IEmailProvider

ResendEmailProvider

这样既保留了业务表达,也隔离了第三方实现。


十四、常见错误示例

错误一:Provider 中写业务逻辑

不推荐:

public class ResendEmailProvider
{
    public Task SendInquiryNotificationAsync(Inquiry inquiry)
    {
        // 拼接询盘通知模板
        // 判断客户来源
        // 调用 Resend
    }
}

问题是:

  • Provider 绑定了业务场景
  • 后续换邮件供应商时,业务逻辑可能被迫复制
  • 基础设施层污染业务逻辑

推荐:

EmailService 负责业务邮件内容
EmailProvider 负责发送动作

错误二:Service 直接依赖具体第三方 SDK

不推荐:

public class InquiryService
{
    private readonly ResendClient _resendClient;
}

问题是:

  • 应用服务直接依赖第三方 SDK
  • 后续替换供应商成本高
  • 单元测试困难

推荐:

public class InquiryService
{
    private readonly IEmailService _emailService;
}

或者:

public class EmailService
{
    private readonly IEmailProvider _emailProvider;
}

错误三:所有能力都封装成 Service

不推荐:

AliyunOssService
SendGridService
OpenAIService
QdrantService

如果这些类只是对接外部平台,更建议叫:

AliyunOssProvider
SendGridEmailProvider
OpenAIEmbeddingProvider
QdrantVectorProvider

这样一看就知道它们是“能力提供者”,不是业务服务。


十五、总结

ProviderService 的区别,本质上是职责边界的区别。

Service 关注业务能力,负责表达系统能做什么。

Provider 关注技术能力,负责提供系统依赖的外部能力。

可以用一句话总结:

Service 面向业务,Provider 面向实现;Service 组织流程,Provider 提供能力。

在实际项目中,建议遵循下面的设计原则:

  • 业务动作放到 Service 或 UseCase
  • 第三方接入放到 Provider
  • Provider 不写业务逻辑
  • Service 不直接依赖具体 SDK
  • 上层依赖抽象,下层提供实现
  • 命名要体现职责,不要所有类都叫 Service

如果项目还很小,可以适当简化。

但只要你的系统会持续演进,尤其是涉及邮件、短信、支付、存储、AI、向量数据库、第三方 API 这类能力时,提前区分 ServiceProvider,会让架构更清晰,也更容易维护。

业务入口 → Service / UseCase → Provider → 第三方能力

这就是更稳定、更可扩展的架构边界。

微信订阅号二维码