用Option模式和对接层简化和管理Go项目的外部API

在项目开发实现功能需求的过程中不可避免的要与外部第三方系统进行交互,这些交互大部分是通过请求API接口来完成的。

前几节提到但一直没带大家用代码过一遍的Lib层就是负责写第三方对接逻辑的,通过把跟第三方对接的逻辑限制在Lib层里,让项目的其他部分不需要关注第三方的逻辑,从而达到每部分都职责分明,这样项目的代码多起来后才不会变得臃肿和杂乱。

不过在演示Lib层的使用前我们需要先一起给项目封装一个好用的HTTP请求工具。

a0a3a84dbc2871473ba1643e164f71c0.png

用Go 实现一个好用的 HTTP 请求工具

Go自带了的http库就能发起API调用,为啥我们还要做这个封装呢?其实主要有以下几个目的:

  • 简化 HTTP 请求的发起

  • 利用Option模式用命名参数的方式进行请求的多选项设置

  • header 头中自动携带trace信息,方便内部的二方服务一起做好链路追踪

  • 慢请求的日志记录

  • 非 200 响应错误统一处理

我们一个个来说,首先在项目中发起HTTP请求调用API的时候不同的情况会有不同的设置:

  • Method GET 或者 是POST

  • POST 请求要设置请求Body

  • 超时时间是否要单独设置

  • Header 头是否要携带的信息

  • 特殊情况下还可能有其他更多的请求设置

如果项目中每次调用API都是像下面这段代码一样用原生 http 库中的方法, 先 new 出一个Request对象,再按照需要一个个设置上面的配置项,最后再发起请求,当然是没有问题,完全能实现功能。

req, err := http.NewRequest(method, url, bytes.NewReader(reqOpts.data))
req.WithContext(ctx)
req.Header.Add("Content-Type", "application/json")

client := &http.Client{Timeout: 5 * time.Second}
resp, err := client.Do(req)

但就是每次都得写这一堆代码,在多人开发的项目中一定会把这些代码粘来粘去,除此之外像请求日志记录、请求头设置追踪信息等通用操作的代码每次也都得写一遍,增加很多冗余不说,一旦忘记了这些后面出问题想排查原因也不好排查。

所以我们必须要封装一个统一的 HTTP 请求工具方法,把一些通用的基础工作在工具中都做好避免每次都要记得去手写那些代码,从而减少编码中不必要的精力浪费。

那么要封装HTTP请求工具就遇到一个问题,我们并不是每次发请求都需要设置这么多参数,那你的工具方法应该怎么设置参数呢?设置少了遇到不满足的情况还得重新再写一个多参数版本的工具方法,那谁能保证类似需要加参数的情况会不会再有呢?

而且参数设置的多了,每次使用时用不到的参数也得给传一个零值才能调用,一旦调用时参数顺序传错了还会有问题,属于自己给自己写BUG的一种常见情况。

用Option模式让Go支持命名参数

考虑到这些情况后,根据这些痛点,我们利用Golang func 的可变参数特性,结合 Option 模式的设计,让我们的工具方法支持可变且具名的参数,即拥有下面的两个能力

  • 用到哪些设置了,调用时再传那些参数,不需要让用不到的设置占用参数位置。

  • 利用Option模式让参数变成具有名称的参数,不再限定参数的顺序。

首先我们在 common/util 下创建 httptool 目录,其中新增httptool.go 文件。

我们用Option模式是为了设置请求的选项,所以我们在 httptool.go 中先定义一个用于保存请求选项的结构体。

type requestOption struct {
 ctx     context.Context
 timeout time.Duration
 data    []byte
 headers map[string]string
}

func defaultRequestOptions() *requestOption {
 return &requestOption{
  ctx:     context.Background(),
  timeout: 5 * time.Second,
  data:    nil,
  headers: map[string]string{},
 }
}

这个里面的字段可以根据自己的需要再增加。然后我们定义出Option的通用行为:

type Option interface {
 apply(option *requestOption) error
}

type optionFunc func(option *requestOption) error

func (f optionFunc) apply(opts *requestOption) error {
 return f(opts)
}

我们看下面这几个请求配置选项对应的Option 函数,这里我不写注释光看每个函数的名字你们也能看出来他们都是用来设置什么的。

func WithContext(ctx context.Context) Option {
 return optionFunc(func(opts *requestOption) (err error) {
  opts.ctx = ctx
  return
 })
}

func WithTimeout(timeout time.Duration) Option {
 return optionFunc(func(opts *requestOption) (err error) {
  opts.timeout, err = timeout, nil
  return
 })
}

func WithHeaders(headers map[string]string) Option {
 return optionFunc(func(opts *requestOption) (err error) {
  for k, v := range headers {
   opts.headers[k] = v
  }
  return
 })
}

func WithData(data []byte) Option {
 return optionFunc(func(opts *requestOption) (err error) {
  opts.data, err = data, nil
  return
 })
}

optionFunc 把这些 func(opts *requestOption) (err error) 类型函数都转换成了自己的类型,让他们成为了Option接口的实现,拥有了apply方法, apply方法的逻辑就是直接调用这些被转换的函数。

这样在我们的请求工具方法中,就可以迭代可变参数的实际参数,然后一个个地去调用他们的 apply 方法来构造最终的请求选项, 像下面这样。

func Request(method string, url string, options ...Option) (httpStatusCode int, respBody []byte, err error) {
 start := time.Now()
 reqOpts := defaultRequestOptions() // 默认的请求选项
 for _, opt := range options {      // 在reqOpts上应用通过options设置的选项
  err = opt.apply(reqOpts)
  if err != nil {
   return
  }
 }
    ...
}

上面这个Request方法就是我们的工具提供的函数,method、url 因为是必填的就不必再整成Option参数了,其他关于请求的设置都可以通过在调用是使用WithXXX()一系列的函数传参进来。

Request("POST", url, WithTimeout(timeout), WithHeaders(headers), WithData(data))

日志和追踪头信息

我们在发起请求的第一个参数都是 context.Context 类型的上下文参数, 这个意图是为了让你调用时把请求上下文 gin.Context 传递进来,我们好从其中取到一开始种进去的追踪信息,然后设置到要发起的请求的Header中去。

func Request(method string, url string, options ...Option) (httpStatusCode int, respBody []byte, err error) {
    
    ......
    // 在Header中添加追踪信息 把内部服务串起来
 traceId, spanId, _ := util.GetTraceInfoFromCtx(reqOpts.ctx)
 reqOpts.headers["traceid"] = traceId
 reqOpts.headers["spanid"] = spanId
 if len(reqOpts.headers) != 0 { // 设置请求头
  for key, value := range reqOpts.headers {
   req.Header.Add(key, value)
  }
 }
    ......
}

同时因为有了ctx 信息,我们使用项目自己的Logger门面进行日志记录的时候也会把请求的追踪信息一并写到日志信息中去,通过trace、span 信息也能查到项目的一个接口在执行过程中内部发起了哪些API调用?以及得到了什么结果?

func Request(method string, url string, options ...Option) (httpStatusCode int, respBody []byte, err error) {
    
    ......
    // 发起请求
 client := &http.Client{Timeout: reqOpts.timeout}
 resp, err := client.Do(req)
 if err != nil {
  return
 }
 defer resp.Body.Close()
 // 记录请求日志
 dur := time.Since(start).Seconds()
 if dur >= 3 { // 超过 3s 返回, 记一条 Warn 日志
  log.Warn("HTTP_REQUEST_SLOW_LOG", "method", method, "url", url, "body", reqOpts.data, "reply", respBody, "err", err, "dur/ms", dur)
 } else {
  log.Debug("HTTP_REQUEST_DEBUG_LOG", "method", method, "url", url, "body", reqOpts.data, "reply", respBody, "err", err, "dur/ms", dur)
 }
}

连接池的设置

服务间接口调用,维持稳定数量的长连接,对性能非常有帮助,这就需要我们在Go 的 http Client的连接池特性,该特性需要在创建Client时用 http.Transport 进行设置。

关于这部分内容,以及如何在项目中引入对接层把项目与第三方API的对接从自身的核心业务逻辑解耦出来,这部分请扫码订阅专栏阅读完整版。

9614931fe8a9c06d2ce2a5cc309a6931.png

专栏配套的项目有完整的实现代码,订阅后加入项目访问 https://github.com/go-study-lab/go-mall/compare/c9...c10 能查看和运行调试本章节的详细代码。

849433424b2f698a3a99c4765d31a158.png

本专栏分为五大部分,以下前四部分内容已经更新完成‍‍‍

e6f392db65c31984ef8ddd9a267d27a9.png

  • 第一部分介绍让框架变得好用的诸多实战技巧,比如通过自定义日志门面让项目日志更简单易用、支持自动记录请求的追踪信息和程序位置信息、通过自定义Error在实现Go error接口的同时支持给给错误添加错误链,方便追溯错误源头。

  • 第二部分:讲解项目分层架构的设计和划分业务模块的方法和标准,让你以后无论遇到什么项目都能按这套标准自己划分出模块和逻辑分层。后面几个部分均是该部分所讲内容的实践。

  • 第三部分:设计实现一个套支持多平台登录,Token泄露检测、同平台多设备登录互踢功能的用户认证体系,这套用户认证体系既可以在你未来开发产品时直接应用

  • 第四部分:商城app C端接口功能的实现,强化分层架构实现的讲解,这里还会讲解用责任链、策略和模版等设计模式去解决订单结算促销、支付方式支付场景等多种多样的实际问题。

  • 第五部分:单元测试、项目Docker镜像、K8s部署和服务保障相关的一些基础内容和注意事项

扫描上方二维码或者访问 https://xiaobot.net/p/golang 即刻订阅

# 启语(chim)编程语言 启语(chim)是一种现代编程语言,结合了Rust、Go、Swift等语言的优点,支持中英文混合编程,具有内存安全、简洁语法高效的编译模式。为了进一步提升语言能力,启语整合了仓颉MoonBit的优点,打造更强大的AI原生编程语言。 ## 语言特性 ### 1. 所有权系统 chim语言借鉴了Rust的所有权机制,确保内存安全: - 所有权(Ownership):每个值都有一个所有者变量 - 借用(Borrowing):允许通过引用来使用值而不获取所有权 - 生命周期:确保引用的有效性 ### 2. 内存管理 采用类似Go的垃圾回收机制,减轻开发者手动管理内存的负担: - 自动垃圾回收 - 并发安全 - 轻量级线程(goroutine) 结合了Rust的所有权系统Go的垃圾回收机制,既保证了内存安全,又提供了灵活性。 - Safe Chim代码 :默认使用类似Rust的所有权系统进行内存管理,无垃圾回收 - Unsafe Chim代码 :在特定复杂场景下(GUI开发、复杂图结构处理、特定异步编程场景)提供类似Go的垃圾回收机制 - 错误处理 :引入自定义错误类型Result类型别名,提供更强大的错误处理能力 ### 3. 语法简洁性 借鉴Swift的简洁语法,提供更优雅的编程体验: - 类型推断 - 简洁的函数定义 - 可选类型模式匹配 - 结构体方法 ### 4. 编译模式 支持JITAOT两种编译模式,类似Dart: - JIT(即时编译)模式开发阶段使用,提供快速编译热重载 - AOT(预先编译)模式:生产环境使用,提供最优性能 - 混合编译模式:根据不同模块选择合适的编译方式 有关JITAOT编译模式的详细实现,请参阅[JIT_AOT_IMPLEMENTATION.md](JIT_AOT_IMPLEMENTATION.md)文档。 ### 5. 动态装载 支持运行时动态装载模块,提高灵活性模块化程度: - 运行时动态加载模块 - 依赖解析版本管理 - 沙箱执行环境 有关动态装载功能的详细设计与实现,请参阅[DYNAMIC_LOADING_SUMMARY.md](BYQ/DYNAMIC_LOADING_SUMMARY.md)文档。 ### 6. LLVM原生支持 启语(chim)语言提供LLVM原生支持,允许将代码编译为高性能的机器码: - 基于LLVM 16.0.1实现 - 生成优化的机器码,提升程序执行性能 - 支持跨平台编译 - 与现有字节码编译模式并存,可通过命令行参数选择 使用方法:在编译时添加`--llvm`参数启用LLVM代码生成模式。 ```bash byq-compiler --llvm your_source_file.chim ``` 有关LLVM支持的详细实现,请参阅编译器源代码中的[llvm_codegen.rs](BYQ/src/llvm_codegen.rs)文件。 ### 7. 增量发布 支持类似Swift的增量发布功能: - 功能开关(Feature Flags) - 条件编译 - 模块化发布 - A/B测试支持 ### 8. 多语言集成 支持与Python、JavaScript、TypeScript、Dart、MoonBit、仓颉语言的无缝集成: - 直接导入使用其他语言的库 - 自动类型映射转换 - 跨语言函数调用 - 内存管理协同 ### 9. AI原生支持 从设计之初就考虑与AI技术的深度融合,结合仓颉MoonBit的设计理念: - 实时语义补全:基于代码语义理解提供更准确的补全建议 - 静态分析与测试验证:AI辅助的代码静态分析自动化测试 - 内置AI助手Yue:能自动生成代码、文档测试用例 - 声明式Agent开发:提供Agent DSL,支持智能体开发 - AI类型安全:通过类型系统确保AI模型输入输出的正确性 - 轻训练重推理:优化推理性能,简化训练流程 ### 10. 编辑器支持 为了提升开发体验,启语(chim)提供了专门的VS Code扩展,支持中英文关键字同步翻译显示: - 实时显示中英文关键字对照 - 语法高亮增强,区分中英文关键字 - 智能提示,支持中英文关键字互查 - 代码折叠导航 有关编辑器支持的详细信息,请参阅[chim-vscode-extension](chim-vscode-extension)目录。 ### 11. 多语言混合编写支持 支持在CHIM项目中混合编写多种编程语言,包括Go、Python、JavaScript、TypeScript、Dart、Swift、MoonBit、仓颉语言,实现跨语言开发函数调用: - 多语言项目结构支持 - 跨语言函数调用接口 - 统一的构建编译流程 - 多种配置文件格式支持,优先级顺序为:package.chim > config.toml > package.yaml > package.json 有关多语言混合编写支持的详细设计与实现计划,请参阅[MULTI_LANGUAGE_SUPPORT_DESIGN.md](MULTI_LANGUAGE_SUPPORT_DESIGN.md)文档。 ## 变量声明 chim语言提供三种变量声明方式: ### 1. 可变变量 可以修改其值的变量: ```chim var name: type = expr let mut name: type = expr 设 name: type = expr ``` ### 2. 不可变变量 声明后不能修改其值的变量: ```chim let name: type = expr 令 name: type = expr ``` ### 3. 常量 在编译时确定值,程序运行期间不能修改: ```chim const name: type = expr 让 name: type = expr ``` ## 控制流 ### 条件语句 ```chim // 使用英文关键字 if condition { // 条件为真时执行 } else { // 条件为假时执行 } // 使用中文关键字 若 条件 { // 条件为真时执行 } 否则 { // 条件为假时执行 } ``` ### 循环语句 ```chim // for循环 for i in 0..10 { // 循环体 } // 使用中文关键字 环 甲 从 0..10 { // 循环体 } // while循环 while condition { // 循环体 } // 使用中文关键字 当 条件 { // 循环体 } ``` ## 函数定义 chim语言支持中英文混合的函数定义语法,借鉴Swift的简洁性Go的并发特性: ## 完整示例 查看 [integration.chim](examples/integration.chim) 文件了解启语语言整合仓颉MoonBit优点的完整示例。 ### 基本函数定义 ```chim // 使用英文关键字 func functionName(param: type) -> returnType { // 函数体 } // 使用中文关键字 函数 函数名(参数: 类型) -> 返回类型 { // 函数体 } ``` ### 简洁函数定义(单表达式函数) ```chim // 使用英文关键字 func add(a: Int, b: Int) -> Int = a + b // 使用中文关键字 函数 加(a: 整数, b: 整数) -> 整数 = a + b ``` ### 高阶函数 ```chim // 函数作为参数 func applyOperation(a: Int, b: Int, operation: (Int, Int) -> Int) -> Int { return operation(a, b) } // 使用中文关键字 函数 应用操作(甲: 整数, 乙: 整数, 操作: (整数, 整数) -> 整数) -> 整数 { 返回 操作(甲, 乙) } ``` ### 并发函数 借鉴Gogoroutine特性,chim支持轻量级并发: ```chim // 使用英文关键字 go func() { // 并发执行的代码 } // 使用中文关键字 行 函数() { // 并发执行的代码 } ``` ### 异步函数 ```chim // 使用英文关键字 async func fetchData() -> Data { // 异步获取数据 } // 使用中文关键字 异步 函数 获取数据() -> 数据 { // 异步获取数据 } ``` ## 错误处理 chim采用类似Swift的错误处理机制: ```chim // 定义错误类型 enum NetworkError { case invalidURL case noResponse } // 使用中文关键字 枚举 网络错误 { 情况 无效URL 情况 无响应 } // 抛出错误 func fetchData() throws -> Data { // 可能抛出错误的代码 } // 使用中文关键字 函数 获取数据() 抛出 -> 数据 { // 可能抛出错误的代码 } // 处理错误 do { let data = try fetchData() // 处理数据 } catch { // 处理错误 } // 使用中文关键字 做 { 令 数据 = 尝试 获取数据() // 处理数据 } 捕获 { // 处理错误 } ``` ## 类型系统 ### 结构体 ```chim // 使用英文关键字 struct Person { var name: String let age: Int } // 使用中文关键字 结构体 人 { 设 名字: 字符串 令 年龄: 整数 } ``` ### 类 ```chim // 使用英文关键字 class Vehicle { var speed: Int func move() { // 移动逻辑 } } // 使用中文关键字 类 车辆 { 设 速度: 整数 函数 移动() { // 移动逻辑 } } ``` ### 成员访问规则 在成员变量、成员函数成员属性的声明前可以添加以下修饰符: - `private`(中文关键字:`私`):设置成员仅在结构体内可见 - `public`(中文关键字:`公`):设置成员在结构体内外均可见 - `static`(中文关键字:`静`):设置成员为静态成员,只能通过结构体名访问 默认为实例成员,只能由实例变量访问。在实例成员函数中可以引用其他成员,在静态成员函数中只能引用静态成员。 在实例成员函数中可以使用 `this` 变量(中文关键字:`此`),它默认为当前实例的拷贝。 #### 示例 ```chim // 使用英文关键字 struct Person { private var name: String public let age: Int static var population: Int = 0 public func introduce() { print("My name is \(this.name), I'm \(age) years old.") } static func getPopulation() -> Int { return population } } // 使用中文关键字 结构体 人 { 私 设 名字: 字符串 公 令 年龄: 整数 静 设 人口: 整数 = 0 公 函数 介绍() { 打印("我的名字是 \(此.名字),今年 \(年龄) 岁。") } 静 函数 获取人口() -> 整数 { 返回 人口 } } ``` ### 协议/接口 ```chim // 使用英文关键字 protocol Drawable { func draw() } // 使用中文关键字 协议 可绘制 { 函数 绘制() } ``` ## 泛型 ```chim // 使用英文关键字 func swapValues<T>(_ a: inout T, _ b: inout T) { let temp = a a = b b = temp } // 使用中文关键字 函数 交换值<T>(_ 甲: 传入传出 T, _ 乙: 传入传出 T) { 令 临时 = 甲 甲 = 乙 乙 = 临时 } ``` ## 可选类型 ```chim // 使用英文关键字 var optionalValue: Int? = nil // 使用中文关键字 设 可选值: 整数? = 空 // 可选绑定 if let value = optionalValue { // 使用value } // 使用中文关键字 如果 令 值 = 可选值 { // 使用值 } ``` ## 包管理 使用choo包管理管理项目依赖构建配置,配置文件为package.chim。 ``` ## 编译器架构 查看 [BYQ编译器架构设计](BYQ/ARCHITECTURE.md) 了解启语(chim)语言编译器的详细实现。 ## 动态装载功能 启语(chim)语言支持动态装载功能,允许在运行时动态加载执行模块,类似于Java的ClassLoader机制。 ### 特性 - 运行时动态加载CHIM模块文件 - 模块间的依赖解析 - 安全的沙箱执行环境 - 类Java的ClassLoader机制 ### 使用方法 使用 `--dynamic-load` 参数来动态加载并执行模块: ```bash byq-compiler --dynamic-load examples/dynamic_module.chim ``` ### 设计文档 查看 [动态装载功能设计](BYQ/DYNAMIC_LOADING_DESIGN.md) 了解详细实现方案。 ### 实现总结 查看 [动态装载功能实现总结](BYQ/DYNAMIC_LOADING_IMPLEMENTATION_SUMMARY.md) 了解完整的实现细节文件列表。 ## 多语言混合编写支持 启语(chim)语言支持多语言混合编写,允许在同一个项目中使用Go、Python、SwiftTypeScript等多种编程语言。 ### 特性 - 支持多种配置文件格式(YAML、TOML、JSONpackage.chim) - 自动检测并解析项目配置文件 - 支持Go、Python、SwiftTypeScript语言的编译执行 - 跨语言函数调用接口 ### 使用方法 目前多语言混合编写功能仍在开发中,敬请期待完整版本。 ### 设计文档 查看 [多语言混合编写支持设计](MULTI_LANGUAGE_SUPPORT_DESIGN.md) 了解详细实现方案。 ### 示例项目 查看 [多语言混合项目示例](examples/multi_language_project) 了解如何在项目中使用多语言混合编写功能。 ### 演示脚本 运行 [demo_multi_language.bat](demo_multi_language.bat) 查看多语言混合编写功能的演示。 ## 平台AI集成功能 启语(chim)语言现已支持与Gitee AIGitCode AI的深度集成,可以利用这些平台的AI能力来增强开发体验。 ### 特性 - 与Gitee AIGitCode AI的基本API集成 - 将平台AI功能作为Yue助手的后端服务 - 利用AI自动审核代码、优化代码、甚至自动修改代码 - 支持跨平台AI服务调用 ### 使用方法 平台AI集成功能可通过Yue助手调用,示例代码请参考 [platform_ai_integration_example.chim](examples/platform_ai_integration_example.chim)。 ### 设计文档 查看 [平台AI集成功能设计](PLATFORM_AI_INTEGRATION_DESIGN.md) 了解详细实现方案。 ## 与其他编程语言的对比 查看 [LANGUAGE_COMPARISON.md](LANGUAGE_COMPARISON.md) 了解启语(chim)语言与其他编程语言(C、C++、Rust、Go、Swift、MoonBit、仓颉)的详细对比分析。 ## 多语言混合项目的性能优化建议 在构建多语言混合项目时,合理分配不同语言的使用场景可以显著提升项目的整体性能开发效率: ### 关键性能部分 - **推荐语言**: Rust、Go - **适用场景**: 高性能计算、系统编程、并发处理 - **优势**: 高性能、内存安全、优秀的并发支持 ### 业务逻辑部分 - **推荐语言**: Python、TypeScript - **适用场景**: 业务逻辑实现、数据处理、Web开发 - **优势**: 易读易维护、丰富的库生态、快速开发 通过这种分工策略,可以在保证关键性能的同时,提高业务逻辑部分的开发效率可维护性。
09-07
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值