Careful with those exported interface in your internal package
Tags: go, firstpost, semver
Published: 2020-12-11 02:29:15

Internal package was first introduced in Go 1.4 to provide limitation to where the exported identifiers can be used. Quoting from the [release note](https://golang.org/doc/go1.4#internalpackages):
> For example, a package .../a/b/c/internal/d/e/f can be imported only by code in the directory tree rooted at .../a/b/c. It cannot be imported by code in .../a/b/g or in any other repository.

This feature is important especially for library maintainer since it can help reduce reasons for introducing breaking changes. 

As such, it might be tempting to *just* move all our shared code&mdash;that we'd expect to only be used inside our project&mdash;into an internal package. That way, we don't have to worry whenever we have to change their signature as needed. 

However, do have some thorough inspection when those shared code is actually an `interface`. Consider this `interface` defined in an internal package under root directory of our project.

```go
// internal/hidden/hidden.go
package hidden

type Hidden interface {
    Hide()
}
```

Any package inside our project are allowed to *use* this `interface`. Note that *use* here does not only mean referencing `hidden.Hidden` or invoking the `Hide()` method, but also making it as a part of exported identifiers, like so:

```go
// pkg/public/public.go
package public

import "our/project/internal/hidden"

func Public(h hidden.Hidden) {
}
```

Despite `hidden` being an `internal` package, user of our project can still invoke `public.Public` by defining an implementation that satisfy `hidden.Hidden`. For example:

```go
// some go files in other project
package main

import "our/project/pkg/public"

type hide struct {}

func (hide) Hide() { fmt.Println("im hidding")  }

func main(){
   public.Public(hide{})
}
```

All is well, until sometimes in the future when we need to change `hidden.Hidden`, be it adding another method or modifying the signature of `Hide()` method. For example:

```go
// internal/hidden/hidden.go
package hidden

type Hidden interface {
    Hide()
    Seek()
}
```

The problem is not about the modification, but the release version that we will use after doing so. 

Being a programmer and at the same time a human being, it would be straightforward to conclude that the changes we introduce is not a breaking change, since the modification is made only to an internal package. Moreover in a scenario where the interface with its current-and-only implementation are written by ourself but `pkg/public/public.go` is written by someone else in our team, the impact of the changes will be almost invisible.

But, as we go with that reasoning and release the change as a patch release, the users of our library who updated their dependency will start to complain that their code won't compile. Why? because their implementation of `hidden.Hidden` interface no longer satisfy the new constraint that we introduces, despite not being a major release.

## Conclusion
Avoid making exported `interface` of an internal package a part of API signature. It will no longer becomes internal to our project **implicitly**. If unavoidable, it's better to move it out of the internal package and **explicitly** make it as part of the public API.