Golang中如何优雅处理多个服务的相似但略有差异的API类型

Golang中如何优雅处理多个服务的相似但略有差异的API类型 如果我要编写一个网络服务器来处理例如 GitHub、GitLab 和 Bitbucket 的仓库,有一些 Go 包会返回代表特定平台对象的自定义类型。我不确定将这些第三方包的对象转换为具有一些共同属性的对象的最佳方法是什么。

到目前为止,我编写了一个接口、符合每个接口的客户端,这些客户端本质上只是一个薄包装层,通过一个循环将包对象转换为共享对象。虽然这没问题,但维护起来感觉很繁琐。我是否错过了更好的方法?

1 回复

更多关于Golang中如何优雅处理多个服务的相似但略有差异的API类型的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


对于处理多个服务的相似但略有差异的API类型,可以考虑使用组合和嵌入的方式减少重复代码。以下是一个示例实现:

package main

import (
	"fmt"
)

// 通用接口定义
type Repository interface {
	GetID() string
	GetName() string
	GetOwner() string
}

// 通用结构体
type CommonRepo struct {
	ID    string
	Name  string
	Owner string
}

func (r *CommonRepo) GetID() string    { return r.ID }
func (r *CommonRepo) GetName() string  { return r.Name }
func (r *CommonRepo) GetOwner() string { return r.Owner }

// GitHub 特定类型
type GitHubRepo struct {
	RepoID   int    `json:"id"`
	FullName string `json:"full_name"`
	Owner    struct {
		Login string `json:"login"`
	} `json:"owner"`
}

// 嵌入 CommonRepo 并实现转换逻辑
type GitHubAdapter struct {
	CommonRepo
}

func NewGitHubAdapter(ghRepo *GitHubRepo) *GitHubAdapter {
	return &GitHubAdapter{
		CommonRepo: CommonRepo{
			ID:    fmt.Sprintf("%d", ghRepo.RepoID),
			Name:  ghRepo.FullName,
			Owner: ghRepo.Owner.Login,
		},
	}
}

// GitLab 特定类型
type GitLabRepo struct {
	ID        int    `json:"id"`
	Path      string `json:"path_with_namespace"`
	Namespace struct {
		Path string `json:"path"`
	} `json:"namespace"`
}

// GitLab 适配器
type GitLabAdapter struct {
	CommonRepo
}

func NewGitLabAdapter(glRepo *GitLabRepo) *GitLabAdapter {
	return &GitLabAdapter{
		CommonRepo: CommonRepo{
			ID:    fmt.Sprintf("%d", glRepo.ID),
			Name:  glRepo.Path,
			Owner: glRepo.Namespace.Path,
		},
	}
}

// 使用示例
func processRepositories(repos []Repository) {
	for _, repo := range repos {
		fmt.Printf("ID: %s, Name: %s, Owner: %s\n", 
			repo.GetID(), repo.GetName(), repo.GetOwner())
	}
}

func main() {
	// 模拟从不同服务获取的数据
	ghRepo := &GitHubRepo{
		RepoID:   123,
		FullName: "octocat/Hello-World",
		Owner: struct {
			Login string `json:"login"`
		}{Login: "octocat"},
	}

	glRepo := &GitLabRepo{
		ID:   456,
		Path: "gitlab-org/gitlab-ce",
		Namespace: struct {
			Path string `json:"path"`
		}{Path: "gitlab-org"},
	}

	// 创建适配器
	adapters := []Repository{
		NewGitHubAdapter(ghRepo),
		NewGitLabAdapter(glRepo),
	}

	// 统一处理
	processRepositories(adapters)
}

另一种方法是使用函数式选项模式,减少重复的转换逻辑:

package main

import (
	"fmt"
)

// 通用配置
type RepoConfig struct {
	ID    string
	Name  string
	Owner string
}

type RepoOption func(*RepoConfig)

func WithID(id string) RepoOption {
	return func(rc *RepoConfig) {
		rc.ID = id
	}
}

func WithName(name string) RepoOption {
	return func(rc *RepoConfig) {
		rc.Name = name
	}
}

func WithOwner(owner string) RepoOption {
	return func(rc *RepoConfig) {
		rc.Owner = owner
	}
}

// 通用适配器
type UnifiedRepo struct {
	config *RepoConfig
}

func (ur *UnifiedRepo) GetID() string    { return ur.config.ID }
func (ur *UnifiedRepo) GetName() string  { return ur.config.Name }
func (ur *UnifiedRepo) GetOwner() string { return ur.config.Owner }

func NewUnifiedRepo(opts ...RepoOption) *UnifiedRepo {
	config := &RepoConfig{}
	for _, opt := range opts {
		opt(config)
	}
	return &UnifiedRepo{config: config}
}

// 转换函数
func FromGitHub(ghRepo *GitHubRepo) *UnifiedRepo {
	return NewUnifiedRepo(
		WithID(fmt.Sprintf("%d", ghRepo.RepoID)),
		WithName(ghRepo.FullName),
		WithOwner(ghRepo.Owner.Login),
	)
}

func FromGitLab(glRepo *GitLabRepo) *UnifiedRepo {
	return NewUnifiedRepo(
		WithID(fmt.Sprintf("%d", glRepo.ID)),
		WithName(glRepo.Path),
		WithOwner(glRepo.Namespace.Path),
	)
}

func main() {
	ghRepo := &GitHubRepo{
		RepoID:   123,
		FullName: "octocat/Hello-World",
		Owner: struct {
			Login string `json:"login"`
		}{Login: "octocat"},
	}

	glRepo := &GitLabRepo{
		ID:   456,
		Path: "gitlab-org/gitlab-ce",
		Namespace: struct {
			Path string `json:"path"`
		}{Path: "gitlab-org"},
	}

	repos := []Repository{
		FromGitHub(ghRepo),
		FromGitLab(glRepo),
	}

	for _, repo := range repos {
		fmt.Printf("ID: %s, Name: %s, Owner: %s\n",
			repo.GetID(), repo.GetName(), repo.GetOwner())
	}
}

这两种方法都能有效处理多个服务的API类型差异,同时保持代码的可维护性和扩展性。

回到顶部