在 Go 项目的 DDD 分层架构中,Echo Web 框架及其 middleware 应该归属到哪一层?

发布于:2025-09-01 ⋅ 阅读:(15) ⋅ 点赞:(0)

在 Go 项目的 DDD 分层架构中,Echo Web 框架及其 middleware 应该归属到哪一层?main 方法应如何组织和放置?在 main 中启动 Echo Server 的最佳实践是什么?


📂 典型的 DDD + Echo 项目目录结构

.
├── cmd/
│   └── myapp/
│       └── main.go        // 程序入口,启动 echo server
├── internal/
│   ├── domain/            // 领域层,实体 (Entity)、值对象 (Value Object)、领域服务
│   ├── application/       // 应用层,用例逻辑,调用 domain
│   ├── infrastructure/    // 基础设施,数据库、第三方 API、日志等
│   └── interfaces/        // 接口层,HTTP handler,Echo router,middleware
└── pkg/                   // 公共库

各层职责

  • domain
    只包含业务核心模型和规则,不依赖外部框架。

  • application
    编排用例逻辑(比如「下单」调用库存和支付),依赖 domain。

  • infrastructure
    具体的实现(DB、缓存、消息队列、第三方服务),为 domain/application 提供支持。

  • interfaces

    • Echo 的 routerhandler
    • Echo 的 middleware(如日志、请求ID、恢复 panic)
    • handler 里调用 application 层的用例

middleware 放哪里?

放在 interfaces 层,因为 middleware 主要是 Web 层的适配逻辑。
例如:

internal/interfaces/http/middleware/logger.go
internal/interfaces/http/middleware/recover.go

main.go 里该做什么?

main.go 是程序入口,一般只做:

  1. 初始化配置(config/env)
  2. 初始化 logger
  3. 初始化基础设施(DB、缓存等)
  4. 组装依赖注入(application -> interfaces)
  5. 创建 Echo 实例并注册 middleware、路由
  6. 启动 server

示例:cmd/myapp/main.go

package main

import (
    "log"
    "myapp/internal/interfaces/http" // 你的 http router
    "myapp/internal/infrastructure/db"
)

func main() {
    // 1. 初始化基础设施
    database, err := db.NewPostgres()
    if err != nil {
        log.Fatal(err)
    }

    // 2. 初始化 Echo
    e := http.NewServer(database) // 在 interfaces 层组装 Echo

    // 3. 启动服务
    if err := e.Start(":8080"); err != nil {
        log.Fatal(err)
    }
}

示例:internal/interfaces/http/server.go

package http

import (
    "github.com/labstack/echo/v4"
    "github.com/labstack/echo/v4/middleware"
    "myapp/internal/interfaces/http/handler"
)

func NewServer(db any) *echo.Echo {
    e := echo.New()

    // 注册全局中间件
    e.Use(middleware.Logger())
    e.Use(middleware.Recover())

    // 注册路由和 handler
    h := handler.NewUserHandler(db) 
    e.GET("/users/:id", h.GetUser)

    return e
}

✅ 总结:

  • echomiddleware 放在 interfaces 层(因为它们是 I/O 适配)。
  • main.go 放在 cmd/{app}/main.go,只负责组装依赖和启动 Echo server。
  • middleware 和路由初始化逻辑写在 internal/interfaces/http 下,不要放到 domainapplication

不过,有人倾向于将 Echo 归类为 Web 框架,并封装在基础设施层,而不是直接暴露给其他层。这样可以降低对具体框架的依赖,从而在未来需要替换为其他框架(如 Gin)时,能够更灵活地进行替换,而不会影响整体架构。

DDD 里面其实有两种常见做法:

  1. 把 web 框架(Echo/Gin/Fiber)放到 infrastructure 层,对外提供一个抽象接口(比如 HTTPServer 接口),application/handler 只依赖接口,不依赖具体实现。这样就能做到将来替换 web 框架时对上层透明。
  2. 把 web 框架直接放在 interfaces 层,但这时 interfaces 层就跟具体框架耦合了。如果未来要换框架,interfaces 层会有比较大的改动。

这两种其实各有权衡:

  • infrastructure 层更“纯粹”,因为它就是实现技术细节的地方。
  • interfaces 层更直观,因为很多人认为 HTTP handler 本身就是“接口适配”。

✅ 如果你坚持把 Echo 放到基础设施层,可以这样组织:

.
├── cmd/
│   └── myapp/
│       └── main.go
├── internal/
│   ├── domain/           
│   ├── application/      
│   ├── infrastructure/   // Echo server 在这里
│   │   └── web/
│   │       ├── echo.go   // Echo 实现
│   │       └── gin.go    // 将来可换 Gin
│   └── interfaces/       // 定义 HTTP handler(只暴露接口,不依赖 echo)
└── pkg/

定义一个抽象接口

internal/interfaces/server.go

package interfaces

type HTTPServer interface {
    Start(addr string) error
    Shutdown() error
}

Echo 实现(基础设施层)

internal/infrastructure/web/echo.go

package web

import (
    "github.com/labstack/echo/v4"
    "github.com/labstack/echo/v4/middleware"
    "myapp/internal/interfaces"
)

type EchoServer struct {
    e *echo.Echo
}

func NewEchoServer() *EchoServer {
    e := echo.New()
    e.Use(middleware.Logger())
    e.Use(middleware.Recover())
    return &EchoServer{e: e}
}

func (s *EchoServer) RegisterRoutes(register func(e *echo.Echo)) {
    register(s.e)
}

func (s *EchoServer) Start(addr string) error {
    return s.e.Start(addr)
}

func (s *EchoServer) Shutdown() error {
    return s.e.Close()
}

main.go 组装

cmd/myapp/main.go

package main

import (
    "log"
    "myapp/internal/infrastructure/web"
    "myapp/internal/interfaces/http"
)

func main() {
    // 基础设施层:echo 实现
    server := web.NewEchoServer()

    // 接口层:注册 handler 到 echo
    server.RegisterRoutes(http.RegisterRoutes)

    // 启动
    if err := server.Start(":8080"); err != nil {
        log.Fatal(err)
    }
}

handler 不依赖 Echo

internal/interfaces/http/routes.go

package http

import (
    "net/http"
    "github.com/labstack/echo/v4"
)

func RegisterRoutes(e *echo.Echo) {
    e.GET("/health", func(c echo.Context) error {
        return c.String(http.StatusOK, "ok")
    })
}

将来要换 Gin,只需要:

  • infrastructure/web/gin.go 实现 HTTPServer 接口
  • main.go 替换 web.NewEchoServer()web.NewGinServer()

👉 所以严格来说:Echo 属于基础设施层,只是大部分 Go 项目为了简单,直接把它放在 interfaces 层用了。


网站公告

今日签到

点亮在社区的每一天
去签到