引言

使用go做开发, 有一些会经常用到的三方库, 今天来盘点一下;
以下不分先后分类 祝各位同学看个开心.

优秀的第三方库

cobra

cobra : 如果你想要使用go做一个稍复杂的命令行工具, 那么cobra应该是一个不错的选择;

快速入门看他的官方文档即可: https://github.com/spf13/cobra/blob/main/site/content/user_guide.md

viper

在正式的项目里, 通常都有大量的配置需要在启动时或者运行时注入, 那么配置加载是必不可少的;

直接按他README里介绍的这几句代码就明白他的主要功能了:

viper.SetConfigName("config") // name of config file (without extension)
viper.SetConfigType("yaml") // REQUIRED if the config file does not have the extension in the name
viper.AddConfigPath("/etc/appname/")   // path to look for the config file in
viper.AddConfigPath("$HOME/.appname")  // call multiple times to add many search paths
viper.AddConfigPath(".")               // optionally look for config in the working directory
err := viper.ReadInConfig() // Find and read the config file
if err != nil { // Handle errors reading the config file
	panic(fmt.Errorf("fatal error config file: %w", err))
}

另外viper还支持配置热加载(即监听配置文件变化), 这样就可以在程序运行时根据配置变化来实现不同的逻辑:

viper.OnConfigChange(func(e fsnotify.Event) {
	fmt.Println("Config file changed:", e.Name)
})
viper.WatchConfig()

lo

前端界非常有名的工具函数库: lodash , go这块也有类似的一个:
lo
在go支持泛型后, 这个库更加好用.

什么数组去重, slice转map等这种之前都要一堆for + map来手动实现的, 现在直接使用lo吧, 方便快捷, 使你专注主要逻辑, 示例:

// 数组去重
names := lo.Uniq([]string{"Samuel", "John", "Samuel"})
// []string{"Samuel", "John"}

singleflight

singleflight 如果你的程序有高并发操作, 那这个可能会用的上.
比如你有一个业务接口, 并发调用量很大, 但是很多调用可能都是重复的(参数啥的完全一样), 你期望的是 重复函数的并发调用合并为一次, 这样对底层的数据库资源可以减少访问;

示例:

package main

import (
	"fmt"
	"golang.org/x/sync/singleflight"
	"time"
)

func getData(id int64) string {
	fmt.Println("query...")
	time.Sleep(10 * time.Second) // 模拟一个比较耗时的操作,比如可以是去mysql里做一个复杂的查询操作
	return "real data"
}

func main() {
	g := new(singleflight.Group)

	// 第1次调用
	go func() {
		v1, _, shared := g.Do("getData", func() (interface{}, error) {
			ret := getData(1)
			return ret, nil
		})
		fmt.Printf("1st call: v1:%v, shared:%v\n", v1, shared)
	}()

	time.Sleep(2 * time.Second)

	// 第2次调用(第1次调用已开始但未结束)
	v2, _, shared := g.Do("getData", func() (interface{}, error) {
		ret := getData(1)
		return ret, nil
	})
	fmt.Printf("2nd call: v2:%v, shared:%v\n", v2, shared)
}

执行结果:

query…
1st call: v1:real data, shared:true
2nd call: v2:real data, shared:true

从输出可以看到getData函数只执行了一次(只打印了一次query...),但是两次调用都拿到了结果(real data)。

这就是singleflight包提供给我们的能力,避免了同时执行重复的函数

wire

wire: “Compile-time Dependency Injection for Go”, 编译时的依赖注入工具, 无性能损耗;
快速入门看其官方示例即可: https://github.com/google/wire/blob/main/_tutorial/README.md

newc

[newc] https://github.com/Bin-Huang/newc 是一个通过 go generate 指令生成"构造函数"的工具; 看个示例立刻明白:

//go:generate newc
type UserService struct {
	baseService
	userRepository *repositories.UserRepository
	proRepository  *repositories.ProRepository
}

如果你用的Golang或者VsCode之类的集成开发工具, 会出现
run go generate ./... | run go generate 之类的按钮, 或者你可以使用命令行:
go generate ./...

在一切正常的情况下, 你会得到和相应文件同级的一个文件: “constructor_gen.go”

// Code generated by github.com/Bin-Huang/newc; DO NOT EDIT.

// NewUserService Create a new UserService
func NewUserService(baseService baseService, userRepository *repositories.UserRepository, proRepository *repositories.ProRepository) *UserService {
	return &UserService{
		baseService:    baseService,
		userRepository: userRepository,
		proRepository:  proRepository,
	}
}

是不是很方便? 再也不用写NewXXX(…)这种无聊的函数了, 关键是结构体多一个成员(通常是其他依赖), 很容易忘记在NewXXX里添加对应的参数及实现;

不过目前的newc还不是很完美,不过一般的情况也够用了, 实现不行就自己手写NewXXX(…);

作者建议, 如果要团队内统一规范, 推荐这样:

//go:generate go run github.com/Bin-Huang/newc@v0.8.3
type UserService struct {
	baseService
	userRepository *repositories.UserRepository
	proRepository  *repositories.ProRepository
}

笔者在此基础上改造了下, 使得init方法支持返回error:
https://github.com/GerryLon/newc, 欢迎使用:
在这里插入图片描述

wire+newc

看了上面的newc, 有会可能会说, 这手写也行, 没必要引入一个新的工具进来, 增加团队成员的理解成本;
一开始我也觉得是这样, 直到我看到这篇文章: Wire:Go最优雅的依赖注入工具
后, 我才觉得引用newc太有必要了:
我要完全自动管理依赖, 不要人为管理这些依赖(自己也有手动写NewXXX导致依赖为nil的情况…)

不定期更新, 欢迎补充指正

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐