依赖注入与控制反转:优化Go语言REST API客户端

简介: 依赖注入与控制反转:优化Go语言REST API客户端

在这篇文章中,我将探讨依赖注入(DI)和控制反转(IoC)是什么,以及它们的重要性。作为示例,我将使用Monibot的REST API客户端。让我们开始吧:


一个简单的客户端实现


我们从一个简单的客户端实现开始,允许调用者访问Monibot的REST API,具体来说,是为了发送指标值。客户端的实现可能如下所示:


package monibot
type Client struct {
}
func NewClient() *Client {
    return &Client{}
}
func (c *Client) PostMetricValue(value int) {
    body := fmt.Sprintf("value=%d", value)
    http.Post("https://monibot.io/api/metric", []byte(body))
}


这里有一个客户端,提供了PostMetricValue方法,该方法用于将指标值上传到Monibot。我们的库的用户可能像这样使用它:


import "monibot"
func main() {
    // 初始化API客户端
    client := monibot.NewClient()
    // 发送指标值
    client.PostMetricValue(42)
}

依赖注入


现在假设我们想对客户端进行单元测试。当所有HTTP发送代码都是硬编码的时候,我们如何测试客户端呢?对于每次测试运行,我们都需要一个“真实”的HTTP服务器来回答我们发送给它的所有请求。不可取!我们可以做得更好:让我们将HTTP处理作为“依赖”;让我们发明一个 Transport 接口:


package monibot
// Transport传输请求。
type Transport interface {
    Post(url string, body []byte)
}


让我们再发明一个具体的使用HTTP作为通信协议的Transport


package monibot
// HTTPTransport是一个使用HTTP协议传输请求的Transport。
type HTTPTransport struct {
}
func (t HTTPTransport) Post(url string, data []byte) {
    http.Post(url, data)
}


然后让我们重写客户端,使其“依赖”于一个Transport 接口:


package monibot
type Client struct {
    transport Transport
}
func NewClient(transport Transport) *Client {
    return &Client{transport}
}
func (c *Client) PostMetricValue(value int) {
    body := fmt.Sprintf("value=%d", value)
    c.transport.Post("https://monibot.io/api/metric", []byte(body))
}


现在,客户端将请求转发到它的Transport依赖。当创建客户端时,transport(客户端的依赖项)被“注入”到客户端中。调用者可以这样初始化一个客户端:


import "monibot"
func main() {
    // 初始化API客户端
    var transport monibot.HTTPTransport
    client := monibot.NewClient(transport)
    // 发送指标值
    client.PostMetricValue(42)
}


单元测试


现在我们可以编写一个使用“伪造”Transport的单元测试:


// TestPostMetricValue确保客户端向REST API发送正确的POST请求。
func TestPostMetricValue(t *testing.T) {
    transport := &fakeTransport{}
    client := NewClient(transport)
    client.PostMetricValue(42)
    if len(transport.calls) != 1 {
        t.Fatal("期望1次传输调用,但是是%d次", len(transport.calls))
    }
    if transport.calls[0] != "POST https://monibot.io/api/metric, body=\\"value=42\\"" {
        t.Fatal("错误的传输调用 %q", transport.calls[0])
    }
}
// 伪造的Transport是单元测试中使用的Transport。
type fakeTransport struct {
    calls []string
}
func (f *fakeTransport) Post(url string, body []byte) {
    f.calls = append(f.calls, fmt.Sprintf("POST %v, body=%q", url, string(body)))
}


添加更多的Transport函数


现在假设我们库的其他部分,也使用了Transport功能,需要比POST更多的HTTP方法。对于它们,我们必须扩展我们的Transport接口:


package monibot
// Transport传输请求。
type Transport interface {
    Get(url string) []byte     // 添加,因为health-monitor需要
    Post(url string, body []byte)
    Delete(url string)         // 添加,因为resource-monitor需要
}


现在我们有一个问题。编译器抱怨我们的fakeTransport不再满足Transport接口。所以让我们通过添加缺失的函数来解决它:


// 伪造的Transport是单元测试中使用的Transport。
type fakeTransport struct {
    calls []string
}
func (f *fakeTransport) Get(url string) []byte {
    panic("不使用")
}
func (f *fakeTransport) Post(url string, body []byte) {
    f.calls = append(f.calls, fmt.Sprintf("POST %v, body=%q", url, string(body)))
}
func (f *fakeTransport) Delete(url string) {
    panic("不使用")
}


我们做了什么?由于在单元测试中我们不需要新的Get()Delete()函数,如果它们被调用,我们就抛出异常。这里有一个问题:每次在Transport中添加新函数时,我们都会破坏现有的fakeTransport实现。对于大型代码库来说,这将导致维护噩梦。我们能做得更好吗?


控制反转


问题在于我们的客户端(和相应的单元测试)依赖于一个它们不能控制的类型。在这种情况下,它是Transport接口。为了解决这个问题,让我们通过引入一个未导出的接口,该接口仅声明了我们的客户端所需的内容,来反转控制:


package monibot
// clientTransport传输Client的请求。
type clientTransport interface {
    Post(url string, body []byte)
}
type Client struct {
    transport clientTransport
}
func NewClient(transport clientTransport) *Client {
    return &Client{transport}
}
func (c *Client) PostMetricValue(value int) {
    body := fmt.Sprintf("value=%d", value)
    c.transport.Post("https://monibot.io/api/metric", []byte(body))
}


现在让我们将我们的单元测试更改为使用假的clientTransport


// TestPostMetricValue确保客户端向REST API发送正确的POST请求。
func TestPostMetricValue(t *testing.T) {
    transport := &fakeTransport{}
    client := NewClient(transport)
    client.PostMetricValue(42)
    if len(f.calls) != 1 {
        t.Fatal("期望1次传输调用,但是是%d次", len(f.calls))
    }
    if f.calls[0] != "POST https://monibot.io/api/metric, body=\\"value=42\\"" {
        t.Fatal("错误的传输调用 %q", f.calls[0])
    }
}
// 伪造的Transport是在单元测试中使用的clientTransport。
type fakeTransport struct {
    calls []string
}
func (f *fakeTransport) Post(url string, body []byte) {
    f.calls = append(f.calls, fmt.Sprintf("POST %v, body=%q", url, string(body)))
}


由于Go的隐式接口实现(如果愿意,可以称之为'鸭子类型'),我们库的用户什么也不需要改变:


import "monibot"
func main() {
    // 初始化API客户端
    var transport monibot.HTTPTransport
    client := monibot.NewClient(transport)
    // 发送指标值
    client.PostMetricValue(42)
}


重新审视Transport


如果我们使IoC成为规范(正如我们应该做的那样),就不再需要导出Transport接口了。为什么呢?因为如果消费者需要一个接口,让他们在自己的作用域中定义它,就像我们对'clientTransport'做的那样。


不要导出接口。导出具体实现。如果消费者需要接口,让他们在自己的作用域中定义。


总结


在这篇文章中,我展示了如何以及为什么在Go中使用DI和IoC。正确使用DI/IoC可以导致更易于测试和维护的代码,特别是在代码库不断增长时。虽然代码示例是用Go编写的,但这里描述的原则同样适用于其他编程语言。

相关文章
|
5天前
|
JSON 监控 API
亚马逊:调用跨境物流API追踪国际包裹清关状态,优化时效
在亚马逊跨境运营中,清关不确定性常导致物流延误。通过调用跨境物流API(如Amazon SP-API),可自动化获取包裹清关状态与预计交付时间,提升响应效率。本文详解API调用步骤,提供Python代码示例,并分享实时监控、预警机制与数据优化策略,助力卖家缩短处理时间、提升客户满意度,实现高效智能的国际物流管理。
35 0
|
9天前
|
数据采集 API
京东:调用用户行为API分析购买路径,优化页面跳转逻辑
京东通过整合用户行为API,构建购买路径分析体系,运用马尔可夫链模型识别高损耗、断裂与冗余路径,优化页面跳转逻辑。实施流程合并、预加载及实时干预策略,转化率提升30.2%,路径缩短34.9%,跳转失败率下降78.7%,实现数据驱动的精细化运营。
84 0
|
9天前
|
JSON 数据可视化 API
淘宝/天猫:利用销售数据API生成区域热力图,优化仓储布局
本文详解如何利用淘宝/天猫销售数据API生成区域热力图,结合核密度估计与线性规划,科学优化仓储布局。通过数据驱动降低物流成本15%-20%,提升配送效率,助力电商高效运营。(238字)
66 0
|
10天前
|
存储 监控 前端开发
淘宝商品详情 API 实战:5 大策略提升店铺转化率(附签名优化代码 + 避坑指南)
本文深入解析淘宝商品详情API的核心字段与实战应用,分享如何通过动态定价、库存预警、差评控制等5大策略提升电商转化率。结合300+店铺实战经验,提供优化代码与避坑指南,助力开发者与运营者实现数据驱动的精细化运营。
机器学习/深度学习 搜索推荐 算法
37 0
|
17天前
|
机器学习/深度学习 自然语言处理 算法
小红书:通过商品标签API自动生成内容标签,优化社区推荐算法
小红书通过商品标签API自动生成内容标签,提升推荐系统精准度与用户体验。流程包括API集成、标签生成算法与推荐优化,实现高效率、智能化内容匹配,助力社交电商发展。
51 0
|
19天前
|
JSON 算法 API
京东:利用商品管理API自动调整商品上下架状态,优化搜索排名
京东通过商品管理API实现商品状态自动调整,优化搜索排名。本文详解API功能、实现方法、排名优化原理,并提供Python代码示例,助力商家提升运营效率与曝光度。
31 0
|
24天前
|
供应链 算法 API
借助唯品会 API,唯品会店铺运动商品库存管理优化
本文介绍如何利用唯品会API优化运动商品库存管理。运动商品因季节性和促销影响,库存波动大,传统管理易导致缺货或积压。通过唯品会API,可实现库存实时监控、销售数据同步与自动化补货。结合预测模型与API数据,店铺可降低成本、提升效率,并增强市场响应能力,实现智能化运营。
44 0
|
29天前
|
供应链 搜索推荐 API
苏宁易购 API 助力,苏宁易购平台 3C 产品销售策略优化
在电商竞争激烈的环境下,苏宁易购通过API优化3C产品销售策略,实现数据驱动决策、个性化推荐与智能库存管理,提升销售额与运营效率,增强用户体验,巩固市场竞争力。
52 0
|
1月前
|
JSON 监控 数据挖掘
借助拼多多 API,拼多多店铺商品类目优化精准指导
在电商竞争激烈的环境下,拼多多店铺通过API优化商品类目,可提升搜索排名与转化率。本文详解如何利用API获取类目数据、分析匹配度并实现自动化优化,助力商家精准定位商品类目,提升流量与销量。
137 0