当我们开始设计和开发一个应用程序时,如何将各种操作逻辑有效地组织和分配到不同的部分,并使其更加清晰和易于维护?领域驱动设计(DDD)的四层模型提供了一种很好的解决方案。在这篇文章中,我们将介绍DDD四层架构模型的作用以及如何使用它来构建一个高效的应用程序。
什么是DDD四层架构模型?
DDD四层模型是领域驱动设计中的一个重要概念,它将一个复杂的应用程序分解成四个层次。这些层次是:
-
用户界面层(Interface Layer)
-
应用服务层(Application Service Layer)
-
领域服务层(Domain Service Layer)
-
基础设施层(Infrastructure Layer)
每个层次都有自己的作用和职责,可以帮助开发者更好地组织和分配应用程序的逻辑,从而实现更好的可维护性和可扩展性。
用户界面层
接口层提供了与用户交互的接口,包括用户界面和服务接口。它主要负责展示领域对象的状态和行为,并向用户提供具体的业务服务。用户界面层与下一层领域服务层一起构成了应用程序的上层。
应用服务层
应用服务层是用户界面层和领域层之间的桥梁,主要负责将用户请求转化成领域模型的操作,协调领域服务层的工作,处理或者捕获业务逻辑上的异常,并返回处理结果给用户界面层。
应用服务层中的业务逻辑和规则并不是具体与任何一种技术实现相关,因此它们也更容易进行复用和测试。应用服务层中还需要处理程序的事务,确保数据的一致性和可靠性。
领域服务层
领域服务层是整个应用程序最核心也是最重要的一层。在这一层中,需要处理业务逻辑,建立领域模型和实现各种领域对象。
其中,领域模型是整个领域驱动设计的核心,它指代了抽象的、具有业务含义的领域概念及其之间的关系。领域模型中定义了各种领域对象,包括实体(Entity)、值对象(Value Object)、聚合根(Aggregate Root)、工厂(Factory)
等。
在领域服务层中,需要遵循DDD的一些设计原则,例如SRP、高内聚、低耦合、封装性、多态性
等。只有这样,才能使得领域模型的设计更加清晰、可扩展和可维护。
基础设施层
基础设施层是应用程序中最底层的一层,它主要处理底层的基础设施,例如数据库、消息队列、缓存等。在这一层中,需要将实现与领域服务层和应用服务层
分离,这样就可以保持领域服务层的纯洁性,以减少技术影响对领域逻辑的干扰。
基础设施层中也需要遵循SRP的原则,让不同的组件或类分别负责不同的职责。同时,还需要保证基础设施层与上一层之间的接口清晰、稳定,以确保不同层之间的交互能够顺畅实现。
实战项目
代码讲解
注册路由
文件位置:main.go
func main() {
dbdriver := os.Getenv("DB_DRIVER")
host := os.Getenv("DB_HOST")
password := os.Getenv("DB_PASSWORD")
user := os.Getenv("DB_USER")
dbname := os.Getenv("DB_NAME")
port := os.Getenv("DB_PORT")
//redis details
redis_host := os.Getenv("REDIS_HOST")
redis_port := os.Getenv("REDIS_PORT")
redis_password := os.Getenv("REDIS_PASSWORD")
services, err := persistence.NewRepositories(dbdriver, user, password, port, host, dbname)
if err != nil {
panic(err)
}
defer services.Close()
services.Automigrate()
redisService, err := auth.NewRedisDB(redis_host, redis_port, redis_password)
if err != nil {
log.Fatal(err)
}
tk := auth.NewToken()
users := interfaces.NewUsers(services.User, redisService.Auth, tk)
r := gin.Default()
r.Use(middleware.CORSMiddleware()) //For CORS
//user routes
r.POST("/users", users.SaveUser)
app_port := os.Getenv("PORT") //using heroku host
if app_port == "" {
app_port = "8888" //localhost
}
log.Fatal(r.Run(":" + app_port))
}
首先persistence.NewRepositories
会实例化出services,services为基础设施层infrastructure与domain结合的实例。
将services.User,与redisService.Auth, tk 传入interfaces层的NewUsers,获取到users类的实例对象。
r.POST("/users", users.SaveUser)
通过users.SaveUser
与路由绑定,从而完成一个接口的编写。
用户界面层Interface
我们看看 Interface users.SaveUser 做了什么
type Users struct {
us application.UserAppInterface
rd auth.AuthInterface
tk auth.TokenInterface
}
//Users constructor
func NewUsers(us application.UserAppInterface, rd auth.AuthInterface, tk auth.TokenInterface) *Users {
return &Users{
us: us,
rd: rd,
tk: tk,
}
}
func (s *Users) SaveUser(c *gin.Context) {
var user entity.User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusUnprocessableEntity, gin.H{
"invalid_json": "invalid json",
})
return
}
//validate the request:
validateErr := user.Validate("")
if len(validateErr) > 0 {
c.JSON(http.StatusUnprocessableEntity, validateErr)
return
}
newUser, err := s.us.SaveUser(&user)
if err != nil {
c.JSON(http.StatusInternalServerError, err)
return
}
c.JSON(http.StatusCreated, newUser.PublicUser())
}
Interface users.SaveUser 主要是通过前面main 函数调用NewUsers,将application``中的us application.UserAppInterface
,基础设施层infrastructure的rd auth.AuthInterface,tk auth.TokenInterface
方法整合成具体的业务逻辑。
应用服务层
package application
import (
"dddd/domain/entity"
"dddd/domain/repository"
)
type userApp struct {
us repository.UserRepository
}
// UserApp implements the UserAppInterface
var _ UserAppInterface = &userApp{}
type UserAppInterface interface {
SaveUser(*entity.User) (*entity.User, map[string]string)
GetUsers() ([]entity.User, error)
GetUser(uint64) (*entity.User, error)
GetUserByEmailAndPassword(*entity.User) (*entity.User, map[string]string)
}
func (u *userApp) SaveUser(user *entity.User) (*entity.User, map[string]string) {
return u.us.SaveUser(user)
}
func (u *userApp) GetUser(userId uint64) (*entity.User, error) {
return u.us.GetUser(userId)
}
func (u *userApp) GetUsers() ([]entity.User, error) {
return u.us.GetUsers()
}
func (u *userApp) GetUserByEmailAndPassword(user *entity.User) (*entity.User, map[string]string) {
return u.us.GetUserByEmailAndPassword(user)
}
可以看到应用层这边其实没有做什么具体的事情,只是请求转化成领域模型的操作,协调领域服务层的工作,处理或者捕获业务逻辑上的异常,并返回处理结果给用户界面层。事务的操作,也是在这部分去完成
领域服务层
entity
type User struct {
ID uint64 `gorm:"primary_key;auto_increment" json:"id"`
FirstName string `gorm:"size:100;not null;" json:"first_name"`
LastName string `gorm:"size:100;not null;" json:"last_name"`
Email string `gorm:"size:100;not null;unique" json:"email"`
Password string `gorm:"size:100;not null;" json:"password"`
CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"`
UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"`
DeletedAt *time.Time `json:"deleted_at,omitempty"`
}
// BeforeSave is a gorm hook
func (u *User) BeforeSave() error {
hashPassword, err := security.Hash(u.Password)
if err != nil {
return err
}
u.Password = string(hashPassword)
return nil
}
type Users []User
// PublicUsers So that we dont expose the user's email address and password to the world
func (users Users) PublicUsers() []interface{} {
result := make([]interface{}, len(users))
for index, user := range users {
result[index] = user.PublicUser()
}
return result
}
repository
type UserRepository interface {
SaveUser(*entity.User) (*entity.User, map[string]string)
GetUser(uint64) (*entity.User, error)
GetUsers() ([]entity.User, error)
GetUserByEmailAndPassword(*entity.User) (*entity.User, map[string]string)
}
基础设施层
infrastructure/user_repositories
func NewUserRepository(db *gorm.DB) *UserRepo {
return &UserRepo{db}
}
// UserRepo implements the repository.UserRepository interface
var _ repository.UserRepository = &UserRepo{}
func (r *UserRepo) SaveUser(user *entity.User) (*entity.User, map[string]string) {
dbErr := map[string]string{}
err := r.db.Debug().Create(&user).Error
if err != nil {
//If the email is already taken
if strings.Contains(err.Error(), "duplicate") || strings.Contains(err.Error(), "Duplicate") {
dbErr["email_taken"] = "email already taken"
return nil, dbErr
}
//any other db error
dbErr["db_error"] = "database error"
return nil, dbErr
}
return user, nil
}
可以看到,infrastructure下的user_repositories主要是通过NewUserRepository
,将main上的db注入进来,而db是从infrastructure下的NewRepositories
返回Repositories
infrastructure/db
type Repositories struct {
User repository.UserRepository
Food repository.FoodRepository
db *gorm.DB
}
func NewRepositories(Dbdriver, DbUser, DbPassword, DbPort, DbHost, DbName string) (*Repositories, error) {
DBURL := fmt.Sprintf("host=%s port=%s user=%s dbname=%s sslmode=disable password=%s", DbHost, DbPort, DbUser, DbName, DbPassword)
db, err := gorm.Open(Dbdriver, DBURL)
if err != nil {
return nil, err
}
db.LogMode(true)
return &Repositories{
User: NewUserRepository(db),
db: db,
}, nil
}
// closes the database connection
func (s *Repositories) Close() error {
return s.db.Close()
}
// This migrate all tables
func (s *Repositories) Automigrate() error {
return s.db.AutoMigrate(&entity.User{}, &entity.Food{}).Error
}