<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                [TOC] ## 參考 示例中的源碼: https://github.com/lupguo/go-ddd-sample 但是示例中的應用為單個應用 多應用的源碼查考:https://github.com/victorsteven/food-app-server ## 圖片智能識別檢索應用 架構圖 ![](https://img.kancloud.cn/33/49/33493c7f2f97c5637d690319336599e1_1384x1740.png) * 領域層:領域層包含**上傳圖片、圖片短鏈、圖片標簽、檢索圖片**四個實體對象,實體需要通過聚合,提供能力給到應用層使用;同時,需要通過倉儲接口抽象好實體持久化存儲能力; * 基礎層:包含日志功能、Mysql數據庫存儲功能、Redis緩存等基礎層能力; * 接口層:圖片Post接收,圖片參數識別,調用應用層圖片上傳應用(采用嚴格分層,否則接口層可以直接調用領域層); * 應用層:圖片上傳應用,調用圖片領域層;其他類似的保護圖片縮放、短鏈生成、圖片檢索等應用功能; > 本架構采用從環境變量中讀取配置, 使用 `github.com/joho/godotenv` 庫 ## 目錄劃分 ``` . ├── application // [必須]DDD - 應用層 ├── cmd // [必須]參考project-layout,存放CMD │ ├── imgupload // 命令行上傳圖片 │ └── imgupload_server // 命令行啟動Httpd服務 ├── deployments // 參考project-layout,服務部署相關 ├── docs // 參考project-layout,文檔相關 ├── domain // [必須]DDD - 領域層 │ ├── entity // - 領域實體 │ ├── repository // - 領域倉儲接口 │ ├── service // - 領域服務,多個實體的能力聚合 │ └── valobj // - 領域值對象 ├── infrastructure // [必須]DDD - 基礎層 │ └── persistence // - 數據庫持久層 ├── interfaces // [必須]DDD - 接口層 │ └── api // - RESTful API接口對外暴露 ├── pkg // [可選]參考project-layout,項目包,還有internel等目錄結構,依據服務實際情況考慮 └── tests // [可選]參考project-layout,測試相關 └── mock ``` ## 領域層 - domain ### 領域實體 實體是領域中非常核心的組成,在我們的應用中,直接定義成`entity.UploadImg` <details> <summary>domain/entity/uploadimg.go</summary> ``` package entity import ( "os" "time" ) // UploadImg 上傳圖片實體 type UploadImg struct { ID uint64 `gorm:"primary_key;auto_increment" json:"id"` Name string `gorm:"size:100;not null;" json:"name"` Path string `gorm:"size:100;" json:"path"` Url string `gorm:"-" json:"url"` Content os.File `gorm:"-" json:"-"` CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"` UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"` DeletedAt *time.Time `json:"-"` } ``` </details> <br/> ### 領域倉儲接口 倉儲接口定義了一組方法,用于定義領域實體的與持久化存儲相關的操作,實現該接口的持久化存儲,都可以操作該領域實體(entity.UploadImg) <details> <summary>domain/repository/uploadimg_repo.go</summary> ``` package repository import "github.com/lupguo/go-ddd-sample/domain/entity" // UploadImgRepo 圖片上傳相關倉儲接口,只要實現了該接口,則可以操作Domain領域實體 type UploadImgRepo interface { Save(*entity.UploadImg) (*entity.UploadImg, error) Get(uint64) (*entity.UploadImg, error) GetAll() ([]entity.UploadImg, error) Delete(uint64) error } ``` </details> <br/> ## 基礎層 - infrastructure ### 總倉儲結構體 這里定義了總的倉儲結構體:`type Repositories struct{}`,其內包含領域層的倉儲接口和DB實例,可以方便持久層; 同時通過`gorm.AutoMigrate()`來實現DB的同步 <details> <summary>infrastructure/persistence/db.go</summary> ``` package persistence import ( "github.com/go-sql-driver/mysql" "github.com/jinzhu/gorm" "github.com/lupguo/go-ddd-sample/domain/entity" "github.com/lupguo/go-ddd-sample/domain/repository" "time" ) // Repositories 總倉儲機構提,包含多個領域倉儲接口,以及一個DB實例 type Repositories struct { UploadImg repository.UploadImgRepo db *gorm.DB } // NewRepositories 初始化所有域的總倉儲實例,將實例通過依賴注入方式,將DB實例注入到領域層 func NewRepositories(DbDriver, DbUser, DbPassword, DbPort, DbHost, DbName string) (*Repositories, error) { cfg := &mysql.Config{ User: DbUser, Passwd: DbPassword, Net: "tcp", Addr: DbHost + ":" + DbPort, DBName: DbName, Collation: "utf8mb4_general_ci", Loc: time.FixedZone("Asia/Shanghai", 8*60*60), Timeout: time.Second, ReadTimeout: 30 * time.Second, WriteTimeout: 30 * time.Second, AllowNativePasswords: true, ParseTime: true, } // DBSource := fmt.Sprintf("%s:%s@%s(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", DbUser, DbPassword, "tcp", DbHost, DbPort, DbName) db, err := gorm.Open(DbDriver, cfg.FormatDSN()) if err != nil { return nil, err } db.LogMode(true) // 初始化總倉儲實例 return &Repositories{ UploadImg: NewUploadImgPersis(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.UploadImg{}).Error } ``` </details> <br/> ### 上傳圖片領域倉儲接口的實現 persistence.UploadImgPersis結構體實現了領域層的倉儲接口,后續只要匹配領域層倉儲接口即可以匹配操作領域中的能力 <details> <summary>infrastructure/persistence/uploadimg_persis.go</summary> ``` // persistence 通過依賴注入方式,實現領域對持久化存儲的控制反轉(IOC) package persistence import ( "errors" "github.com/jinzhu/gorm" "github.com/lupguo/go-ddd-sample/domain/entity" ) // UploadImgPersis 上傳圖片的持久化結構體 type UploadImgPersis struct { db *gorm.DB } // NewUploadImgPersis 創建上傳圖片DB存儲實例 func NewUploadImgPersis(db *gorm.DB) *UploadImgPersis { return &UploadImgPersis{db} } // Save 保存一張上傳圖片 func (p *UploadImgPersis) Save(img *entity.UploadImg) (*entity.UploadImg, error) { err := p.db.Create(img).Error if err != nil { return nil, err } return img, nil } // Get 獲取一張上傳圖片 func (p *UploadImgPersis) Get(id uint64) (*entity.UploadImg, error) { var img entity.UploadImg err := p.db.Where("id = ?", id).Take(&img).Error if gorm.IsRecordNotFoundError(err) { return nil, errors.New("upload image not found") } if err != nil { return nil, err } return &img, nil } // GetAll 獲取一組上傳圖片 func (p *UploadImgPersis) GetAll() ([]entity.UploadImg, error) { var imgs []entity.UploadImg err := p.db.Limit(50).Order("created_at desc").Find(&imgs).Error if gorm.IsRecordNotFoundError(err) { return nil, errors.New("upload images not found") } if err != nil { return nil, err } return imgs, nil } // Delete 刪除一張圖片 func (p *UploadImgPersis) Delete(id uint64) error { var img entity.UploadImg err := p.db.Where("id = ?", id).Delete(&img).Error if err != nil { return err } return nil } ``` </details> <br/> ## 應用層 - application 可能涉及自身或遠程領域服務調用 ### 上傳圖片應用 - 應用層比較薄,主要做業務流程實現,需要對服務進行組合與編排,另外應用層做到承上啟下,即對上接口層暴露實例化應用的方法,方便把倉儲實現給接管過來,并通過調用具體的倉儲實現完成業務 - 上傳圖片應用,這塊統一采用_app結尾,這可可以統一標識應用層文件 - UploadImgApp.db實際是一個倉儲層接口在編寫應用層時候,看不到任何DB具體實現,同時也看不到任何接口層的入參信息,這就是接口抽象的優勢,層之間隔離的比較徹底 - 在應用層還會做一些額外的處理,比如這里的rawUrl()函數組合,非常通用的功能可以考慮放入pkg包內 <details> <summary>application/uploadimg_app.go</summary> ``` package application import ( "github.com/lupguo/go-ddd-sample/domain/entity" "github.com/lupguo/go-ddd-sample/domain/repository" "os" ) type UploadImgAppIer interface { Save(*entity.UploadImg) (*entity.UploadImg, error) Get(uint64) (*entity.UploadImg, error) GetAll() ([]entity.UploadImg, error) Delete(uint64) error } type UploadImgApp struct { db repository.UploadImgRepo } // NewUploadImgApp 初始化上傳圖片應用 func NewUploadImgApp(db repository.UploadImgRepo) *UploadImgApp { return &UploadImgApp{db: db} } func (app *UploadImgApp) Save(img *entity.UploadImg) (*entity.UploadImg, error) { img, err := app.db.Save(img) if err != nil { return nil, err } img.Url = rawUrl(img.Path) return img, nil } func (app *UploadImgApp) Get(id uint64) (*entity.UploadImg, error) { img, err := app.db.Get(id) if err != nil { return nil, err } img.Url = rawUrl(img.Path) return img, nil } func (app *UploadImgApp) GetAll() ([]entity.UploadImg, error) { imgs, err := app.db.GetAll() if err != nil { return nil, err } for i, img := range imgs { imgs[i].Url = rawUrl(img.Path) } return imgs, nil } func (app *UploadImgApp) Delete(id uint64) error { return app.db.Delete(id) } func rawUrl(path string) string { return os.Getenv("IMAGE_DOMAIN") + os.Getenv("LISTEN_PORT") + path } ``` </details> <br/> ## 接口層 - interfaces 接口層是整體架構的最上層,用于處理信息的輸入和輸出,這里我們通過_handler來作為統一后綴標識接口層處理文件 <details> <summary>interfaces/api/handler/uploadimg_handler.go</summary> ``` package handler import ( "errors" "fmt" "github.com/labstack/echo" "github.com/lupguo/go-ddd-sample/application" "github.com/lupguo/go-ddd-sample/domain/entity" "io" "io/ioutil" "math/rand" "net/http" "os" "path" "strconv" "time" ) // UploadImgHandle 上傳處理 func UploadImgHandle(c echo.Context) error { callback := c.QueryParam("callback") var content struct { Response string `json:"response"` Timestamp time.Time `json:"timestamp"` Random int `json:"random"` } content.Response = "Sent via JSONP" content.Timestamp = time.Now().UTC() content.Random = rand.Intn(1000) return c.JSONP(http.StatusOK, callback, &content) } // UploadImgHandler 圖片上傳接口層處理 type UploadImgHandler struct { uploadImgApp application.UploadImgAppIer } // NewUploadImgHandler 初始化一個圖片上傳接口 func NewUploadImgHandler(app application.UploadImgAppIer) *UploadImgHandler { return &UploadImgHandler{uploadImgApp: app} } func (h *UploadImgHandler) Save(c echo.Context) error { forms, err := c.MultipartForm() if err != nil { return err } var imgs []*entity.UploadImg for _, file := range forms.File["upload"] { fo, err := file.Open() if err != nil { continue } // file storage path _, err = os.Stat(os.Getenv("IMAGE_STORAGE")) if err != nil { if os.IsNotExist(err) { if err := os.MkdirAll(os.Getenv("IMAGE_STORAGE"), 0755); err != nil { return err } } else { return err } } // file save ext := path.Ext(file.Filename) tempFile, err := ioutil.TempFile(os.Getenv("IMAGE_STORAGE"), "img_*"+ext) if err != nil { return err } _, err = io.Copy(tempFile, fo) if err != nil { return err } // upload uploadImg := entity.UploadImg{ Name: file.Filename, Path: tempFile.Name(), CreatedAt: time.Time{}, UpdatedAt: time.Time{}, } img, err := h.uploadImgApp.Save(&uploadImg) if err != nil { return err } imgs = append(imgs, img) } return c.JSON(http.StatusOK, imgs) } func (h *UploadImgHandler) Get(c echo.Context) error { strID := c.Param("id") if strID == "" { return errors.New("the input image ID is empty") } id, err := strconv.ParseUint(strID, 10, 0) if err != nil { return err } img, err := h.uploadImgApp.Get(id) if err != nil { return err } return c.JSON(http.StatusOK, img) } func (h *UploadImgHandler) GetAll(c echo.Context) error { imgs, err := h.uploadImgApp.GetAll() if err != nil { return err } return c.JSON(http.StatusOK, imgs) } func (h *UploadImgHandler) Delete(c echo.Context) error { strID := c.Param("id") if strID == "" { return errors.New("the deleted image ID is empty") } id, err := strconv.ParseUint(strID, 10, 0) if err != nil { return err } err = h.uploadImgApp.Delete(id) if err != nil { return err } msg := fmt.Sprintf(`{"msg": "delete Imgage ID:%s success"`, strID) return c.JSON(http.StatusOK, msg) } ``` </details> <br/> ## 服務入口 - main <details> <summary>cmd/imgupload_server/main.go</summary> ``` package main import ( "github.com/joho/godotenv" "github.com/labstack/echo" "github.com/labstack/echo/middleware" "github.com/lupguo/go-ddd-sample/application" "github.com/lupguo/go-ddd-sample/infrastructure/persistence" "github.com/lupguo/go-ddd-sample/interfaces/api/handler" "log" "os" ) func init() { // To load our environmental variables. if err := godotenv.Load(); err != nil { log.Println("no env gotten") } } func main() { // db detail 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") // 初始化基礎層實例 - DB實例 persisDB, err := persistence.NewRepositories(dbDriver, user, password, port, host, dbname) if err != nil { log.Fatal(err) } defer persisDB.Close() // db做Migrate if err := persisDB.AutoMigrate(); err != nil { log.Fatal(err) } // 初始化應用層實例 - 上傳圖片應用 uploadImgApp := application.NewUploadImgApp(persisDB.UploadImg) // 初始化接口層實例 - HTTP處理 uploadImgHandler := handler.NewUploadImgHandler(uploadImgApp) e := echo.New() e.Use(middleware.Logger()) e.Use(middleware.Recover()) // 靜態主頁 e.Static("/", "public") // 圖片上傳 e.POST("/upload", uploadImgHandler.Save) e.GET("/delete/:id", uploadImgHandler.Delete) e.GET("/img/:id", uploadImgHandler.Get) e.GET("/img-list", uploadImgHandler.GetAll) // Start server e.Logger.Fatal(e.Start(os.Getenv("LISTEN_PORT"))) } ``` </details> <br/> ## 總結 1. DDD適合偏復雜業務,DDD不是萬能的。簡單業務使用DDD會有些殺雞用牛刀感覺(思考架構三原則:簡單、合適、演進),不要拿著DDD這個錘子到處找釘子; 2. DDD分層建議采用嚴格分層,不跨層調用,而是采用依賴注入方式把相關實例傳入下層(例如不要從接口層直接調用存儲層方法,因為跨層調用會導致整個調用鏈變復雜); 3. DDD目錄結構命名,這塊也是比較關鍵一點。目前Go是傾向簡潔,不希望向Java那么冗余,所以這塊命名還可以在DEMO基礎上進一步優化; 4. DDD分層會接口一多,代碼可讀性不好的問題。可以通過好的命名來規避(比如統一后綴、選取合適簡短的接口名),同時用依賴倒置思維逐層看接口,以及其依賴; 5. DDD設計步驟,可以按**領域層 -> 基礎層 -> 應用層 -> 接口層**,一般是按這個步驟開發; 6. DDD分層后,每層隔離得比較干凈,非常適合單元測試和Mock測試(可以參考文末`food-app-server`這個倉庫)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看