~jzs/sketchground.site

044480053511d55a3da5bd2461255b1b6f4539bc — Jens Zeilund 1 year, 1 month ago a27175a structuring_go_code
Structuring go code wip
1 files changed, 44 insertions(+), 0 deletions(-)

A blog/2021-03-15_Structuring_go_code.md
A blog/2021-03-15_Structuring_go_code.md => blog/2021-03-15_Structuring_go_code.md +44 -0
@@ 0,0 1,44 @@
# Structuring go code

This entry is going to be fluffy and my personal oppinion on how i like to structure go code. And it is going to involve interfaces.

## Folder structure

## func main()
I tend to keep my main function relatively small. In some projects i do all my setup stuff in main whereas in other projects i move the setup out into its own package for testing purposes.

## On keeping things separate
It is important for me to try and keep things as separate as possible. 

## Interfaces where it makes sense
I have a tendency to use interfaces to abstract external services. If i have to integrate with S3 i reguarly implement a `BlobStore` interface that abstracts the functionality i need. Likewise with other external dependencies. These interfaces end up as a grouping of functionality rather than the idiomatic way of having interfaces represent very few methods.
If you have to send out notifications, emails, etc. Abstract this away in interfaces too. That way your code can be tested without actually sending stuff out of the house.

## Avoid side effects
Do not modify arguments to your functions unless explicitly specified in the function name.



Start with highlevel thoughts and become more and more concrete.
Define your "API"

Keep usecases highlevel and let the low level responsibility to other parts of the code. Keep the low level implementations to only do one thing as these should be irrelevant for understanding what the code does.
Keep http etc. out of your usecases. Your http endpoints responsibility is to get data from the http call and transform it into something your usecases will deal with. Return concrete errors from your usecases that your http implementation can convert into for example json. Or your grpc implementation can send over protobuf.
Your usecases should know nothing about cookies, post/get/update/delete, persistent connection information etc. Your usecase might trigger an update being sent to a websocket connection but that would be abstracted away with interfaces. It will be clear in your NewXXX or DoXYZ function that your usecase depends on an interface used to send out updates.


```
type Notifier interface {
	// NotifyChannel notifies participants in the channel that there's a new entry
	NotifyChannel(ctx context.Context, channelID int64)
}
func SendMessageToChannel(ctx context.Context, notifyUpdate Notifier, channelID int64, message string) {
	// Update channel messages.
	// Notify about the changes.
}
```
This example is by no means perfect and possibly misses a lot of details. But the important thing here is that your SendMessageToChannel doesn't know what medium the notification should be sent on. It doesn't know if you internally throw it on a queue to be processesed elsewhere or if it is just your local server that finds relevant active websocket connections to send the notification to.

Keep locks internal to a struct and it's methods. And keep methods on the struct grouped in the same file. That way it's much easier to find where a forgotten unlock is.

Be explicit about ownership of chans. If you take a chan as input. Specify the flow of the channel explicitly. Do not pass around chan but pass <-chan instead. That way it's clear who is responsible of publishing and closing the chan and who is responsible of consuming the chan.