如何使用“internal”包?

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

How to use "internal" packages?

问题

我试着理解如何使用"internal"包来组织Go代码。让我展示一下我所拥有的结构:

project/
  internal/
    foo/
      foo.go # foo包
    bar/
      bar.go # bar包
  main.go

# 这是main.go中的代码
package main

import (
  "project/internal/foo"
  "project/internal/bar"
)

project/位于GOPATH树之外。无论我尝试从main.go导入哪个路径,都不起作用,唯一有效的情况是import "./internal/foo|bar"。我想我可能做错了什么,或者对"internal"包的理念有误解。请问有人能够让事情更清楚吗?

更新

上面的示例是正确的,我唯一需要做的就是将project/文件夹放在$GOPATH/src下。所以问题在于,只有当我们从project/子树中导入时,像project/internal/foo|bar这样的导入路径才可行,而不能从外部导入。

英文:

I try understand how to organize go code using "internal" packages. Let me show what the structure I have:

project/
  internal/
    foo/
      foo.go # package foo
    bar/
      bar.go # package bar
  main.go

# here is the code from main.go
package main

import (
  "project/internal/foo"
  "project/internal/bar"
)

project/ is outside from GOPATH tree. Whatever path I try to import from main.go nothing works, the only case working fine is import "./internal/foo|bar". I think I do something wrong or get "internal" package idea wrong in general. Could anybody make things clearer, please?

UPDATE

The example above is correct the only what I need was to place project/ folder under $GOPATH/src. So the thing is import path like the project/internal/foo|bar is workable if we only import it from project/ subtree and not from the outside.

答案1

得分: 55

在Go v1.11及以上版本中引入了模块的概念,你不需要在$GOPATH/src中指定项目路径。

你需要通过创建go.mod文件告诉Go每个模块的位置。请参考go help mod文档。

以下是一个示例:

project
|   go.mod
|   main.go
|
\---internal
    +---bar
    |       bar.go
    |       go.mod
    |
    \---foo
            foo.go
            go.mod

project/internal/bar/go.mod

module bar

go 1.14

project/internal/bar/bar.go

package bar

import "fmt"

//Bar prints "Hello from Bar"
func Bar() {
	fmt.Println("Hello from Bar")
}

project/internal/foo/go.mod

module foo

go 1.14

project/internal/foo/foo.go

package foo

import "fmt"

//Foo prints "Hello from Foo"
func Foo() {
	fmt.Println("Hello from Foo")
}

project/main.go

package main

import (
	"internal/bar"
	"internal/foo"
)

func main() {
	bar.Bar()
	foo.Foo()
}

现在最重要的模块
project/go.mod

module project

go 1.14


require internal/bar v1.0.0
replace internal/bar => ./internal/bar
require internal/foo v1.0.0
replace internal/foo => ./internal/foo

这里有几点需要注意:

  1. 在require中可以使用任何名称。你可以使用project/internal/bar。Go认为这是包的URL地址,因此它会尝试从网络上获取并给出错误信息。
go: internal/bar@v1.0.0: malformed module path "internal/bar": missing dot in first path element

这就是为什么你需要使用replace,告诉Go在哪里找到它,这是关键!

replace internal/bar => ./internal/bar
  1. 在这种情况下,版本并不重要。你可以使用v0.0.0,它也可以工作。

现在,当你执行代码时,会得到以下输出:

Hello from Bar
Hello from Foo

这是代码示例的GitHub链接

英文:

With modules introduction in Go v1.11 and above you don't have to specify your project path in $GOPATH/src

You need to tell Go about where each module located by creating go.mod file. Please refer to go help mod documentation.

Here is an example of how to do it:

project
|   go.mod
|   main.go
|
\---internal
    +---bar
    |       bar.go
    |       go.mod
    |
    \---foo
            foo.go
            go.mod

project/internal/bar/go.mod

module bar

go 1.14

project/internal/bar/bar.go

package bar

import "fmt"

//Bar prints "Hello from Bar"
func Bar() {
	fmt.Println("Hello from Bar")
}

project/internal/foo/go.mod

module foo

go 1.14

project/internal/foo/foo.go

package foo

import "fmt"

//Foo prints "Hello from Foo"
func Foo() {
	fmt.Println("Hello from Foo")
}

project/main.go

package main

import (
	"internal/bar"
	"internal/foo"
)

func main() {
	bar.Bar()
	foo.Foo()
}

Now the most important module
project/go.mod

module project

go 1.14


require internal/bar v1.0.0
replace internal/bar => ./internal/bar
require internal/foo v1.0.0
replace internal/foo => ./internal/foo

Couple things here:

  1. You can have any name in require. You can have project/internal/bar if you wish. What Go think it is URL address for the package, so it will try to pull it from web and give you error
go: internal/bar@v1.0.0: malformed module path "internal/bar": missing dot in first path element

That is the reason why you need to have replace where you tell Go where to find it, and that is the key!

replace internal/bar => ./internal/bar
  1. The version doesn't matter in this case. You can have v0.0.0 and it will work.

Now, when you execute your code you will have

Hello from Bar
Hello from Foo

Here is GitHub link for this code example

答案2

得分: 28

这是你要翻译的内容:

包必须位于你的$GOPATH中才能被导入。你提供的例子中,使用import "./internal/foo|bar"可以工作,因为它进行了本地导入。internal只是为了防止不共享公共根目录的代码导入internal目录中的包。

如果你把所有这些放在你的gopath中,然后尝试从不同的位置导入,比如OuterFolder/project2/main.go,其中OuterFolder包含projectproject2,那么import "../../project/internal/foo"将会失败。无论你尝试使用import "foo"还是其他任何方式,都会失败,因为没有满足以下条件:

如果导入代码位于“internal”目录的父目录为根的树之外,则禁止导入包含“internal”元素的路径。

现在,如果你的路径是$GOPATH/src/project,那么你可以在$GOPATH/src/project/main.go中使用import "foo"import "bar",导入将会成功。然而,不包含在project下的内容将无法导入foobar

英文:

The packages have to be located in your $GOPATH in order to be imported. The example you gave with import "./internal/foo|bar" works because it does a local-import. internal only makes it so code that doesn't share a common root directory to your internal directory can't import the packages within internal.

If you put all this in your gopath then tried to import from a different location like OuterFolder/project2/main.go where OuterFolder contains both project and project2 then import "../../project/internal/foo" would fail. It would also fail as import "foo" or any other way your tried due to not satisfying this condition;

> An import of a path containing the element “internal” is disallowed if
> the importing code is outside the tree rooted at the parent of the
> “internal” directory.

Now if you had the path $GOPATH/src/project then you could do import "foo" and import "bar" from within $GOPATH/src/project/main.go and the import would succeed. Things that are not contained underneath project however would not be able to import foo or bar.

答案3

得分: 25

以下方法更具可扩展性,特别是当您计划构建多个二进制文件时:

github.com/servi-io/api
├── cmd/
│   ├── servi/
│   │   ├── cmdupdate/
│   │   ├── cmdquery/
│   │   └── main.go
│   └── servid/
│       ├── routes/
│       │   └── handlers/
│       ├── tests/
│       └── main.go
├── internal/
│   ├── attachments/
│   ├── locations/
│   ├── orders/
│   │   ├── customers/
│   │   ├── items/
│   │   ├── tags/
│   │   └── orders.go
│   ├── registrations/
│   └── platform/
│       ├── crypto/
│       ├── mongo/
│       └── json/

cmd/ 文件夹中的子文件夹代表您想要构建的二进制文件的数量。

了解更多

英文:

below way is more scalable, especially when you plan to build multiple binaries

github.com/servi-io/api
├── cmd/
│   ├── servi/
│   │   ├── cmdupdate/
│   │   ├── cmdquery/
│   │   └── main.go
│   └── servid/
│       ├── routes/
│       │   └── handlers/
│       ├── tests/
│       └── main.go
├── internal/
│   ├── attachments/
│   ├── locations/
│   ├── orders/
│   │   ├── customers/
│   │   ├── items/
│   │   ├── tags/
│   │   └── orders.go
│   ├── registrations/
│   └── platform/
│       ├── crypto/
│       ├── mongo/
│       └── json/

The folders inside cmd/ represents the number of binaries you want to build.

for more

答案4

得分: 0

另外要检查的是:当你使用外部导入的对象类型时,确保在它们前面加上命名空间。作为一个刚入门的Golang开发者,我不知道我需要这样做,一直在想为什么我保存后VS Code会删除我的导入(因为没有使用)。原来是因为我必须在导入的对象前加上命名空间的名称:

示例:

import (
    "myInternalPackageName"  // 只要你按照本文中的所有说明操作,这样写是没问题的
)

// 在代码中使用:
myInternalPackageName.TheStructName   // 加上前缀,否则无法正常工作

如果你在对象/结构体名称前不加命名空间前缀,VS Code会删除你的导入,因为它被认为是未使用的,然后你仍然会遇到错误:"找不到TheStructName"......这非常令人困惑,我不得不通过命令行在没有VS Code的情况下进行构建,才能理解这一点。

关键是:在实际使用来自内部包的结构体时,我必须指定包前缀。

如果你不想在使用导入的对象时使用限定符前缀,可以使用:

import . "thePath"    // 可以在不加前缀的情况下使用内容

参考:https://stackoverflow.com/questions/6478962/what-does-the-dot-or-period-in-a-go-import-statement-do

英文:

Also to check: When you are using your externally imported object types: make sure you are prefixing them with the namespace they're in. As a golang newbie, I didn't know I had to do that, and was wondering why is VS Code just removing my import (for not being used) when I saved. It's because I had to prefix the imported object with the namespace name:

Example:
import (
    "myInternalPackageName"  // works fine as long as you follow all instructions in this thread
)

//Usage in code: 
myInternalPackageName.TheStructName   // prefix it, or it won't work. 

If you don't put the namespace prefix before the object/struct name, VS code just removes your import for being unused, and then you still have the error: "Can't find TheStructName"... That was very confusing, and I had to do a build without VS code through the command line to understand that.

The point is: I had to specify the package prefix when actually using the struct from that internal package.

If you don't want to use the qualifier prefix when using imported objects, use:

import . "thePath"    // Can use contents without prefixing. 

Reference: https://stackoverflow.com/questions/6478962/what-does-the-dot-or-period-in-a-go-import-statement-do

huangapple
  • 本文由 发表于 2015年10月27日 00:59:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/33351387.html
匿名

发表评论

匿名网友

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

确定