在 DDD (clean/hexagonal) 架构中处理数据库连接和环境配置
Handling DB connections and Env config in DDD (clean/hexagonal) architecture
虽然我掌握了总体思路,但我很难看到管理配置环境和管理数据库连接的最佳实践。
含义:
如果我有存储库(例如 PostgreSQL),我应该将 NewRepository 函数传递给数据库配置吗?它不会以某种方式对架构原则(维护、可测试性等)产生不利影响吗?
我们如何处理 defer db.Close() 之类的事情?
我的意思是,我们显然希望它相对于 scope main 函数延迟,所以将该代码移动到存储库中是有问题的 "class"(除非有一种方法可以使用 Context? )
另一方面,在主作用域中调用 NewRepository,然后让数据库处理它之外的连接感觉有点奇怪。
我发现的大多数示例都使用了 main 函数,因此很简单。问题是在使用 DDD (clean/hexagonal) 架构时如何正确地做到这一点?特别是这样所有的片段都是 "pluggable" 而不必更改代码 "around it".
这里是我整理的一个例子,这里有没有违反ddd模式的一些原则?或者这些事情实际上是如何完成的?
1.我不应该在存储库本身内部处理 defer db.Close() 吗?也许使用 Context 我可以将它推迟到与主要功能范围相关但在存储库本身内部?
2。我真的应该将配置传递到 NewRepository 吗?
pkg/main.go :
func main() {
// get configuration stucts via .env file
configuration, err := config.NewConfig()
if err != nil {
panic(err)
}
postgresRepo, err := postgres.NewRepository(configuration.Database)
defer postgresRepo.DB.Close()
myService := autocomplete.NewService(postgresRepo)
handler := rest.NewHandler(myService)
...
...
...
}
pkg/config/config.go:
// Config is a struct that contains configuration variables
type Config struct {
Environment string
Port string
Database *Database
}
// Database is a struct that contains DB's configuration variables
type Database struct {
Host string
Port string
User string
DB string
Password string
}
// NewConfig creates a new Config struct
func NewConfig() (*Config, error) {
env.CheckDotEnv()
port := env.MustGet("PORT")
// set default PORT if missing
if port == "" {
port = "3000"
}
return &Config{
Environment: env.MustGet("ENV"),
Port: port,
Database: &Database{
Host: env.MustGet("DATABASE_HOST"),
Port: env.MustGet("DATABASE_PORT"),
User: env.MustGet("DATABASE_USER"),
DB: env.MustGet("DATABASE_DB"),
Password: env.MustGet("DATABASE_PASSWORD"),
},
}, nil
}
不要将数据库配置传递到您的存储库,而是尝试传递数据库连接。例如:
func main() {
db, err := sql.Open("postgres", "...")
if err != nil {
log.Fatal(err)
}
defer db.Close()
repo := postgres.NewAutocompleteRepo(db)
svc := autocomplete.NewService(repo)
handler := autocomplete.NewHTTPHandler(svc)
}
这将把连接到存储库之外的数据库的责任留给存储库,并允许更容易的测试。
虽然我掌握了总体思路,但我很难看到管理配置环境和管理数据库连接的最佳实践。
含义:
如果我有存储库(例如 PostgreSQL),我应该将 NewRepository 函数传递给数据库配置吗?它不会以某种方式对架构原则(维护、可测试性等)产生不利影响吗?
我们如何处理 defer db.Close() 之类的事情?
我的意思是,我们显然希望它相对于 scope main 函数延迟,所以将该代码移动到存储库中是有问题的 "class"(除非有一种方法可以使用 Context? )
另一方面,在主作用域中调用 NewRepository,然后让数据库处理它之外的连接感觉有点奇怪。
我发现的大多数示例都使用了 main 函数,因此很简单。问题是在使用 DDD (clean/hexagonal) 架构时如何正确地做到这一点?特别是这样所有的片段都是 "pluggable" 而不必更改代码 "around it".
这里是我整理的一个例子,这里有没有违反ddd模式的一些原则?或者这些事情实际上是如何完成的?
1.我不应该在存储库本身内部处理 defer db.Close() 吗?也许使用 Context 我可以将它推迟到与主要功能范围相关但在存储库本身内部?
2。我真的应该将配置传递到 NewRepository 吗?
pkg/main.go :
func main() {
// get configuration stucts via .env file
configuration, err := config.NewConfig()
if err != nil {
panic(err)
}
postgresRepo, err := postgres.NewRepository(configuration.Database)
defer postgresRepo.DB.Close()
myService := autocomplete.NewService(postgresRepo)
handler := rest.NewHandler(myService)
...
...
...
}
pkg/config/config.go:
// Config is a struct that contains configuration variables
type Config struct {
Environment string
Port string
Database *Database
}
// Database is a struct that contains DB's configuration variables
type Database struct {
Host string
Port string
User string
DB string
Password string
}
// NewConfig creates a new Config struct
func NewConfig() (*Config, error) {
env.CheckDotEnv()
port := env.MustGet("PORT")
// set default PORT if missing
if port == "" {
port = "3000"
}
return &Config{
Environment: env.MustGet("ENV"),
Port: port,
Database: &Database{
Host: env.MustGet("DATABASE_HOST"),
Port: env.MustGet("DATABASE_PORT"),
User: env.MustGet("DATABASE_USER"),
DB: env.MustGet("DATABASE_DB"),
Password: env.MustGet("DATABASE_PASSWORD"),
},
}, nil
}
不要将数据库配置传递到您的存储库,而是尝试传递数据库连接。例如:
func main() {
db, err := sql.Open("postgres", "...")
if err != nil {
log.Fatal(err)
}
defer db.Close()
repo := postgres.NewAutocompleteRepo(db)
svc := autocomplete.NewService(repo)
handler := autocomplete.NewHTTPHandler(svc)
}
这将把连接到存储库之外的数据库的责任留给存储库,并允许更容易的测试。