探究 Go 的高级特性之 【领域驱动设计上篇】

当我们开始设计和开发一个应用程序时,如何将各种操作逻辑有效地组织和分配到不同的部分,并使其更加清晰和易于维护?领域驱动设计(DDD)的四层模型提供了一种很好的解决方案。在这篇文章中,我们将介绍DDD四层架构模型的作用以及如何使用它来构建一个高效的应用程序。

image.png

什么是DDD四层架构模型?

DDD四层模型是领域驱动设计中的一个重要概念,它将一个复杂的应用程序分解成四个层次。这些层次是:

  1. 用户界面层(Interface Layer)

  2. 应用服务层(Application Service Layer)

  3. 领域服务层(Domain Service Layer)

  4. 基础设施层(Infrastructure Layer)

每个层次都有自己的作用和职责,可以帮助开发者更好地组织和分配应用程序的逻辑,从而实现更好的可维护性和可扩展性。

image.png

用户界面层

接口层提供了与用户交互的接口,包括用户界面和服务接口。它主要负责展示领域对象的状态和行为,并向用户提供具体的业务服务。用户界面层与下一层领域服务层一起构成了应用程序的上层。

应用服务层

应用服务层是用户界面层和领域层之间的桥梁,主要负责将用户请求转化成领域模型的操作,协调领域服务层的工作,处理或者捕获业务逻辑上的异常,并返回处理结果给用户界面层。

应用服务层中的业务逻辑和规则并不是具体与任何一种技术实现相关,因此它们也更容易进行复用和测试。应用服务层中还需要处理程序的事务,确保数据的一致性和可靠性。

领域服务层

领域服务层是整个应用程序最核心也是最重要的一层。在这一层中,需要处理业务逻辑,建立领域模型和实现各种领域对象。

其中,领域模型是整个领域驱动设计的核心,它指代了抽象的、具有业务含义的领域概念及其之间的关系。领域模型中定义了各种领域对象,包括实体(Entity)、值对象(Value Object)、聚合根(Aggregate Root)、工厂(Factory)等。

在领域服务层中,需要遵循DDD的一些设计原则,例如SRP、高内聚、低耦合、封装性、多态性等。只有这样,才能使得领域模型的设计更加清晰、可扩展和可维护。

基础设施层

基础设施层是应用程序中最底层的一层,它主要处理底层的基础设施,例如数据库、消息队列、缓存等。在这一层中,需要将实现与领域服务层和应用服务层分离,这样就可以保持领域服务层的纯洁性,以减少技术影响对领域逻辑的干扰。

基础设施层中也需要遵循SRP的原则,让不同的组件或类分别负责不同的职责。同时,还需要保证基础设施层与上一层之间的接口清晰、稳定,以确保不同层之间的交互能够顺畅实现。

实战项目

地址
github.com/trisna-asha…

代码讲解

image.png

注册路由

文件位置: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
    }

image.png

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYZXOOuw' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片