Weird Go Enum Leads to Lesson: Different Project Structure

Jaeeun Cho
7 min readOct 10, 2022

--

Previous Story: Implementing Cache With Go

Some random enum that I wrote on Go project

My Expectations on Enum

After a couple Go projects, my project repositories are getting more and more complicated. In order to deal with it. I split the codes into different files. One of the methodology of splitting was make enum to group up every constant values in one file.

Before I got to know Go, I use to write TypeScript. Check out following TypeScript code snippets for enums.

Easy eh? TypeScript enums syntax is clear and well defined. There are not much to explain. Just use keyword ‘enum’ followed by its name. Items without values will be set as integer starting from 0, or can use string value.

Usage on TypeScript is also tidy. I can use the name of enum as type, so that we can use it when defining var/functions types. Also the items the enum holds can be accessed as members of enum type.

The same traits can be found on Java and C#. It acts as good syntax sugar to avoid magic strings in code and group up constant values with same purpose to single type.

I expected the same or similar feature on Go. However, it didn’t. Compared to TypeScript what I used right before, it seems less natural because it is just constant with custom type, no enum keyword. Check out the next section.

What They Say about Enum in Go

When you google “golang enum” or “go enum”, one of documents you get is like above. You can see 2 things;

  • a type definition “Direction”
  • a series of constants typed as “Direction” and given a value with “iota” keyword.

When I first see this, I felt weird about it because it was just constant with custom type, not a well defined enum keyword by language itself.

Usage is kind of same with other languages but since enum items are constants, I could not use <EnumType>.<item> kind of syntax. I only use <item>.

After all, I accepted it as a philosophy of Go; trying to simplify its syntax. However when I tried to use it in real project, I encountered some weird situations.

Some Weird Situations

The situations that I encounter were these; too many files and folders, names of enums got too long and sometimes it overlaps with other things, and there were chance of too many imports. Overall, using enum was bad for readability

Folder Structure that I Used Before

Before diving into the situations. I have to explain the project structure that I used before. As you can see in the above picture;

  • I made “enums” folder
  • I made each <enum_name> folder
  • I made <enum_name>.go files under each folder.

Don’t follow this structure; I’m telling you that it is not right.

This structure(making enum folder to put all enum files) are pretty easy to find in other OOP language projects. I used to do like this in my previous Angular project; my senior taught me to write enums this way.

The only difference is there are wrapping folders for each enum files so that the enum file should have package name <enum_name>_enum in order to tell apart each one.

Without no doubt, I wrote enums like this in current Go project and encountered many weird situation.

Situation 1: So Many Folders and Files

As you can see in the previous picture, one enum requires one wrapping folder. This was necessary because I wanted <enum_name>.<item_name> usage.

There are also some problems without wrapping folders. Putting all enums in enums package will cause

  • Collision
  • Readability
Colliding Items (left) / Readability Issue (right)

Even if the above codes are in one file, it would be the same when you put two files in a same folder and mark it as same package. As you can see, there are items called ‘Running’ for two different enum. Since it is not a enum item but constants, it will collide with its name.

Also, take an example when you use enum somewhere else. As you can see on the above code, it is hard to find what ‘Running’ and ‘Stopped’ means by just looking it. How about ‘Database.Running’ or ‘Threadmill.Stopped’? Much better to read.

These are why I choose to wrap each enum code with its package, ended up with so many folders and so many files to write or search around.

Situation 2: Keywords Get So Long or Overlaps

Usually if I want to implement some API about something, I would make handler file for handle http or other request, enum file for constants, repository file which controls database or cache, etc. All these will have names related to the object that I want to implement.

For example, if I want to implement apple API, the naming of each component would be like follows; “repository.AppleRepository”, “enum.AppleEnum”, “handler.AppleHandler”.

I can use some abbreviation or removing some redundant words, it could be possible to cause readability problems for others. But apart from these problems, this doesn’t look to be some elegant solution.

Situation 3: So Many Imports

This one could be the minor one; If I wanted to use enums, I had to have additional one lines for each enums because each enums were separated as packages.

Some would argue it is not a problem at all, but for me, this possibility of so much extended import section seemed awful. I thought there could be some much simpler and elegant solution.

Solution: Change the Package Structure

The official http package shows best practice for enum use

The moment when I glimpse the solution was when I was using some net/http package which is Go’s out of box package for http server feature. It has some constant values of http response codes and as you can see from the above picture, it is included in http package and named as prefix of type followed by item: <package_name>.<enum_name><item_name>.

After examining the structure of http package, I noticed this; variables and constants related to some object are “good” to be placed in the same package. Not just enums, handlers, repositories, etc. are good to be regrouped to a single package.

I could not find some official documentation about the range of one package should have but as Effective Go says:

Java programs are written in Java, not Go. On the other hand, thinking about the problem from a Go perspective could produce a successful but quite different program

The biggest error that I made was I tried to use Java style of project structure for Go code. Check out the changes that I have made.

Some Java style project structure I tried (Left) / The fixed one (Right)

Note that some names are changed(coin->cryptocurrency). You can see the clear difference between two. Previous structure has handlers, enums, repositories that are about same object(coin) separated across different packages. Now, those are grouped into a single package called cryptocurrency.

This change address all three situation addressed.

  1. So many folders and files: As you can see from above picture, it clearly reduced folders and files.
  2. Long and overlapping keywords: “cryptocurrency_enum.CryptocurrencyEnum” to “cryptocurrency.TypeCryptocurrency”. Seems not a big change but much clearer and no redundancy. Also. It applies for every other components such as “handler.CryptocurrencyHandler” to “cryptocurrency.Hander” or “repository.CryptocurrencyRepository” to “cryptocurrency.Repository”.
  3. Too many imports: Squashed to single package.

I thought this solution is much fitter to Go syntax and satisfied.

Conclusion

I had some reminiscent of TypeScript(or Java) style folder structure, and applied it to Go programming, experiencing some weird situations. Enum was the one that caused noticeable problems; causing unnecessary folders and files to be created, making names too long and to collide with each other, and unnecessarily long import lines. I found the error from the folder structure and fixed it to new structure that is much suitable for Go.

I think both styles have pros and cons. It is just shows the difference in philosophy. Interesting one.

Thanks for reading. Go stories will continue.

--

--