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.

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

报告相同问题?

悬赏问题

  • ¥60 版本过低apk如何修改可以兼容新的安卓系统
  • ¥25 由IPR导致的DRIVER_POWER_STATE_FAILURE蓝屏
  • ¥50 有数据,怎么建立模型求影响全要素生产率的因素
  • ¥50 有数据,怎么用matlab求全要素生产率
  • ¥15 TI的insta-spin例程
  • ¥15 完成下列问题完成下列问题
  • ¥15 C#算法问题, 不知道怎么处理这个数据的转换
  • ¥15 YoloV5 第三方库的版本对照问题
  • ¥15 请完成下列相关问题!
  • ¥15 drone 推送镜像时候 purge: true 推送完毕后没有删除对应的镜像,手动拷贝到服务器执行结果正确在样才能让指令自动执行成功删除对应镜像,如何解决?