Typed HTTP Client应归属至应用架构的哪一层?
现有架构层级
- Web API:为外部调用提供应用接入点
- 基础设施层(Infrastructure):提供数据库访问仓储
- 服务层(Service Layer):负责各类业务用例(如「添加商品至购物车」)
- 领域层(Domain):包含带业务逻辑的领域对象
架构层级引用关系可参考下图:
我希望添加一个类型化HTTP客户端,用于从外部API获取特定信息,想请教该客户端应创建在哪一层/项目中?
我的现有思路
我最初考虑放在基础设施层,但基础设施层依赖于需要API数据的服务层,这意味着要在API之上引入接口,同时类型化HTTP客户端的响应对象必须定义在基础设施层,这开始变得混乱。
这种情况下会面临两种选择:
A) 服务层直接依赖API响应对象;
B) 类型化HTTP客户端将响应映射为服务层定义的对象。
这意味着要么引入不必要的依赖,那还不如把所有内容都放在服务层;要么让类型化HTTP客户端承担更多职责,这感觉像是代码异味。
最佳实践建议
类型化HTTP客户端本质是外部资源的访问实现,和数据库仓储属于同一类基础设施组件,因此更适合放在基础设施层。但需要通过依赖倒置原则解决你提到的依赖问题,具体方案如下:
定义抽象接口:在服务层(或独立的抽象层)定义一个用于获取外部数据的抽象接口,比如
IExternalDataService,接口中声明所需的业务方法(而非HTTP调用细节),方法参数和返回值使用领域层/服务层定义的业务对象。实现类型化客户端:在基础设施层创建类型化HTTP客户端,并让它实现服务层定义的抽象接口。在实现中处理HTTP调用、响应解析,并将外部API的响应对象映射为服务层需要的业务对象。
这样做的核心优势:
- 遵循依赖倒置:服务层仅依赖抽象接口,不直接依赖基础设施层的实现,避免了反向依赖问题;
- 职责清晰:类型化客户端专注于HTTP通信和外部数据适配,服务层专注于业务用例逻辑;
- 可测试性:服务层可以通过Mock抽象接口进行单元测试,无需调用真实外部API。
举个简单代码示例:
- 服务层抽象接口:
public interface IProductInfoService { Task<ProductDetails> GetProductDetailsAsync(string productId); }
- 基础设施层类型化客户端实现:
public class ExternalProductApiClient : IProductInfoService { private readonly HttpClient _httpClient; public ExternalProductApiClient(HttpClient httpClient) { _httpClient = httpClient; } public async Task<ProductDetails> GetProductDetailsAsync(string productId) { var response = await _httpClient.GetFromJsonAsync<ExternalProductResponse>($"products/{productId}"); // 映射外部响应到业务对象 return new ProductDetails { Id = response.Id, Name = response.ProductName, Price = response.Price.Amount }; } }
- 依赖注册(在Startup/Program.cs中):
builder.Services.AddHttpClient<IProductInfoService, ExternalProductApiClient>(client => { client.BaseAddress = new Uri("https://external-api.example.com/"); });
这种方式既保留了基础设施层处理外部资源的职责,又避免了你的两种顾虑:服务层不需要依赖外部API的响应对象,类型化客户端的映射职责属于基础设施层的合理范围(负责与外部系统的适配),不属于代码异味。
内容的提问来源于stack exchange,提问作者Jakob Busk Sørensen

