在使用多个子包的Gorilla Mux路由时出现循环导入问题。

huangapple go评论90阅读模式
英文:

Cycle import issue in gorilla mux routing with multiple sub packages

问题

这是我的项目结构:

--主包
--|--子包1
--|--子包2
--|--子包3

我在主包中列出了所有API调用的路由和方法调用管理。

主包中的路由处理程序(main_package.go)如下所示:

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.method)

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

问题是,当我为子包编写测试用例时,我需要使用这个Handlers方法来创建测试服务器,为此我需要在子包中导入主包,然后显然会发生循环导入,因为子包被导入到主包中。
请问有人能给我建议如何解决这个问题吗?

英文:

Here is my project structure
<pre>
--main package
--|--child_package1
--|--child_package2
--|--child_package3
</pre>
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(&quot;/api1&quot;, child_package1.method )
  router.HandleFunc(&quot;/api2&quot;, child_package2.method)
  router.HandleFunc(&quot;/api3&quot;, child_package3.mehtod)

  fileHandler := http.FileServer(http.Dir(&quot;./client/compiled&quot;))
  router.PathPrefix(&quot;/&quot;).Handler(http.StripPrefix(&quot;/&quot;, 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

得分: 2

我假设你的main_package不是Go语言中的main包。我认为子包不应该位于main_package下面,因为我们的目标是将每个包解耦。以下是我目前在项目中使用的模式,以避免依赖冲突:

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

基本上,每个包都不应该处理自身之外的任何内容(或者至少尽可能少地处理)。broker将是在任何两个包之间进行“协商”的唯一一方。

// main_package.go
package main_package

import (
    "path/to/sql"
    "path/to/mux"
    "path/to/brokers"
)

// 不要直接使用包中的选择器,而是为每个端点创建一个`Broker`对象
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()

    // 每个broker都有自己的`MyHandler`函数
    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
}

brokers包是通信的中心接口。

// 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)
}

// 创建`Broker`实例的工厂函数
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
}

现在,child_packageX不再与任何内部依赖项解耦,只要它向代理公开一个“代表”或Delegate对象以与broker通信即可。

// child_package1.go
package child_package1

import "net/http"

type Delegate struct {
    // 可选参数可以由Delegate携带,在创建的Broker的任何地方使用
}

func (d *Delegate) MyHandler(w http.ResponseWriter, r *http.Request) {
    // 在这里返回JSON
}

每个子包都可以有自己的MyHandler函数,为不同的API调用执行不同的操作,而无需知道它们提供的端点。

// child_package2
package child_package2

import "net/http"

type Delegate struct {}

func (d *Delegate) MyHandler(w http.ResponseWriter, r *http.Request) {
    // 在这里返回XML
}

main_package不导入所有的child_packageX,而只导入broker包。你可以编写一个导入broker包而不是实际包的测试,甚至可以为测试编写另一个broker。

package test

import (
    "testing"
    "path/to/main_package"
)

func TestMain(*testing.T) {
    // 测试`main_package`中的路由
}

你不再测试处理程序函数的功能,而是测试broker公开的端点之一。这鼓励你编写通用的处理程序函数并专注于更高级别的端点。

package test

import (
    "testing"
    "path/to/broker"
)

func TestGetJSONAlright(*testing.T) {
    bk1 := brokers.New("/api1")
    // 测试是否能够获取JSON
}

func TestGetXMLAlright(*testing.T) {
    bk1 := brokers.New("/api2")
    // 测试是否能够获取XML
}

在我看来,这是一种强大的模式,因为你可以编写更“通用”的处理程序,并将它们插入到你想要的路由中。

英文:

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
│&#160;&#160; └── child_package1.go
├── child_package2
│&#160;&#160; └── child_package2.go
└── child_package3
 &#160;&#160; └── 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 (
    &quot;path/to/sql&quot;
    &quot;path/to/mux&quot;
    &quot;path/to/brokers&quot;
)

// Never use selectors from packages directly
// but create a `Broker` object for each endpoint
var bk1 = brokers.New(&quot;/api1&quot;)
var bk2 = brokers.New(&quot;/api2&quot;)
var bk3 = brokers.New(&quot;/api3&quot;)

func Handlers(db *sql.DB, customeruploadFile string) *mux.Router {
    router := mux.NewRouter()

    // each broker has its own `MyHandler` function
    router.HandleFunc(&quot;/api1&quot;, bk1.MyHandler)
    router.HandleFunc(&quot;/api2&quot;, bk2.MyHandler)
    router.HandleFunc(&quot;/api3&quot;, bk3.MyHandler)

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

The brokers package is the central interface for the communication

// brokers.go
package brokers

import (
    &quot;path/to/child_package1&quot;
    &quot;path/to/child_package2&quot;
    &quot;path/to/child_package3&quot;
    &quot;net/http&quot;
)

type Broker interface {
	MyHandler(http.ResponseWriter, *http.Request) 
}

// Factory function to create a `Broker` instance
func New(uri string) Broker {
    if uri == &quot;/api1&quot; {
        return Broker( new(child_package1.Delegate) )
    } else if uri == &quot;/api2&quot; {
        return Broker( new(child_package2.Delegate) )
    } else if uri == &quot;/api3&quot; {
        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 &quot;net/http&quot;

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 &quot;net/http&quot;

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 (
    &quot;testing&quot;
    &quot;path/to/main_package&quot;
)

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 (
    &quot;testing&quot;
    &quot;path/to/broker&quot;
    
)

func TestGetJSONAlright(*testing.T) {
     bk1 := brokers.New(&quot;/api1&quot;)
     // test if I get JSON here
}

func TestGetXMLAlright(*testing.T) {
    bk1 := brokers.New(&quot;/api2&quot;)
    // 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.

huangapple
  • 本文由 发表于 2015年12月11日 19:48:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/34222841.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定