“This is the 27th day of my participation in the First Challenge 2022. For details: First Challenge 2022”

It’s complicated, but it doesn’t have to be

The application structure is complex.

A good application structure can improve the developer experience. It helps you isolate what they’re working on without having to keep the entire code base in your head. A well-structured application can help prevent errors by decoupling components and making it easier to write useful tests.

A poorly structured application may do the opposite; It can make testing more difficult, it can make it challenging to find relevant code, it can introduce unnecessary complexity and wordy language that slows you down without any real benefit.

The last point is important – using structures that are much more complex than the requirements actually hurts the project more than it helps.

What I write here is probably not news to anyone. Programmers have long been taught the importance of organizing their code. Whether it’s naming variables and functions, or naming and organizing files, this is an early topic in almost every programming course.

All of which begs the question — why is it so hard to figure out how to build Go code?

Organize by context

In the past Go Time q&A, we were asked a question about building Go applications and Peter Bourgon answered as follows:

Many languages have the convention (I guess) that for the same type of project, all projects have roughly the same structure…… For example, if you were making a Web service in Ruby, you would have a layout where the package would be named after the architectural pattern you used. For example, MVC; Controllers and so on. But in Go, that’s not really what we’re doing. We have packages and project structures that basically reflect the realm of what we’re implementing. Not the patterns we use, not the scaffolding, but the specific types and entities in the project domain that we’re working on.

Thus, by definition, it is always customarily different from project to project. What makes sense in one place doesn’t make sense in another. Not that this is the only way to do things, but it’s something we tend to do…… So, yes, there is no answer, and the truth about idioms in language is very confusing to many people and may turn out to be the wrong choice…… I don’t know, but I think that’s the main problem.

Peter Bourgon on Go Time #147. The point is I added it.

In general, most successful Go applications are not structured so that you can copy/paste from one project to another. That is, we can’t take a generic folder structure and copy it into a new application and expect it to work, because the new application is likely to have a unique set of contexts to work with.

Instead of looking for templates to copy, start thinking about the context of your application. To help you understand what I mean, let’s try to see how to build a Web application to host my Go Courses.

The Go Courses application is a website where students register for courses and view individual courses within the course. Most courses have a video section with links to the code used in the course, as well as other relevant information. If you’ve ever used any video course site, you should have a general idea of what it looks like, but if you want to dig a little deeper, you can sign up for Gophercises for free.

At this point, I’m pretty familiar with the requirements of an application, but I’m going to try to guide you through my thought process when I first started building an application, because that’s the state you’ll always start too.

To get started, I want to consider two main interfaces:

  1. The student context
  2. The admin/teacher context

The student interface is familiar to most people. In this case, the user logs in to the account, sees the dashboard containing the courses they have access to, and can navigate down to individual courses within the course.

The admin interface is a little different, and most people won’t see it. As administrators, we worry less about consuming courses and more about managing them. We need to be able to add new lessons to the course, update videos of existing lessons, and more. In addition to being able to manage courses, administrators need to manage users, purchases, and refunds.

To create this separation, my REPO will start with two packages:

admin/
  ... (some go files here)
student/
  ... (some go files here)
Copy the code

By separating the two packages, I was able to define entities differently in each context. For example,

From the student’s point of view, the Lesson consists mainly of urls pointing to resources, and it has user-specific information, such as the CompletedAt field, which shows when/if that particular user has completed the course.

package student type Lesson struct { Name string // Name of the lesson, eg: "How to run a test" Video string // URL to the video for this lesson. Empty if the user // doesn't have access to this. SourceCode string // URL to the source code for this lesson. CompletedAt *time.Time // A boolean representing whether or  not the lesson // was completed by this user. // + more }Copy the code

At the same time, the course type in the administrator interface does not have the CompletedAt field, because that makes no sense in this context. This information is only relevant to the context of the logged-in user viewing the course, not as an administrator managing the course content.

Instead, the administrator course type provides access to fields such as Requirement, which are used to determine whether users can access the content. Other fields will look a little different; The Video field may not be the URL of the Video, but information about where the Video is hosted, because that’s how the administrator updates the content.

package admin // Using inline structs for brevity in this example type Lesson struct { Name string // A video's URL can be constructed dynamically (and in some cases with time // limited access tokens) using this information. Video struct {  Provider string // Youtube, Vimeo, etc ExternalID string } // Information needed to determine the URL of a repo/branch SourceCode struct { Provider string // Github, Gitlab, etc Repo string // eg "gophercises/quiz" Branch string // eg "solution-p1" } // Used to determine if a user has access to this lesson. // Usually a string like "twg-base", then when a user purchases // a course license they will have these permission strings linked to // their account. Prob not the most efficient way to do things, but works // well enough for now and makes it really easy to make packages down the // road that provide access to multiple courses. Requirement string }Copy the code

I chose to go this route because I believe that both conditions have changed enough to justify separation, but I also doubt that either will evolve enough to justify any further organization.

Can I organize this code differently? Absolutely!

One way I might change the structure is to separate it further. For example, some of the code that goes into the admin package is related to managing users, while others are related to managing courses. It is easy to divide it into two backgrounds. In addition, I can pull out all the code related to authentication — registering, changing passwords, etc. — and put it in an Auth package.

Rather than overthinking it, I find it more useful to pick something that looks pretty good and adjust it as needed.

Package as layer

Another way to decompose an application is through dependencies. Ben Johnson has a good discussion of this at gobeyond.dev, especially in articles that are packaged as layers, not groups. This concept is very similar to the hexagonal architecture Kat Zien mentioned during her GopherCon talk, “How You Build Your Go Applications.”

At a high level, the idea is that we have a core area where we can define our resources and the services we use to interact with them.

package app type Lesson struct { ID string Name string // ... } type LessonStore interface { Create(*Lesson) error QueryByPermissions(... Permission) ([]Lesson, error) // ... }Copy the code

Using a type like Lesson and an interface like LessonStore, we can write a complete application. Without the LessonStore implementation, we wouldn’t be able to run our program, but we could write all the core logic without worrying about how it was implemented.

When we are ready to implement an interface like LessonStore, we will add a new layer to our application. In this case, it might be in the form of an SQL package.

package sql type LessonStore struct { db *sql.DB } func Create(l *Lesson) error { // ... } func QueryByPermissions(perms ... Permission) ([]Lesson, error) { // ... }Copy the code

If you want to read more information about this policy, I strongly suggest you check Ben’s article on https://www.gobeyond.dev/.

The layer-by-layer approach may seem very different from the one I chose in Go Courses, but mixing and matching these strategies is actually a lot easier than it first seems. For example, if we treat administrators and students as a domain that defines resources and services, we can implement these services using a tiered packaging approach. Here is an example of using an Admin package domain and an SQL package that has an implementation of admin.LessonStore.

package admin

type Lesson struct {
  // ... same as before
}

type LessonStore interface {
  Create(*Lesson) error
  // ...
}
Copy the code
package sql

import "github.com/joncalhoun/my-app/admin"

type AdminLessonStore struct { ... }

func (ls *AdminLessonStore) Create(lesson *admin.Lesson) error { ... }
Copy the code

Is this the right choice for the application? I don’t know.

Using such an interface does make it easier to test smaller pieces of code, but this is only important if it provides real benefits. Otherwise, we end up writing interfaces, decoupling code, and creating new packages with no real benefit. Basically, we are creating busy jobs for ourselves.

The only wrong decision is no decision

In addition to these structures, there are countless ways of structuring (or not structuring) code that make sense depending on the context. I’ve tried using a flat structure — a single package — on a few projects, and I’m still amazed at how well it works. When I first started writing Go code, I used MVC almost exclusively. Not only is this better than the entire community might lead you to believe, but it frees me from the decision paralysis of not knowing how to structure my application and, therefore, not even knowing where to start.

In Q&A Go Time, we were asked how to build Go code, and Mat Ryer expressed the benefits of not having a fixed way of structuring code:

I think while it might be liberating to say there’s no real way to do this, it also means you can’t really do it wrong. It suits your situation.

​​Mat Ryer on Go Time #147​​

Now THAT I have a lot of experience with Go, I totally agree with Mat. It is liberating to be able to decide on the right structure for each application. I like that there is no set way of doing things and there is no really wrong way. Although I feel this way now, I remember feeling very frustrated when I was inexperienced and didn’t have specific examples.

The truth is, deciding which structure is right for your situation is almost impossible without some experience, but it feels like we’re being forced to make a decision before gaining any experience. It was a catch-22 that stopped us before we even started.

Catch-22 is a metaphor for people caught in an absurd dilemma. Translator Huang Wenfan translated the situation as “twenty-two people cheated.” A man can’t get a job because he has no experience, but he can’t get experience because he has no job.

Instead of giving up, I chose to use what I knew –MVC. This allowed me to write code, get some work done, and learn from those mistakes. As time went on and I began to understand other ways to structure code, my application became less and less similar to MVC, but it was a very gradual process. I suspect that if I had forced myself to structure the application immediately, I would not have succeeded at all. At best, I can only succeed after experiencing a lot of setbacks.

It is absolutely true that AN MVC will never provide as much clarity as a project-specific application structure. It is also true that for someone with little experience with Go code structure, discovering the ideal application structure for a project is not a realistic goal. It requires practice, experimentation, and refactoring to get the results right. MVC is simple and approachable. It’s a reasonable starting point when we don’t have enough experience or background to come up with something better.

On the whole

As I said at the beginning of this article, good application structure is designed to improve the developer experience. It is designed to help you organize your code in a way that makes sense to you. That doesn’t mean paralyzing newcomers and not being sure how to proceed.

If you find yourself stuck and aren’t sure how to proceed, ask yourself what is more efficient – to stay frozen, or choose any application structure and give it a try?

The former can do nothing. With the latter, even if you get it wrong, you can learn from it and do better next time. That sounds a lot better than never starting.

\

The original link: https://changelog.com/posts/on-go-application-structure