Skip to content
bitzorcas
中EN

Recipe

实战:编写六边形架构基础设施适配器(Adapter)

掌握 BitzOrcas.Modern 六边形端口与适配器模式:在领域层定义依赖倒置窄端口(Port)、在基础设施层编写生产适配器(Adapter)、封装第三方网络异常、配置 Polly 弹性策略与注册 Fallback 安全回退。

Last updated

在大型企业级软件系统中,业务领域模型必须保持高度纯粹,绝不能直接依赖任何具体的第三方云服务 SDK(如电子签章服务、银行支付网关、全国企业工商查询 API 等)。直接耦合第三方 SDK 会导致系统难以进行本地单元测试、第三方厂商 API 变更直接破坏领域模型,并在第三方服务故障时引发全站雪崩。

BitzOrcas.Modern 严格贯彻六边形架构(Ports and Adapters,又称洋葱架构/整洁架构端口模式):

  • 领域层定义窄端口(Port):仅声明业务所需的纯抽象接口,入参和返回值均为领域类型;
  • 基础设施层实现适配器(Adapter):封装具体 HTTP 通讯、网络异常重试、签名算法与厂商 SDK;
  • 提供 Fallback/Mock 回退实现:确保在离线开发、CI 自动化测试与第三方服务降级时系统平稳可用。

本篇指南将以**第三方电子合同云签章服务(如法大大/腾讯电子签)**为例,带你完整实现一个工业级基础设施适配器。

端口与适配器交互全景图

外部第三方云服务基础设施层 (Infrastructure Adapters)业务领域层 (Domain & Application)仅调用领域端口实现端口实现端口Polly 重试与熔断

MatterIntakeCommandHandler
(立案审批业务处理器)

IElectronicSignaturePort
(依赖倒置只读窄端口)

FadadaSignatureAdapter
(生产环境真实适配器)

MockSignatureAdapter
(本地开发与离线测试回退)

第三方电子签章 OpenAPI
(HTTP / HTTPS / Webhook)


第一步:在领域层定义依赖倒置窄端口(Port)

在业务模块的 Domain 或 Contracts 层(例如 src/Modules/Legal/BitzOrcas.Modules.Legal.Domain/Ports/)定义强类型窄接口。

[!IMPORTANT] 端口设计铁律:

  1. 接口方法仅暴露业务所需的最小能力(窄端口原则);
  2. 入参与返回值严禁出现第三方 SDK 的专有类型,统一使用领域类型或 Result<T>;
  3. 必须支持 CancellationToken 取消令牌传播。
src/Modules/Legal/BitzOrcas.Modules.Legal.Domain/Ports/IElectronicSignaturePort.cs
using System.Threading;
using System.Threading.Tasks;
using BitzOrcas.Domain.Results;
namespace BitzOrcas.Modules.Legal.Domain.Ports;
/// <summary>
/// 电子签章第三方服务抽象端口
/// </summary>
/// <remarks>
/// <para>业务领域层仅通过该窄端口与电子签章系统交互,与具体厂商 SDK 解耦。</para>
/// <para>在生产环境中由真实云适配器实现,在本地测试环境中由 Mock 适配器回退。</para>
/// </remarks>
public interface IElectronicSignaturePort
{
/// <summary>
/// 异步发起合同多方在线签署流程
/// </summary>
/// <param name="contractCode">合同业务流水号。</param>
/// <param name="contractTitle">合同签署标题。</param>
/// <param name="signerIdCard">签署人身份证件号码。</param>
/// <param name="signerMobile">签署人接收验证码的手机号。</param>
/// <param name="documentStorageUrl">待签署 PDF 合同文件在对象存储中的只读下载链接。</param>
/// <param name="cancellationToken">异步取消令牌。</param>
/// <returns>包含第三方签章任务唯一流水号的结果对象。</returns>
Task<Result<string>> InitiateSignatureAsync(
string contractCode,
string contractTitle,
string signerIdCard,
string signerMobile,
string documentStorageUrl,
CancellationToken cancellationToken = default);
/// <summary>
/// 异步查询指定签章任务的实时签署进度状态
/// </summary>
/// <param name="signatureTaskId">第三方签章任务流水号。</param>
/// <param name="cancellationToken">异步取消令牌。</param>
/// <returns>当前签署状态(1=等待中, 2=已签署, 3=已拒签)。</returns>
Task<Result<int>> QuerySignatureStatusAsync(
string signatureTaskId,
CancellationToken cancellationToken = default);
}

第二步:在基础设施层编写生产适配器(Adapter)

在 src/Platform/BitzOrcas.Infrastructure/Signature/ 中创建 FadadaSignatureAdapter.cs。 该适配器使用 HttpClient 调用第三方 API,并通过 Polly 实现网络抖动自动重试与错误封装:

src/Platform/BitzOrcas.Infrastructure/Signature/FadadaSignatureAdapter.cs
using System;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using BitzOrcas.Domain.Results;
using BitzOrcas.Modules.Legal.Domain.Ports;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace BitzOrcas.Infrastructure.Signature;
/// <summary>
/// 电子签章适配器稳定错误契约
/// </summary>
public static class SignatureErrors
{
/// <summary>
/// 第三方网关响应异常
/// </summary>
public static readonly Error RemoteGatewayError =
Error.Failure("Signature.RemoteGatewayError", "第三方签章通道响应异常。");
/// <summary>
/// 第三方业务受理拒绝
/// </summary>
public static readonly Error BusinessRejected =
Error.Failure("Signature.BusinessRejected", "第三方签章接口业务受理失败。");
/// <summary>
/// 签章通道网络通信超时
/// </summary>
public static readonly Error NetworkTimeout =
Error.Failure("Signature.NetworkTimeout", "签章通道通讯超时,请稍后重试。");
/// <summary>
/// 签章任务流水未找到
/// </summary>
public static readonly Error TaskNotFound =
Error.NotFound("Signature.TaskNotFound", "未检索到指定签章任务流水。");
/// <summary>
/// 查询签章进度失败
/// </summary>
public static readonly Error QueryFailed =
Error.Failure("Signature.QueryFailed", "查询签章进度失败。");
}
/// <summary>
/// 电子签章服务配置选项
/// </summary>
public sealed class FadadaSignatureOptions
{
/// <summary>
/// 配置节点名称
/// </summary>
public const string SectionName = "Integrations:FadadaSignature";
/// <summary>
/// 第三方网关基础地址
/// </summary>
public string BaseUrl { get; set; } = "https://api.fadada.com/v2/";
/// <summary>
/// 开发者应用凭证 AppId
/// </summary>
public string AppId { get; set; } = string.Empty;
/// <summary>
/// 开发者签名私钥 AppSecret
/// </summary>
public string AppSecret { get; set; } = string.Empty;
}
/// <summary>
/// 第三方电子签章生产适配器
/// </summary>
/// <remarks>
/// <para>实现 <see cref="IElectronicSignaturePort"/> 端口,承载与第三方云平台的 HTTP 通讯。</para>
/// <para>负责请求参数加密、网络超时重试以及将第三方错误码映射为标准领域错误。</para>
/// </remarks>
public sealed class FadadaSignatureAdapter : IElectronicSignaturePort
{
private readonly HttpClient _httpClient;
private readonly FadadaSignatureOptions _options;
private readonly ILogger<FadadaSignatureAdapter> _logger;
/// <summary>
/// 初始化电子签章生产适配器
/// </summary>
/// <param name="httpClient">弹性 HTTP 客户端。</param>
/// <param name="options">配置选项。</param>
/// <param name="logger">日志记录器。</param>
public FadadaSignatureAdapter(
HttpClient httpClient,
IOptions<FadadaSignatureOptions> options,
ILogger<FadadaSignatureAdapter> logger)
{
_httpClient = httpClient;
_options = options.Value;
_logger = logger;
}
/// <summary>
/// 异步发起合同签署流程
/// </summary>
public async Task<Result<string>> InitiateSignatureAsync(
string contractCode,
string contractTitle,
string signerIdCard,
string signerMobile,
string documentStorageUrl,
CancellationToken cancellationToken = default)
{
try
{
// 1. 组装第三方特定的请求载荷
var payload = new
{
app_id = _options.AppId,
timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
contract_no = contractCode,
title = contractTitle,
doc_url = documentStorageUrl,
signer = new
{
id_card = signerIdCard,
mobile = signerMobile
}
};
// 2. 发起 HTTP POST 请求
var response = await _httpClient.PostAsJsonAsync("contract/initiate", payload, cancellationToken);
if (!response.IsSuccessStatusCode)
{
_logger.LogWarning("第三方签章服务响应 HTTP 异常状态码: {StatusCode}", response.StatusCode);
return Result.Failure<string>(
SignatureErrors.RemoteGatewayError.WithDescription($"第三方签章通道响应异常 (HTTP {response.StatusCode})。"));
}
var responseData = await response.Content.ReadFromJsonAsync<FadadaApiResponse>(cancellationToken);
if (responseData is null || responseData.Code != 1000)
{
_logger.LogWarning("第三方签章接口业务失败: Code={Code}, Msg={Msg}", responseData?.Code, responseData?.Message);
return Result.Failure<string>(
SignatureErrors.BusinessRejected.WithDescription(responseData?.Message ?? "第三方签章接口受理失败。"));
}
// 3. 提取第三方签章流水号
return Result.Success(responseData.Data.TaskId);
}
catch (OperationCanceledException)
{
throw; // 允许取消令牌向上冒泡
}
catch (Exception ex)
{
_logger.LogError(ex, "调用第三方电子签章服务发生未捕获网络异常: ContractCode={ContractCode}", contractCode);
return Result.Failure<string>(SignatureErrors.NetworkTimeout);
}
}
/// <summary>
/// 异步查询指定任务的签署状态
/// </summary>
public async Task<Result<int>> QuerySignatureStatusAsync(
string signatureTaskId,
CancellationToken cancellationToken = default)
{
try
{
var response = await _httpClient.GetFromJsonAsync<FadadaApiResponse>(
$"contract/status?task_id={Uri.EscapeDataString(signatureTaskId)}", cancellationToken);
if (response is null || response.Code != 1000)
{
return Result.Failure<int>(SignatureErrors.TaskNotFound);
}
return Result.Success(response.Data.Status);
}
catch (Exception ex)
{
_logger.LogError(ex, "查询签章状态异常: TaskId={TaskId}", signatureTaskId);
return Result.Failure<int>(SignatureErrors.QueryFailed);
}
}
private sealed class FadadaApiResponse
{
public int Code { get; set; }
public string Message { get; set; } = string.Empty;
public FadadaData Data { get; set; } = new();
}
private sealed class FadadaData
{
public string TaskId { get; set; } = string.Empty;
public int Status { get; set; }
}
}

第三步:编写本地与测试 Mock 适配器(Mock Adapter)

为了让开发同学在没有第三方外网 Key 的环境下也能顺畅本地调试,编写一个内存回退实现:

src/Platform/BitzOrcas.Infrastructure/Signature/MockSignatureAdapter.cs
using System;
using System.Threading;
using System.Threading.Tasks;
using BitzOrcas.Domain.Results;
using BitzOrcas.Modules.Legal.Domain.Ports;
using Microsoft.Extensions.Logging;
namespace BitzOrcas.Infrastructure.Signature;
/// <summary>
/// 电子签章本地开发与单元测试 Mock 适配器
/// </summary>
/// <remarks>
/// 仅在未配置第三方密钥或处于测试环境下自动生效,即时返回模拟签章流水号。
/// </remarks>
public sealed class MockSignatureAdapter : IElectronicSignaturePort
{
private readonly ILogger<MockSignatureAdapter> _logger;
/// <summary>
/// 初始化 Mock 适配器
/// </summary>
public MockSignatureAdapter(ILogger<MockSignatureAdapter> logger)
{
_logger = logger;
}
public Task<Result<string>> InitiateSignatureAsync(
string contractCode,
string contractTitle,
string signerIdCard,
string signerMobile,
string documentStorageUrl,
CancellationToken cancellationToken = default)
{
var mockTaskId = $"MOCK-SIG-{DateTime.UtcNow:yyyyMMdd}-{Random.Shared.Next(10000, 99999)}";
_logger.LogInformation("[MockSignatureAdapter] 已为合同【{ContractCode}】生成本地模拟签章流水: {TaskId}", contractCode, mockTaskId);
return Task.FromResult(Result.Success(mockTaskId));
}
public Task<Result<int>> QuerySignatureStatusAsync(
string signatureTaskId,
CancellationToken cancellationToken = default)
{
return Task.FromResult(Result.Success(2)); // 模拟已签署状态
}
}

第四步:使用 TryAdd 机制完成服务注册

在服务注册扩展方法中,使用 TryAddScoped 确保适配器可优雅回退:

src/Platform/BitzOrcas.Infrastructure/Signature/SignatureServiceCollectionExtensions.cs
using BitzOrcas.Modules.Legal.Domain.Ports;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace BitzOrcas.Infrastructure.Signature;
/// <summary>
/// 电子签章服务注册扩展
/// </summary>
public static class SignatureServiceCollectionExtensions
{
/// <summary>
/// 注册电子签章基础设施适配器
/// </summary>
/// <param name="services">服务集合。</param>
/// <param name="configuration">配置根。</param>
/// <returns>链式调用服务集合。</returns>
public static IServiceCollection AddElectronicSignatureAdapter(
this IServiceCollection services,
IConfiguration configuration)
{
var optionsSection = configuration.GetSection(FadadaSignatureOptions.SectionName);
var options = optionsSection.Get<FadadaSignatureOptions>();
// 若配置了有效的 AppId 与 AppSecret,则注册生产适配器
if (options is not null && !string.IsNullOrWhiteSpace(options.AppId))
{
services.Configure<FadadaSignatureOptions>(optionsSection);
services.AddHttpClient<IElectronicSignaturePort, FadadaSignatureAdapter>(client =>
{
client.BaseAddress = new Uri(options.BaseUrl);
client.Timeout = TimeSpan.FromSeconds(15);
});
}
else
{
// 否则安全回退至本地 Mock 适配器,保证系统正常开机与测试运行
services.TryAddScoped<IElectronicSignaturePort, MockSignatureAdapter>();
}
return services;
}
}

总结

六边形架构适配器模式为系统带来了核心价值:

  • 领域纯粹性:业务 Handler 与聚合根只与抽象端口交互,完全免疫第三方厂商 API 变更;
  • 高弹性保障:网络异常与 HTTP 状态码在适配器内部被标准化为 Result.Failure,绝不引发未处理异常崩溃;
  • 开箱即用开发体验:无密钥环境下自动激活 Mock 适配器,本地与 CI 流水线零配置秒级拉起。

100%

滚轮或按钮缩放 · 放大后拖动画面 · 双击切换 100% / 200%