Golang tips and tricks, part 3
When you’re writing software it’s not so much the one liner that changes how you work, but the balancing act of programming patterns you use to build your software. I’m hoping to outline a few rules and patterns which make sense to me when building out software in Go.
Passing along data and objects
As you know by this series of articles, I’m very much interested in how to pass stuff to where you will needed. One of the most common patterns with API development that I’m dealing with is writing handlers like these:
func getJob(w http.ResponseWriter, r *http.Request) { ...
The obvious caveat of writing handlers like this is thinking about how and where you will get your data from. Or how you will access a database client. Or some other object that is specific to you and your main package.
What I do, is I provide a function which returns the handler with the above declaration. The function itself can take any number of arguments that you see fit - and you usually pass things from your main function.
func EndpointRun(hub *Hub) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
// your code goes here, you can use `hub` :)
}
}
The endpoint can be used like this:
m.HandleFunc("/api/{token}", EndpointRun(hub))
One benefit of this method is that the EndpointRun function can be part
of another package. All you have to do is make sure that this package
knows about what Hub
is. It’s a good idea because of this to either
keep everything in the main
package, or to use packages but declare
all structures in a separate package so they can be reused.
Passing along functions
The EndpointRun
example above has the pitfall that you’re just wrapping
your code, which might always need *Hub
for every call that you implement.
If you consistently use the same pattern and have a significant number of
API calls you’re implementing, you could use something like this:
type HttpHandler func(http.ResponseWriter, *http.Request)
type MyHandler func(*Hub, http.ResponseWriter, *http.Request)
func httpHandler(hub *Hub, callback MyHandler) HttpHandler {
return func(w http.ResponseWriter, r *http.Request) {
callback(hub, w, r)
}
}
I’m using types here just to shorten the declaration of httpHandler
a bit, trying
to keep line length a bit shorter. As you see, I declared MyHandler
function with
the additional hub
parameter which I want to pass.
m.HandleFunc("/echo", httpHandler(hub, echo))
There is only some benefit to this madness - when you will decide to add
or remove a function parameter to your API declaration, you would modify
httpHandler
(or MyHandler
type, respectively). When you do this, you can
rely on go that it will error out when building/running, until you will
add the parameter to all the API functions.
Ahh - the benefit of a strongly typed language. I could be hunting down errors like this for a very long time - Go is reasonably safe for refactoring in the sense that most likely you will break your development build long before an issue due to such refactoring would crop up in production.
Casting
You may come across a scenario where you need to add some functionality around
your data structures. For example - you might be building a chat application
where you want all the data inside a PayloadClient
structure, and you
also want to have a Client
object which implements additional interfaces or
properties. For example:
type Client struct {
PayloadClient
send chan interface{}
hub *Hub
conn *websocket.Conn
}
An embedded structures fields may be used from the Client
object, so you
could use c.Nickname
in this case, but you can also use PayloadClient
in
the same way:
func (r *Client) ToPayloadClient() *PayloadClient {
cl := &r.PayloadClient
return cl
}
So, we’re actually taking casting out of the equation here. We’re literally using the underlying data type to provide the value which is required.
Why would you declare two structures like this? Separation of responsibility.
The declaration of PayloadClient
is dealing exclusively with data structures
which are used for different types of storage - for example, a database table,
or a JSON string. It makes sense to provide functions which work on this object
that deal with storage.
And on the other side, the declaration of Client
is dealing with functionality.
There’s a send
channel which is used for transmission of message payloads, there’s
the actual websocket connection used by a client, and there’s the hub - a registry
of all clients. None of these things translate into anything that’s visible in
the storage layer and I’m not necessarily of the opinion that all this should be
piled onto one object.
It’s very difficult to strike a good balance between the two sides.
Using packages
If you’re just using the main
package and don’t use additional packages, you
should be fine. But if you’re using packages, there’s at least one rule of wisdom
which I can pass forward:
Declare your structures in a package which you can import!
Well, the wisdom explains what the problem is. Let me just explain how you’d get
to the problem. When writing APIs, I like to use packages to organise my code.
I start out with an empty main
, and I declare some esoteric dependencies in
the common
package, and more concrete dependencies, like a database client in
the factory
package. All of this works well exactly for one reason: I don’t
need any structures in common
or factory
packages.
Then, when writing a web application, there’s usually three things you need to
worry about. API calls, front-end calls, and underlying data storage. I want to
suggest to create the api
, web
and structs
packages for this scenario.
The structs
package must not import the api
or web
packages. If you’re
doing this, you already fucked up and should just move all your code into main
or start reading this section from the beginning.
The structs package must not have any internal dependency, which may again have the structs package for a dependecy. You’ll make the Go compiler angry with a circular dependency.
The web
package may import the api
package and use things already declared there.
I’m pretty sure that most if not all things that are available on the web can
be created as APIs. Or atleast they can be declared in the api
package, even
if they are never exposed via a HTTP interface.
That’s the logic. The problem always begins, however, that you start with main
.
As soon as you declare a structure in main, even if it’s for something simple like
configuration flags - you have to make damn sure that this structure is accessible
in your packages. And it seems like a bad reason to resort to interface{}
.
As for the code, I can’t point to anything of mine which uses these patterns (yet), but I did stumble across the “adopt a source” project, which seems to use similar patterns. You can check out adopt a source on github as an example of a project laid out over several packages.
The argument for reusability
Most of the problems we create for ourselves. When arguing code reusability against the alternative, starting off with a sane boiler plate, you just have to figure out when the cost will bite you in the ass.
One of my first CMS projects started without code reusability. The first 10 pages we made on them suffered from evolution - it was obvious that things were improving from one to the other, and it was inevitable that we will have to go back and fix it - at least the administration panel. And we did. And we created about 150 more web pages at an astounding pace. And it was still mostly copy-paste (files).
Of course, the next step was to build out a core
, which I’m happy to say constantly
improves, is unit tested, and runs a ridiculously significant number of projects.
And the core gets updated with new features, and old features either get phased out,
or refactored keeping the existing interfaces for backwards compatibility.
So, over all, reusability is just like any other approach in programming. You have to know where it can be used to achieve what is most important to you. If you’re building out just a testing web service, you’ll most likely not need to pay much attention to it. But when you’ll have 5 of them, patterns will emerge.
Closing words
The article is part of “Diving deeper with Go” series where I try to discover and present knowledge about how to use Go effectively. You might want to read the other parts:
- Golang Tips and Tricks, part 1
- Advanced Go Tips and Tricks, part 2
- Dependency Injection patterns in Go
- Dependency Injection continued
While I have you here...
It would be great if you buy one of my books:
- Go with Databases
- Advent of Go Microservices
- API Foundations in Go
- 12 Factor Apps with Docker and Go
Feel free to send me an email if you want to book my time for consultancy/freelance services. I'm great at APIs, Go, Docker, VueJS and scaling services, among many other things.
Want to stay up to date with new posts?
Stay up to date with new posts about Docker, Go, JavaScript and my thoughts on Technology. I post about twice per month, and notify you when I post. You can also follow me on my Twitter if you prefer.