依赖注入与控制反转:优化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编写的,但这里描述的原则同样适用于其他编程语言。

  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Salesforce REST API 的 Python 客户通常指的是能够使用 Python 语言调用 Salesforce REST API 的库或工具。 有许多 Python 库可以用于调用 Salesforce REST API,例如: - simple-salesforce:这是一个轻量级的 Python 库,可以轻松调用 Salesforce REST API。 - beatbox:这是一个由 Salesforce 开发的 Python 库,可以用于调用 Salesforce REST API 和 Salesforce SOAP API。 - PySalesforce:这是一个 Python 库,可以用于调用 Salesforce REST API 和 Salesforce SOAP API,并提供了丰富的调试功能。 你可以在 Python 的包管理器 pip 中安装这些库,然后使用它们的 API 调用 Salesforce REST API。 例如,你可以使用如下代码调用 simple-salesforce 库来获取 Salesforce 组织的信息: ```python from simple_salesforce import Salesforce sf = Salesforce(username='[email protected]', password='password', security_token='token') org_info = sf.organization.describe() ``` 在这段代码中,我们首先导入了 simple-salesforce 库,然后使用 Salesforce 类连接到 Salesforce 组织,并使用 describe 方法获取组织的信息。 希望这对你有帮助。 ### 回答2: Salesforce REST API的Python客户是一个用于与Salesforce平台进行交互的工具。它提供了一套简便的Python接口,用于访问和操作Salesforce的对象、记录和元数据。 通过Python客户,开发人员可以在他们的应用程序中实现对Salesforce数据的读取、创建、更新和删除操作。该客户对使用Salesforce REST API进行认证和授权提供了支持,所以开发人员可以通过Salesforce提供的访问令牌来访问他们的数据。 使用Salesforce REST API的Python客户,开发人员可以以编程方式连接到Salesforce实例,并使用简单的Python语法和方法来执行各种操作。他们可以通过客户来查询和检索Salesforce对象的记录,也可以创建新的记录,更新或删除现有的记录。 该客户还允许开发人员与Salesforce平台的元数据进行交互。他们可以使用客户来获取和更新对象的元数据,如字段、关系和布局信息。这使开发人员能够动态地管理和修改他们的Salesforce实例的结构。 Salesforce REST API的Python客户还提供了错误处理和异常管理的功能。它可以捕捉和处理与Salesforce交互过程中可能出现的错误和异常,以确保应用程序的稳定和可靠性。 总的来说,Salesforce REST API的Python客户是一个方便易用的工具,它使开发人员可以使用Python编程语言与Salesforce平台进行交互,并对Salesforce数据和元数据进行操作。它为开发人员提供了更简单、更灵活的方式来使用Salesforce的功能,并为他们的应用程序提供了更好的可管理性和扩展性。 ### 回答3: Salesforce REST API的Python客户是一种用于与Salesforce平台进行通信的工具。通过使用这个客户,开发人员可以轻松地在Python应用程序中使用Salesforce REST API来访问、创建、更新和删除Salesforce中的数据。使用Python的优势是在Python的丰富生态系统中可以找到许多已有的库和工具,这些可以方便地用于处理和分析Salesforce中的数据。 Python客户提供了一系列简单易用的方法和函数,使得与Salesforce REST API进行交互变得非常容易。通过使用这些方法和函数,开发人员可以进行身份验证、执行SOQL查询、创建新的记录、更新现有记录以及删除记录。此外,Python客户还提供了异常处理机制,以便开发人员能够处理任何可能的错误情况。 使用Python客户,开发人员可以轻松地集成Salesforce REST API到他们的Python应用程序中。他们可以根据自己的需求自定义请求参数,并使用Python的数据处理和分析库来处理从Salesforce返回的数据。这使得开发人员可以更好地理解和使用Salesforce中的数据,并据此进行进一步的业务逻辑和决策。 总而言之,Salesforce REST API的Python客户为开发人员提供了一种简单、方便和灵活的方式来与Salesforce平台进行交互。它使得Python开发人员能够轻松地访问和处理Salesforce中的数据,并将其集成到他们的应用程序中。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值