Setup for Your Next Golang Project
martinheinz.dev
martinheinz.dev
> The list goes on and it never gets to the point that I'm actually satisfied with the setup...
alias b='go build'
`b` is even faster to type :)It's not the end of the world, but I think the effort required outweighs the benefit of natural growth, and any arbitrary, ill-fitting consistency is preferable to no consistency. imo, at some point, the worn-out "cattle, not pets" mantra applies to codebases too, not just infrastructure.
So, I don't have any extremely strong opinions on project layout and if all of the unimportant decisions are already made for me, all the better. That said, I would never want to tell anyone "you must blindly follow this one particular structure". Rules are meant to be thoughtfully broken.
There is one thing, I find very nice in Rails and other MVC projects and this is a standardized project setup. You can still bend and break the rules, but the mental effort is so much reduced and the programmer is able to concentrate more on code and doesn't need to think about structuring.
Code practically writes itself (in my opinion) if you structure it well.
A) The less you know about something in advance, you want to iterate in small increments in order to learn&fail fast, pivot and have many small gains and something to show early (Agile).
B) The more you know about something in advance (ie. re-implement Z on platform Y instead of X), you want more up-front design and re-use of already established patterns. Since the end result is mostly already known, you don't want to waste time&energy hunting down clever solutions, but spend shorter time on pure implementation.
For a typical golang project you have some idea of scope, or purely convention/convenience, you could decide to setup according to minimal structure. Even though we know java-programmers will want to implement their "architecture" on the project filesystem-level, we don't have to go all the way to the other end of the spectrum either.
A plain main.go file would require more refactoring, but hold the most promise in terms of freedom, innovation and creative input. But the Go-community have started to follow some minimal conventions already, so as to rein in the worst and unnecessary cases of divergence.
For those interested in skipping initial page (which seems to be insanely slow), just check out the boilerplate repo: https://github.com/MartinHeinz/go-project-blueprint
The pages loads quite slow indeed, that's because of the traffic coming in right now... The 1GB RAM VM is having hard time serving all the requests...
- Use of Docker is a serious problem. I have no intention of ever running Docker on my machine to build some random project for which I have the toolchain already installed. Wrapping Docker with a Makefile is even more irritating.
- Use of "package pkg" to keep the version number in is bizarre - I've never seen that before. Some use `package version` _inside_ a `pkg` directory which contains other public packages also, and use of `package version` at the top level is also fairly common.
- Multiple modules in one git repository presents many challenges for people who want to reference them at different commits in other projects.
- `pkg app` under a `cmd` directory is sometimes fine, but if there is only one app and no library, keeping it in the root should be preferred so that `go get` works properly. Furthermore, other packages being nested under it is not often great, since they are highly likely to be reused by other `cmd`s in the same repository.
Personally for Go I've never needed a "starter" project, since not much needs configuring out the box as the toolchain is quite reasonable.
If I were evaluating a project structured like this as a potential dependency, I'd knock off major marks for the structure and investigate alternatives. I guess if things came down to the wire, the structure wouldn't be a deal breaker, but I'd certainly aim to avoid it.
I've had nothing but bad luck with Docker for Mac. It's been super slow. If any container is running at all, the com.docker.hyperkit process eats 160% CPU even if `docker stats` shows the process hovering near 0% CPU. This has been the case for our entire team, and we're in the process of moving away from it.
Depending on how critical these apps are for my work, putting up with eg. a sub-par Docker experience on MacOS may be the lesser evil.
I'm not sure if this affects every Mac user.
I have a Docker course that has over 15,000+ people who signed up, of which a little less than half use MacOS and of that I've only had a handful of people mention really poor volume performance.
I don't have enough data points to know exactly what the cause is. It's definitely a legit problem and it does affect a decent amount of people because I've seen the posts in other places too, but I don't know how wide spread the problem really is. It might be some type of hardware / OS version / tech stack combo (some languages and frameworks require dealing with many more files than others).
I happen to use Docker for Windows and it's really really fast too for reference and I never had anyone mention slow volume performance in large apps. Native Linux is as good as it gets as well.
I hope one day the folks at Docker figure out why the performance is so bad for some Mac users.
Most of the people who take the course go off to Dockerize their own applications using whatever framework they use. Most of the people who have mentioned slowness were working with Laravel apps, but there were also a few using large Node apps too. In all cases they were using a Mac.
No one has mentioned slow volume performance issues on Windows that wasn't fixable with tweaking a Windows setting or 2. Most of the time it was Windows defender (or some other anti-virus tool) drastically slowing down I/O.
---
But to add a serious note, why is every dependency on the environment so complicated that we just wrap it in some container to just get rid of it?
Bad build/dependency-management tooling and non-perfect abstractions over system APIs probably account for most of the issue.
Containers are conceptually useful; it's nice to have ephemeral, isolated environments. But if it literally eats all of the CPU it can get its hands on, then it's not useful.
You generally use `cmd` package if you have more than 2 binaries in your project. Or, in rare case, where you ship a library and a binary (so you still can import a library by root package path).
Otherwise, you don't need cmd/ dir at all.
You kind of need cmd/, because you can only have one main() in a package, and a package with a main() can't easily be imported into another package. That's why the directory exists, not (as I understand it) because there is some merit to burying code 3 directory levels deep.
One thing I dislike about go is that the way imports work you often see source code mixed with stuff like docker and make files.
That's definitely not a problem I have with Go. Building everything with Docker is wayyy overkill.
Also, why is everything under the cmd/ package? It doesn't make sense.
As for go modules, using `mod vendor` might not be the best suggestion as it requires using `go build -mod=vendor`. If you use go modules, just use go get and drop the vendor folder.
edit -- on mobile, scroll down to "development"