Learning Go (Day 4)

Holy cow — has it been 2 months already??!? Where has the time gone?

Well, I spent most of September traveling throughout Iceland, Czechia, and Germany. My friends and I drank ourselves silly at Oktoberfest, and then we drank ourselves silly again at Snalleygaster. Some important political stuff also happened, and then we all just sorta dissociated for a week or two...

Nevertheless, it's time to get back to learning Go!

Time for a Project

So far, I've spent most of my time reading up on the language and experimenting with its concepts. So, I've decided that it's time to begin building a real application.

Over the next few weeks, I intend to build a simple web application for conducting polls. I have a lot of grand ideas for this application, but the immediate goal is to get acquainted with the Go ecosystem. In particular, I want to focus on:

  • Learning how to organize and package the code.
  • Writing automated tests.
  • Building web services.
  • Interfacing with databases.

Releasing Libraries

I've written a lot of applications over the years, and I've found that extracting functionality into reusable libraries is an investment that pays for itself very quickly. So, I decided to practice doing this in Go before getting too far along with things.

Somehow, releasing libraries in Go is simultaneously very straightforward and very complicated. 😵‍💫

Initially, it's very straightforward. Just push your library's code to a public source code repository, and BOOM — your library is immediately available! There's no need to build releasable artifacts or publish them to an official package registry! Just import your library wherever it is needed!

After that... it starts to get complicated. 😅

Granted, many of these complications exist in other languages as well. For example: if you're writing private or internal libraries, then obviously you can't just push the code to a public source code repository. Instead, you'll need to push the code to a private repository and maintain a private package registry of some kind. That's just life!

Yet, Go also brings its own complications to the table. For example: supporting multiple versions of your library might be actual hell. According to various sources, it's not enough to tag your code using semantic versioning. In addition to this, your library's main branch should also maintain a distinct directory for each major version of your code. But... it seems like this would just create weird interference between releases, tags, and directories. For instance, let's pretend that I just released v5.0.0 of my library. Afterwards, I discover a bug that is only present in v3.1.2 of the library. So, I fix the bug and a create a tag for v3.1.3.

Now suppose that somebody is using v3.1.3 of my library in their application. One day, they decide to upgrade to v5.0.0, so they update their go.mod file to point to this version. However, it turns out to be a bigger job than they expected, so they only manage to migrate half of their source code before their next application release. Fortunately, the v5.0.0 tag contains a v3 directory with the Version 3 code. So, everything should be fine, right?

Except... the v5.0.0 tag was created before the v3.1.3 tag. Therefore, this v3 directory actually contains a copy of the v3.1.2 code, not the v3.1.3 code. Consequently, this partial upgrade to v5.0.0 implicitly downgraded the remaining code to v3.1.2, thus reintroducing the bug. So... now what?

Guess I'll die

I guess you could work around this problem by creating a v5.0.1 tag even though nothing actually changed in v5.0.0. But... now we're just working around a quirk in Go's library management...

So yeah: I'm currently not a fan of the way Go handles module versions!

Workspaces

In practice, it's not always possible to establish a clear distinction between an Application and its Dependencies. For example: let's imagine that we want to rewrite our Application using a newer web service framework. As a first step, we extract the Application's Business Logic into a separate Library so that it can be shared by both incarnations of the Application. So far so good!

However, this Library is still very closely related to our Application. There is never a scenario where we would make a revision to the Library without also considering its direct impact on the Application. As such, it would be very helpful if these components could somehow share the same development lifecycle. Otherwise, you would have to:

  1. Implement changes into the Library module.
  2. Release a new version of the Library module.
  3. Integrate the new version of the Library module into the Application module.
  4. Verify that the Application module works as expected.
  5. Uh oh! There was a problem! Go back to step 1.
  6. Watch as the months and years slip away. You die of old age.
  7. Release a new version of the Application module.

Dreadful stuff!

Fortunately, Go's Workspaces make it possible to develop multiple Go modules simultaneously. Thus, the Application module that you are actively developing can import the Library module that you are actively developing. This completely eliminates the complicated dance between the component lifecycles! So, the new workflow looks like this:

  1. Implement changes to both the Library module and Application module simultaneously.
  2. Verify that everything works as expected in both modules.
  3. Release a new version of the Library module.
  4. Integrate the new version of the Library module into the Application module.
  5. Release a new version of the Application module.

Wonderful!

Closing Thoughts

I don't feel like I got a lot done on Day 4, but at least this blog post helped me untangle some of the complications I was facing when working with modules in a real application.

For the next few days, I'm going to fight the urge to split things into libraries so that I can focus on writing the application code. Once things are a bit further along, I'll take a break from the application code and figure out how to setup a private "package registry" for the internal libraries.

Previous Post

Election Aftermath