How I pass around shared resources (databases, configuration, etc) within Golang projects.

Anthony Alaribe
ITNEXT
Published in
6 min readAug 12, 2018

--

Configuration Everywhere

In my previous article, we explored an overall architecture that could be used for any web application, and we went ahead to build a static JSON api for a todo app. In this article we will go through an important part of every web application.

In most applications, there is usually a need to pass around shared resources. These resources could be configurations, database connections, etc.

It is easy to go into a lot of sweet talking, but instead i will show you how i do it in code.

Let’s see some code

First, let’s state where we left off in the previous article. We had a project with routing and handlers setup. let’s start by throwing in configuration. In most projects, you need data from the external environment (config files, environment variables, etc). These data would usually include things like database connection strings, database names, and other configuration data.

Application Configuration

I prefer to use a tool called viper, to handle my configuration.

Why? It’s been around for a long time (~ 4 years), and using a third party removes the need for me to rewrite my own json/toml/yaml deserialization logic (which is not so difficult to write though), but i also get some extras:

  • Setting defaults for fields not in the config files
  • Reading from JSON, TOML, YAML, HCL, and Java properties config files
  • Live watching and re-reading of config files (optional)
  • Reading from environment variables
  • Reading from remote config systems (etcd or Consul), and watching changes
  • Reading from command line flags
  • Reading from buffers
  • Setting explicit values

Let’s write some code:

First off, we create a package where we will initialise our configuration and other third party connections.This will be internal/config. We place the config package within internal, to prevent other projects using this config package.

Internal package is used to make specific packages unimportable.

Within the config package, let’s create a function which parses the configuration file and returns a struct with that data

Load data into from our config file

What this code does is rather simplistic:

  • viper.SetConfigName(“todo.config”) makes viper expect a config file with the name `todo.config`, Usually with a .toml or .yaml extension.
  • viper.AddConfigPath(“.”) makes viper search the current/main directory for the config file. But you an have multiple instances of viper.AddConfigPath(“xxx”) with different paths which viper can search for configuration files in.
  • viper.SetDefault sets 8080 and the default value of PORT, even if the config file is empty, or PORT isnt defined in it.
  • viper.Unmarshal does the most important work of unmarshalling the data that was read out of the config file into out constant struct.

Our config file would be something like this:

Next up, we’ll create our database connection instance and place it in a config struct, which we’ll pass around our app, to modules which require a database instance, or other config data.

Things to note:

  • We store the the constants returned by initViper into the config instance (which we will be passing around the application)
  • We make use of those constants (DB URL and DBName) to dial the mongodb connection
  • We store the database connection into the config instance
  • We return a pointer to the config instance (We will be passing around a pointer to this config instance)

Let’s make use of this configuration in the rest of our app

Things to Note:

  • We call config.New() from within our main function, getting an instance of our configuration (a pointer actually).
  • We use this new configuration to access the PORT address from the config file.
  • We adjust the Routes function in main.go to accept a configuration as a parameter

Most importantly

We now have a `New()` function in our `todo` feature package. We pass the configuration into new, which in turn returns a type which allows us call the Routes method within the todo features package.

r.Mount("/api/todo", todo.New(configuration).Routes())

Now for the actual code:

Things to note:

  • In Go, we can’t define methods on an imported type, so we embed that imported type within a locally defined config type.
  • Every Handler is now a method of the config type, so they now hace access to the data within our config files, as well as a database connection for their persistence needs.
  • Understand that while we use a mongoDB database in this example, the connection could be to any source, a redis database, a connection to a metrics engine, a connection to a third party service provider, etc.

In the example above, we made the Handlers methods of the Config struct, but this is not really necessary. Let us examine an alternative approach. Instead of using methods of a struct, we use closures (Yes. Closures can be used in golang as well. And beautifully too.)

Using Closures instead of struct methods

We pass in the configuration directly into Routes function.

In case you’re a bit confused, the most important line in that snippet is this:

r.Mount("/api/todo", todo.Routes(configuration))

What we did was adjust our previous approach of mounting routes. So instead of using a New function to initialise, we pass the configuration directly into the Routes function (no more a method).

Next we adjust our Routes method to accept this configuration, and equally adjust our handlers to become closures which accept the configuration, and then return a handler to handle the request (The handler would have access to the configuration argument as well, thanks to closure magic.

Our handlers now take advantage of closures to remain as regular handlers, while being able to receive extra arguments

Things to Note:

  • The smaller code surface area. Using closures, the code feels more compact (and in my opinion, elegant).
router.Get("/{todoID}", GetATodo(configuration))
  • Closures can be a little less unnatural, since you can have scenarios such as
functionA(argA)(argB)(argC)

That example is a valid code that could result from using closures. functionA accepts argA, and returns a function which accepts argB, and equally returns a function which accepts argC which could then return anything (even another function)

But I still see closures as really powerful tools for code composition, and use them when the gains outweigh the harms, in terms of code readability and maintainability.

Conclusion

I am really bad at concluding articles, as i usually end up editing them and adding new things along the way.

Anyway, we learnt:

  • How to use viper to parse in configuration
  • How to create a mongo database instance
  • How to pass this configuration around the app using
  • - Embedded structs and methods (OOP approach)
  • - Closures (A more functional approach)

Please endeavour to reach out to me if you have questions, need advice, or just want to chat about Golang, coding, or awesomeness in general 🙂.

I’m thinking about starting a semi regular newsletter about things i learn with respect to code architectures and composing neat and future proof golang codebases, but have no idea if anyone would want to join such as newsletter. Please do comment if you’re such a person who would be interested.

Link to sub project on github:

OOP pattern:

Using Closures:

Previous Article in the series:

--

--

I help solve business challenges using technology. Golang and Web optimisation (PWA, service workers, progressive enhancement)