Sometimes we need to provide an interface that we only want our packages to implement.
The most common way of doing this is having a package that has a private method, so only types in that package can implement it.
type MyInterface interface {
SayHello()
internalOnly()
}
type Implementation struct {}
func (i Implementation) SayHello() {
fmt.Println("Hello")
}
func( i Implementation) internalOnly() {}
The code above provides MyInterface
which has method internalOnly
which can only be implemented by a type in that package. This is great and I often use this method to prevent others from implementing an interface that I expose.
Allowing others to implement an interface you define is dicey. I only do it when there is both a need and I KNOW that the interface won’t change. Otherwise adding a method at a later date will break all of my user’s code.
But this method doesn’t extend to handle the case where you want to organize your code with sub-packages. In that case, you might want to have a main package that defines an interface and sub-packages that implement that interface. But if you use the method above, the sub-packages cannot implement the interface. If you get rid of the private method, users can implement the interface and your interface becomes unchangeable without breaking user code (or doing a major version increment, which is breaking to the users in a different way). You can make a superset of the interface, but this clutters your API.
And while you can put all the types that implement an interface in the same package, I don’t find that as clean as using sub-packages.
A solution to the problem
Recently I came up with a solution that uses the internal/
package directory (I’ve never seen this method in use before, but it doesn’t mean someone hasn’t done it). For those that don’t know, internal/
can hold packages that can only be accessed by packages within a certain folder hierarchy. You can read about it here.
To achieve my goal, I create an internal package internal/local
:
package local
type InternalInter interface {
internalOnly()
}
type InternalImpl struct {}
func(i InternalImpl) internalOnly(){}
Now this looks similar to what we do when we have a single package. InternalInter
can only be implemented by a type in the local
package. In this case we create the implementation InternalImpl
.
Now, let’s say our package structure looks like the following:
hello/
hello.go
internal/
local/
local.go
sayit/
sayit.go
In the above structure, hello.go
will hold our public interface. sayit.go
will hold an implementation of the interface in hello.go
.
hello.go
will look like:
package hello
import ".../internal/local"
type SayHelloer interface {
SayHello()
local.InternalInter
}
Note: “…” stands for the package’s path.
So now the interface we have defined can only be implemented if they implement local.InternalInter
we defined in our local
package. No user packages can import local
.
But our sub-packages can take advantage of this in our by doing the following:
package sayit
import ".../internal/local"
type SayIt struct {
local.InternalImpl // embeds the local.InternalImpl
}
func (s SayIt) SayHello() {
fmt.Println("Hello")
}
Now you have a sub-package that implements the interface in our package hello
, but the public interface cannot be implemented by external packages.
Hopefully this method will help those of you trying to organize your code in a similar manner.
Good luck and happy coding!
Shameless plug
If you like this article and you are interested in using Go for DevOps work, check out my book: