Structure
The next most important aspect of the package design is choosing and maintaining a healthy structure.
Similarly to the project layout, the package layout either improves the overall quality of the package, application and project, or unnecessary complicates, causes variety of issues, and just makes it harder to develop and support code.
It's important to structure packages well, as fixing it up at a later stage requires significantly more effort. Writing software is the easiest step in its lifecycle – the better you do from the beginning, the lower the cost of its maintenance.
This section discusses the following guidelines:
- use flat structure by default
- there are no sub-packages in Go.
Flat by Default
Most of the time, a package should have a flat structure, i.e. no structure at all.
That's it. As said earlier, do not introduce any structure for the sake of an artificial hierarchy. Several flat packages are better than some hierarchy when there is no actual need for that.
For example, when working on the model package, it does not make sense to have packages within it for each particular model.
| Do | Don't |
|---|---|
model | model/user, model/track, model/playlist |
Here is what objects would look like for the first case:
model.Usermodel.Trackmodel.Playlist- etc.
Now compare it to an alternative, where everything is in a separate package. We would then get something more or less awful, for example:
model/user.Usermodel/track.Trackmodel/playlist.Playlist- an so forth.
However, there is a good reason for placing packages in a directory that has already got a package in it, – the context provided by a good name.
Not a Sub-Package, But a Package in the Same Directory
There is no such a thing as a sub-package in Go. Instead, when it makes sense and is appropriate, a package can be created in a directory that already has a package in it.
If there is no hierarchical relation between packages, then what would be a reason for such a placement? Like said earlier, a good name sets the proper namespace and context. Therefore, it would be great if it was possible to benefit from using these for other code which fits nicely nearby the package inside the directory.
One of the best examples is the net package. Have a look at which packages reside in the net directory alongside with the net package, in the standard library:
httpmailrpcsmtptextprotourl.
While many developers are accustomed to think of them as net/http, net/url and so forth, the reality is that they're http, url and so on, respectively. What creates a feeling that the http package is a sub-package of net, is its import path. But that's only an import path. The package is http. And because the context and namespace set by net are appropriate and suitable for packages that deal with various aspects of networking, it makes a perfect sense to place them in that directory.
A real-life example of such placing would be a handler/middleware package. While middleware is a package on its own right, placing it under the handler directory is reasonable, because a middleware has to do with handling requests, and with handlers.
Use this sparingly and cautiously because of the risk of introducing a dependency cycle. The best way to avoid it is to have no shared code between packages at different levels. Design the relations such that dependencies always point into one, the same direction. For example, the package that is located at the parent directory can be imported by packages located deeper, but not the other way around. In some situations, it's reasonable to put all packages at the same level, by shifting what would be the top-level package into a child directory, so each package is in a sibling directory. Note that this is rather rare.