Golang中的六边形架构与接口污染问题探讨

Golang中的六边形架构与接口污染问题探讨 我正在一个项目中,使用端口与适配器概念的规范版本实现,我们只有三层:模型层(实体、DTO等)、领域层(业务逻辑)、基础设施层(处理器、PostgreSQL)。

但是,在领域层中,我们有许多臃肿的接口,包含了许多实际上可能是辅助函数的长方法名。这给我带来了糟糕的开发体验,因为这些接口必须被实现,感觉就像不必要的官僚主义或预防性抽象。

这种在Go中采用六边形架构的传统方法正确吗?我有一年的Go经验。

2 回复

你好 @adrianolmedo

臃肿的接口是 Java 风格的。我不认为六边形架构(HA)要求接口必须是臃肿的。HA 的核心是关注点分离,因此只要你实现了这个目标,就应该没问题。所以我认为在 Go 中使用 HA 没有问题。问题似乎更多在于如何让接口更符合 Go 的风格(也就是说,尽可能将它们拆分成更小的接口)。

更多关于Golang中的六边形架构与接口污染问题探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go中实现六边形架构时,接口臃肿确实是常见问题。传统方法往往过度抽象,导致接口包含过多方法。更符合Go习惯的做法是定义小而专注的接口,让接口自然演进。

示例代码展示更简洁的接口设计:

// 传统臃肿接口
type UserRepository interface {
    Create(ctx context.Context, user *User) error
    Update(ctx context.Context, user *User) error
    Delete(ctx context.Context, id string) error
    FindByID(ctx context.Context, id string) (*User, error)
    FindByEmail(ctx context.Context, email string) (*User, error)
    FindAll(ctx context.Context, filter Filter) ([]*User, error)
    Count(ctx context.Context, filter Filter) (int, error)
    // 更多方法...
}

// 改进后的小接口
type UserSaver interface {
    Save(ctx context.Context, user *User) error
}

type UserFinder interface {
    FindByID(ctx context.Context, id string) (*User, error)
    FindByEmail(ctx context.Context, email string) (*User, error)
}

type UserRemover interface {
    Remove(ctx context.Context, id string) error
}

// 领域层使用具体的小接口
type UserService struct {
    saver   UserSaver
    finder  UserFinder
    remover UserRemover
}

func (s *UserService) RegisterUser(ctx context.Context, email, name string) error {
    user := &User{Email: email, Name: name}
    return s.saver.Save(ctx, user)
}

基础设施层实现可以按需组合:

// PostgreSQL实现
type PostgresUserStore struct {
    db *sql.DB
}

func (p *PostgresUserStore) Save(ctx context.Context, user *User) error {
    // 具体实现
    _, err := p.db.ExecContext(ctx, 
        "INSERT INTO users (id, email, name) VALUES ($1, $2, $3)",
        user.ID, user.Email, user.Name)
    return err
}

func (p *PostgresUserStore) FindByID(ctx context.Context, id string) (*User, error) {
    // 具体实现
    row := p.db.QueryRowContext(ctx, 
        "SELECT id, email, name FROM users WHERE id = $1", id)
    // 解析结果...
}

// 实现所有需要的接口
var _ UserSaver = (*PostgresUserStore)(nil)
var _ UserFinder = (*PostgresUserStore)(nil)
var _ UserRemover = (*PostgresUserStore)(nil)

对于辅助函数,可以提取到独立的包或结构体:

// 将辅助逻辑提取到独立组件
type UserValidator struct{}

func (v *UserValidator) ValidateEmail(email string) error {
    if !strings.Contains(email, "@") {
        return errors.New("invalid email format")
    }
    return nil
}

func (v *UserValidator) ValidateName(name string) error {
    if len(name) < 2 {
        return errors.New("name too short")
    }
    return nil
}

这种设计减少了接口方法数量,提高了代码的可测试性和可维护性。接口应该由消费者定义,而不是提供者。只有当领域层真正需要某个方法时,才在接口中声明它。

回到顶部