Amirgem
Amirgem

Reputation: 161

Does golang foment no file structure?

I've been looking into golang in order to build a web app, I like the language and everything, but I'm having trouble wrapping my head around the concept of structure in golang. It seems it pretty much forces me to have no file structure, no folders, no division, no separation of concerns. Is there any way to organize the .go files in a way that I'm not seeing? So far file structuring has been a headache and it's the only bad experience I've had with the language. Thank you!

Upvotes: 0

Views: 132

Answers (2)

Ibraheam
Ibraheam

Reputation: 11

As @del-boy said it depends on what you want to do, I went over this problem multiple times but what suited me more when developing a golang web app is to divide your packages by dependencies

- myproject 
-- cmd 
--- main.go
-- http
--- http.go
-- postgres
--- postgres.go
-- mongodb
--- mongodb.go
myproject.go

myproject.go will contain the Interfaces and Structs that contain the main domain or business models

For example you can have inside myproject.go

type User struct {
MongoID    bson.ObjectId `bson:"_id,omitempty"`
PostgresID string
Username       string 
}

and an Interface like this

type UserService interface { 
GetUser(username string) (*User, error)
}

Now in your http package you will handle exposing your api endpoints

 //Handler represents an HTTP API interface for our app.
type Handler struct {
    Router         *chi.Mux // you can use whatever router you like
    UserService    myproject.UserService
}

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *Request){
//this just a wrapper for the Router ServeHTTP
h.Router.ServeHTTP(w,r)
}

func (h *Handler) someHandler(w http.ResponseWriter, r *Request){
//get the username from the request
// 
user := h.UserService.GetUser(username)
}

in your postgres.go you can have a struct that implements UserService

type PostgresUserService struct {
DB *sql.DB
}

and then you implement the service

func (s *PostgresUserService) GetUser(username string) {
//implement the method
}

and the same thing can be done with mongodb

type MongoUserService struct {
Session *mgo.Session
}

func (s *MongoUserService) GetUser(username string) {
    //implement the method
    }

Now in your cmd/main.go you can have something like this

func main(){
postgresDB, err := postgres.Connect()
mongoSession, err := mongo.Connect()

postgresService := postgres.PostgresUserService{DB: postgresDB}
mongoService := mongo.MongoUserService{Session: mongoSession}

//then pass your services to your http handler
// based on the underlying service your api will act based on the underlying service you passed 
myHandler := http.Handler{}
myHandler.UserService = postgresService


}

assuming you changed your underlying store you only have to change it in here and you will not change anything

This design is heavily inspired from this blog, I hope you find it helpful

Upvotes: 1

del-boy
del-boy

Reputation: 3654

You are partially right. Go does not enforce anything regarding file and package structure, except that it forbids circular dependencies. IMHO, this is a good thing, since you have freedom to choose what best suites you.

However, it puts burden on you to decide what is the best. I have tried few approaches and depending on what I am doing (e.g. library, command line tool, service) I believe different approaches are best.

If you are creating only command line tool, let root package (root of your repository) be main. If it is small tool, that is all you need. It might happen that you command line tool grows, so you might want to separate some stuff to their own that can, but does not have to be, in same repository.

If you are creating library, do the same, except that package name will be name of your library, not main.

If you need combination (something that is useful both as the library and command line tool), I would go with putting library code (everything public for the library) in VCS root, with potential sub-packages and cmd/toolname for your binary.

When it comes to web services, I found it is most practical to follow these guidelines. It is best to read entire blog post, but in short - define your domain in VCS root, create cmd/app (or multiple) as command line entry point and create one package per dependency (e.g. memcache, database, http, etc). Your sub-packages never depend on each other explicitly, they only share domain definitions from root. It takes some getting used to and I am still adapting it to my use case, but so far it looks promising.

Upvotes: 2

Related Questions