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)。清洁架构的领域层应该是完全独立的,不依赖任何框架、第三方库或基础设施细节。
正确的做法应该是:
- 在domain层定义抽象的仓储接口(你已经做了
PolicyRepo),接口里只暴露领域相关的操作,不涉及任何Redis的具体方法。 - 把Redis的具体实现完全放在基础设施层(比如rdb包),包括
Database结构体和Redis客户端的初始化。 - 仓储层(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
相关产品推荐
相关产品推荐

