doushi1964 2015-12-11 11:48
浏览 34
已采纳

带有多个子包的大猩猩多路复用路由中的循环导入问题

Here is my project structure

--main package
--|--child_package1
--|--child_package2
--|--child_package3

I have all the the routes and method call management for API calls listed in main_package

The router Handler from main_package.go looks like this:

func Handlers(db *sql.DB, customeruploadFile string) *mux.Router {
  router := mux.NewRouter()
  router.HandleFunc("/api1", child_package1.method )
  router.HandleFunc("/api2", child_package2.method)
  router.HandleFunc("/api3", child_package3.mehtod)

  fileHandler := http.FileServer(http.Dir("./client/compiled"))
  router.PathPrefix("/").Handler(http.StripPrefix("/", fileHandler))
  return router
}

The problem is when I write test cases for child packages, I need this Handlers method to create test server, for that I need to import main_package in child_packages, then as obvious there is cycle imports happening, as child_packages are imported in main_package. Can any one please suggest me the best approach to tackle this?

  • 写回答

1条回答 默认 最新

  • doru52911 2015-12-11 18:45
    关注

    I assume your main_package isn't the main package in Go. I think the child_packages shouldn't be under the main_package as our goal is to decouple each package from one another.

    This is the pattern that I'm currently using in my project to avoid dependency conflicts:

    project/
    ├── main_package
    │   └── main_package.go
    ├── brokers
    │   └── brokers.go
    ├── child_package1
    │   └── child_package1.go
    ├── child_package2
    │   └── child_package2.go
    └── child_package3
        └── child_package3.go
    

    Essentially, each package should never have to deal with anything outside of itself (or at least do so with as little as possible). The broker will be the sole party who "negotiates" between any two packages.

    // main_package.go
    package main_package
    
    import (
        "path/to/sql"
        "path/to/mux"
        "path/to/brokers"
    )
    
    // Never use selectors from packages directly
    // but create a `Broker` object for each endpoint
    var bk1 = brokers.New("/api1")
    var bk2 = brokers.New("/api2")
    var bk3 = brokers.New("/api3")
    
    func Handlers(db *sql.DB, customeruploadFile string) *mux.Router {
        router := mux.NewRouter()
    
        // each broker has its own `MyHandler` function
        router.HandleFunc("/api1", bk1.MyHandler)
        router.HandleFunc("/api2", bk2.MyHandler)
        router.HandleFunc("/api3", bk3.MyHandler)
    
        fileHandler := http.FileServer(http.Dir("./client/compiled"))
        router.PathPrefix("/").Handler(http.StripPrefix("/", fileHandler))
        return router
    }
    

    The brokers package is the central interface for the communication

    // brokers.go
    package brokers
    
    import (
        "path/to/child_package1"
        "path/to/child_package2"
        "path/to/child_package3"
        "net/http"
    )
    
    type Broker interface {
        MyHandler(http.ResponseWriter, *http.Request) 
    }
    
    // Factory function to create a `Broker` instance
    func New(uri string) Broker {
        if uri == "/api1" {
            return Broker( new(child_package1.Delegate) )
        } else if uri == "/api2" {
            return Broker( new(child_package2.Delegate) )
        } else if uri == "/api3" {
            return Broker( new(child_package3.Delegate) )
        }
        return nil
    }
    

    Now child_packageX is no long decoupled to any internal dependency, provided it expose a "representative" or Delegate object to talk to the broker.

    // child_package1.go
    package child_package1
    
    import "net/http"
    
    type Delegate struct {
       // Optional parameters can be carried by the Delegate
       // to be used in the created Broker anywhere
    }
    
    func (d *Delegate) MyHandler(w http.ResponseWriter, r *http.Request) {
      // Maybe return a JSON here
    }
    

    Each child can have its own MyHandler that does different things for different api calls, without having to know what endpoints they are serving.

    // child_package2
    package child_package2
    
    import "net/http"
    
    type Delegate struct {}
    
    func (d *Delegate) MyHandler(w http.ResponseWriter, r *http.Request) {
        // Maybe return an XML here
    }
    

    The main_package doesn't import all the child_packageX, but just the broker package. You can write a test that imports the broker package instead of the actual packages, or you can even write another broker for testing.

    package test
    
    import (
        "testing"
        "path/to/main_package"
    )
    
    func TestMain(*testing.T) {
        // test the routing in `main_package`
    }
    

    You're no longer testing a functionality of a handler function, but one of an endpoint exposed by a broker. This encourage you to write generic handler functions and focus on the higher level endpoints.

    package test
    
    import (
        "testing"
        "path/to/broker"
    
    )
    
    func TestGetJSONAlright(*testing.T) {
         bk1 := brokers.New("/api1")
         // test if I get JSON here
    }
    
    func TestGetXMLAlright(*testing.T) {
        bk1 := brokers.New("/api2")
        // test if I get XML here
    }
    

    This, in my opinion, is a powerful pattern since you can write more "generic" handlers and just plug them in to the routes you want.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 stm32代码移植没反应
  • ¥15 matlab基于pde算法图像修复,为什么只能对示例图像有效
  • ¥100 连续两帧图像高速减法
  • ¥15 组策略中的计算机配置策略无法下发
  • ¥15 如何绘制动力学系统的相图
  • ¥15 对接wps接口实现获取元数据
  • ¥20 给自己本科IT专业毕业的妹m找个实习工作
  • ¥15 用友U8:向一个无法连接的网络尝试了一个套接字操作,如何解决?
  • ¥30 我的代码按理说完成了模型的搭建、训练、验证测试等工作(标签-网络|关键词-变化检测)
  • ¥50 mac mini外接显示器 画质字体模糊