You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Golang清洁架构中领域层集中定义服务结构体的合理性问询

清洁架构下Golang微服务中Redis客户端结构体的分层困惑

我正在用清洁架构开发Golang微服务,现在对将Redis初始化用的核心结构体定义在domain层的做法是否合适感到困惑。我觉得自己的实现更解耦、更贴合清洁架构的领域层原则,但没看到开源项目这么做,大多是在各层本地定义结构体再在适配器层复用,这种常规做法反而增加耦合,违背清洁架构的依赖规则,希望得到指正和意见。

我的实现方式

// domain/redis.go
package domain
import "github.com/go-redis/redis/v8"

type Database struct {
    Client *redis.Client
}

// domain/policy.go
package domain

type Policy struct {
    Name string `json:"name" binding:"required"`
    Data string `json:"data"`
}

type PolicyRepo interface {
    SavePolicy(policy *Policy) error
    GetPolicy(name string) (*Policy, error)
    GetField(name string, fields ...string) ([]interface{}, error)
    UpdatePolicy(name string, fields ...string) error
}

// repository/policy.go
package repository
import "context"
import "your-project-path/domain"

type policyRepo struct {
    db *domain.Database
}

func NewPolicyRepo(db *domain.Database) domain.PolicyRepo {
    return &policyRepo{
        db: db,
    }
}

func (repo *policyRepo) SavePolicy(policy *domain.Policy) error {
    if err := repo.db.Client.HSet(context.Background(), "domain:"+policy.Name, map[string]interface{}{
        "name": policy.Name,
        "data": policy.Data,
    }).Err(); err != nil {
        return err
    }
    return nil
}
// 其他接口方法实现...

// rdb/redis.go
package rdb
import "fmt"
import "your-project-path/domain"
import "github.com/go-redis/redis/v8"

func NewRedisDB(host string, port string) (*domain.Database, error) {
    client := redis.NewClient(&redis.Options{
        Addr:     fmt.Sprintf("%s:%s", host, port),
        Password: "",
        DB:       0,
    })
    
    // 错误处理逻辑
    return &domain.Database{
        Client: client,
    }, nil
}

// adapter/adapter.go
package adapter
import "your-project-path/domain"
import "your-project-path/repository"

func Init(redisDB *domain.Database) {
    // 初始化仓储
    policyRepo := repository.NewPolicyRepo(redisDB)
    // ...
    // 将仓储注入用例层
}

// main.go
package main
import "log"
import "your-project-path/rdb"
import "your-project-path/adapter"

func main() {
    rdb, err := rdb.NewRedisDB(conf.REDISHOST, conf.REDISPORT)
    if err != nil {
        log.Fatal("Redis服务启动失败")
    }
    adapter.Init(rdb)
}

常规实现方式(我认为有耦合问题)

// services/redis/redis.go
package rdb
import "fmt"
import "github.com/go-redis/redis/v8"

type Database struct {
    Client *redis.Client
}

func NewRedisDB(host string, port string) (*Database, error) {
    client := redis.NewClient(&redis.Options{
        Addr:     fmt.Sprintf("%s:%s", host, port),
        Password: "",
        DB:       0,
    })

    // 错误处理逻辑
    return &Database{
        Client: client,
    }, nil
}

// repository/policy.go
package repository
import "your-project-path/domain"
import "your-project-path/services/redis"

type policyRepo struct {
    db *rdb.Database
}

func NewPolicyRepo(db *rdb.Database) domain.PolicyRepo {
    return &policyRepo{
        db: db,
    }
}

// adapter/adapter.go
package adapter
import "your-project-path/services/redis"
import "your-project-path/repository"

func Init(redisDB *rdb.Database) {
    // 初始化仓储
    policyRepo := repository.NewPolicyRepo(redisDB)
    // ...
    // 将仓储注入用例层
}

这种常规做法会导致仓储层直接依赖基础设施层(rdb包)的结构体,违反了清洁架构**依赖方向应该向内(指向领域层)**的规则——领域层不应该依赖外层,而外层依赖领域层,但这里仓储层反而依赖了最外层的基础设施层,耦合度更高。

分析与建议

你的思路方向是对的,但把*redis.Client直接放在domain层的Database结构体里有个核心问题:领域层不应该依赖任何外部库(比如go-redis)。清洁架构的领域层应该是完全独立的,不依赖任何框架、第三方库或基础设施细节。

正确的做法应该是:

  1. 在domain层定义抽象的仓储接口(你已经做了PolicyRepo),接口里只暴露领域相关的操作,不涉及任何Redis的具体方法。
  2. 把Redis的具体实现完全放在基础设施层(比如rdb包),包括Database结构体和Redis客户端的初始化。
  3. 仓储层(repository)依赖domain层的接口和领域实体,同时通过依赖注入接收基础设施层的Redis客户端实例,让仓储层只依赖抽象而非具体的Redis结构体。

调整后的示例大概是这样:

// domain/policy.go(领域层,无外部依赖)
package domain

type Policy struct {
    Name string `json:"name" binding:"required"`
    Data string `json:"data"`
}

type PolicyRepo interface {
    SavePolicy(policy *Policy) error
    GetPolicy(name string) (*Policy, error)
    // 其他领域操作方法
}

// rdb/redis.go(基础设施层,处理Redis细节)
package rdb
import "fmt"
import "github.com/go-redis/redis/v8"

type RedisClient struct {
    client *redis.Client
}

func NewRedisClient(host, port string) (*RedisClient, error) {
    c := redis.NewClient(&redis.Options{
        Addr: fmt.Sprintf("%s:%s", host, port),
    })
    // 错误处理
    return &RedisClient{client: c}, nil
}

// repository/policy_repo.go(仓储层,依赖领域接口和基础设施的Redis客户端)
package repository
import "context"
import "your-project-path/domain"
import "your-project-path/rdb"

type policyRepo struct {
    redis *rdb.RedisClient
}

func NewPolicyRepo(redis *rdb.RedisClient) domain.PolicyRepo {
    return &policyRepo{redis: redis}
}

func (r *policyRepo) SavePolicy(p *domain.Policy) error {
    return r.redis.client.HSet(context.Background(), "policy:"+p.Name, map[string]interface{}{
        "name": p.Name,
        "data": p.Data,
    }).Err()
}
// 其他接口实现...

这样调整后:

  • 领域层完全独立,不依赖任何外部库,符合清洁架构的核心要求。
  • 仓储层依赖领域层的接口(向内依赖),同时接收基础设施层的具体实现(通过依赖注入,避免硬编码依赖)。
  • 基础设施层的细节被封装,更换存储方式(比如从Redis换成MySQL)时,只需要修改基础设施层和仓储层的实现,领域层和用例层完全不需要改动。

你最初的想法是对的——要避免外层结构体被内层依赖,但错在把外部库的类型放到了领域层。领域层应该只放领域核心的实体、值对象和抽象接口,任何与具体技术实现相关的内容都应该放在外层的基础设施层。


内容的提问来源于stack exchange,提问作者DrusePstrago

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 07:48:12