Best Way To Structuring Golang Code
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 . .
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
directoryapi
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 inmain.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 atmain.go
file
- next we go to handler which is
rest.go
andgrpc.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 inmain.go
we don’t need to changerest.go
because in here we only accepthttp.ResponseWriter
andhttp.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 likepubsub.go
orwebsocket.go
you can removegrpc.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
- next we had
validation.go
this is just single function you can add to your bussiness logic or before you callservice_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 toservice_hello.go
so you know where you put error log and make you easy to find problem
- 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 embedrepository.go
struct to yourservice_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
- 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 inmain.go
so when you seemain.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
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