Best Way To Structuring Golang Code

Aris Haryanto
8 min readDec 18, 2022

golang should be simple, don’t make it worst and complicated

Hi guys, I wrote this article because to many question to me about how to structuring go ?, what is the best structure in go ?, why you code like that ?, why your code like this ?, and many more.

that’s way I create this article . . .

on the internet there’s so many article about golang architecture like hexagonal, onion, clean. but the structure is always different

Why ? . . . that because that’s a view from a person, yes there’s no standard about how golang should be structuring, so many people create with their perception

I have tried some implementation but i think i just recomend with Flat Structure

because, like i said . . .

golang should be simple, don’t make it worst and complicated

some people implement some architecture with many interface,
yes no one can stop you to do that, but i think that’s not good

i.e when you go to your handler you create interface to your use case and create interface again to your repository and then create other interface to the module to connect with DB or other vendor or anything

that’s called Overkill Abstraction because to call some function from other you need to interface and the funny is sometime you call it in the same package, in golang the consumer should define the interface and this implementation should be in different package.

so what should we do now ?

I create simple project to show you even with this simple structure you can still create your architecture, tha’s all depend on your code mindset

here’s the full code :
https://github.com/Aris-haryanto/Best-Way-To-Structuring-Golang-Code

let’s checkout with this diagram . .

Golang Flow Architecture Diagram

for the first we go to cmd folder
inside on cmd folder we got 2 file main.go and config.go

config.go in here we’ll define read config file, we apart config to standalone file that make it easy to find

and then main.go in here we setup and configure everything we need
like a config, adapter, what server we use like rest or grpc (in this example we run both grpc and rest)

next we’re going to services level

this is we called Flat Structure, because in here we put every file in one package to create our system

the first flow it’s start from rest.go and grpc.go this file like a handler
they will get request from client and send it to main service
in this example is service_hello.go

of course you can add validation if you want, so you can embed this validation function in rest.go or grpc.go or in service_hello.go

also you can add unit test to validation.go with specific function, and you don’t need to setup anything in here, you only focus with the parameter and output, that’s simple right!

in service we also add utils.go in here we create function helper only for this package, don’t create global utils package and consume in other package
tha’s because if you have helper which is used in different package then you want to modified only for some package you need to update your helper function or create new helper, another issue is when you import this utils to other package that make some helper not relevant to that package. which means that’s not good design

We also add init.go in here we can init config, constanta or something like that
for config we’ve set in main.go basicaly in config we set with global variable i prefer to not store in a struct, so we can add anywhere we want without confusing to embeding the struct, that’s more easier.

and then in service_hello.go here’s we put all of bussines logic
remember, the best practice is you can apart bussiness logic to different function
and you can combine it in agregator function, this is make your code more maintainable and adopt Single Responsibility Principle

in service_hello.go you can call directly to adapter via interface (service/adapter.go you have set this in main.go)
or you can send them first to repository.go in here you can manipulate them to what ever you want,
i.e if you want to create CQRS concept you can combine insert to different DB like SQL and Elasticsearch, then you call adapter through adapter.go

when you call some modul through adapter via interface make your code rely on abstraction that’s mean your code adopt Dependency Inversion Principle.

when you call adapter directly from service_hello.go this is call YAGNI (You Aren’t Gona Need It)
that’s mean if your application don’t need repository.go to binding your data before call adapter then you don’t have to implement it

to implement repository you need to embed struct repository.go to the service_hello.go to call repository function, this make your code adopt Open/Close Principle

why you need repository.go for doing this, because you can’t put this in bussines logic (service_hello.go) or in adapter level directly that’s make your code not adopt Single Responsibility Principle
btw you can rename repository.go with other name if necessarily

if you realize this is the advantage when you use Flat Structure
you dont need to initialize or import something to call function you need
this make your code simple, easy to read, easy to learn and easy to maintain

next we’ll go to api and adapter directory
api is used for define struct to communicate with adapter
and adapter is used for comunicate to other module like DB, redis, 3rd party or vendor, or anything

every function inside adapter directory will define as interface in services/adapter.go
don’t implement interface and their function in same package.
you have to apart them and define only for what your service need, remember Consumer should define the interface
this make your code adopt Interface Segregation Principle
and this is relate to golang wiki interface

Recap . . .

okay, why I naming the title is Best way to structuring golang code.
because we use Flat Structure with flow diagram I mentioned above make your code simple, easy to read, easy to learn and easy to maintain

  • first we call main.go we setup every configuration in here, you can change the rest framework, in this example we use gin, of course you can change to echo, mux or anything you want, also you can change adapter and config initialization.
    why we put all of this initialization in main.go, because if your working with a team they will know what framework we use, what adapter or module we use, what config we set and other things.
    only by looking at main.go file
You can change rest framework, or change adapter, and initialize config file in here
  • next we go to handler which is rest.go and grpc.go in here we only accept data request from client and send it to service.
    so here we don’t touch directly to bussiness logic, the advantage in here is when we change the rest api framework in main.go we don’t need to change rest.go because in here we only accept http.ResponseWriter and http.Request that’s golang standart code because every rest framework use this (i’ve mentioned this before)
    then if we want to add or remove i.e we want to add pubsub or websocket we just need to add new file we can name it like pubsub.go or websocket.go
    you can remove grpc.go if your system no needed anymore
    we can do all of that things without changing the logic! and no need any interface. it’s realy simple right
If you want update your handler
  • next we had validation.go this is just single function you can add to your bussiness logic or before you call service_hello.go this function should be adopt Single Responsibility so you can plug and play.
    also that make your test easy, because you just call function you want to test with some test case, remember not every unit test need mock testing, mock need interface and to many interface make your code confuse that’s called Overkill Abstraction
  • next we’re go to bussiness logic service_hello.go this is only for bussiness logic you create part of logic function and combining in agregator function so your code will more maintainable.
    if your logic change you just need to change in this file.
    yes you can create unit test in here but you need mock, because in here you connect to adapter like a DB, redis, vendor or anything through interface (adapter.go) that’s mean you have to control what the result from adapter, whatever he need to get unexpected result or something else, so you can change it with your mock.
    one thing, you should be aware, put your log only in bussines logic not in other file.
    the other file only return the error and return the data.
    you have to set error from other function very detail and return them to service_hello.go so you know where you put error log and make you easy to find problem
When you change bussiness logic
  • next repository.go this is a additional layer if you want to manipulate data before you send it to your adapter, remember this is not for bussiness logic this only for some technical thing before you send your data to adapter. you need to embed repository.go struct to your service_hello.go struct to use the function
    if you want to change the logic just change in repository, do not touch the bussines logic ever!,
    and you don’t need bind this repository with interface! come’on man please . . .
    the concept is, repository only for manipulate your data before comunicating to adapter, so you just use interface when you comunicating with adapter
    when your adapter change you don’t need to change repository or bussiness logic.
    except if your adapter update with a new parameter or response from vendor, yes you have to update the repository to
When you change the repository
  • next api this is directory for defining struct to comunicate between service package and adapter package, this is the point you must be aware. because adapter, repository and bussiness logic depend on this struct
  • next adapter like I said erlier in here we define every 3rd party module, vendor, DB, redis or anything else and registering them in main.go
    so when you see main.go you’ll know what DB we use, what vendor we use, what cache management we use.
    remember you need to have same function name with the same responsibility
    i.e if you write function to insert to DB then in your cache management like redis you need to write same function logic and same function name.
    if you write function to read or get , also you need to have a function with same name function in redis
When you change the adapter

some golang library use Flat Structure :
- https://github.com/gin-gonic/gin
- https://github.com/spf13/viper
- https://github.com/urfave/cli

also in go package it self :
- https://github.com/golang/go/blob/master/src/net/http/client.go
- https://github.com/golang/go/tree/master/src/fmt

I hope this article is clearly to help you to decide how to structuring your code

--

--