Unleashing xLoad: Your Ultimate Data Loader for Go Structs!
Meet xLoad, your data-loading superhero 🦸♂️! It effortlessly brings data from various corners of the universe straight into Go structs. Whether you’re juggling configurations, internationalization labels, or experiments, xload is your trusty sidekick.
🤷♂️ Why xload?
- Diverse Data Sources: Load data from environment variables, http requests, command-line arguments, files, or even remote sources like apis.
- Avoid Boilerplate: Say goodbye 👋 to repetitive code and hello to a cleaner, more maintainable and readable codebase.
- Flexibility: Inspired by go-envconfig but with more muscle 💪! Customize struct tags and use custom data loaders.
Getting Started 🛠️
go get github.com/gojekfarm/xtools/xload
How does xload work its magic? 🎩✨
- Feed xload a Go struct with annotations.
- Let xload populate it with data from any source you want.
- A source simply has to implement
Loader
interface, or use one of existing ones!
- A source simply has to implement
- Enjoy the separation of data loading from its usage.
Here’s an example of the inbuilt OSLoader
:
type Config struct{ Key string `env:"KEY"` }
func main() {
var cfg Config
_ = xload.Load(context.TODO(), &cfg)
}
📝 Dive into Examples & Tutorials
🌍 1. Loading from Environment Variables
Imagine you have an application that needs to fetch configurations from the environment it’s running in. Instead of manually fetching each variable, xload lets you define a struct and populates it for you.
type AppConfig struct {
LogLevel string `env:"LOG_LEVEL"`
// ...
// your application config here
}
func main() {
ctx := context.Background()
cfg := DefaultAppConfig()
err := xload.Load(ctx, &cfg, xload.OSLoader())
// use cfg
}
🎨 2. Using Custom Types
With xload, you can also use custom types, such as Timeouts can be tricky. But with xload, you can directly specify durations like 5s without ambiguity. No more guessing if a timeout value is in seconds or milliseconds, and say goodbye to naming conventions like TIMEOUT_SEC!
type AppConfig struct {
Timeout time.Duration `env:"TIMEOUT"` // TIMEOUT=5s is 5 seconds
}
Or implement your own by defining one of the following methods for xload:
interface{ Decode(string) error }
encoding.TextUnmarshaler
json.Unmarshaler
encoding.BinaryUnmarshaler
encoding.GobDecoder
Example:
// URL is a type alias for url.URL.
// The general form represented is: [scheme:][//[userinfo@]host][/]path[?query][#fragment]
type URL url.URL
func (u *URL) String() string { return (*url.URL)(u).String() }
func (u *URL) Decode(v string) error {
parsed, err := url.Parse(v)
if err != nil {
return err
}
*u = URL(*parsed)
return nil
}
🪄 3. Nested Structs
For complex applications, configurations can get intricate. With xload, you can nest structs, making it easier to group, reuse, and maintain configurations.
type HTTPConfig struct {
Port int `env:"PORT"`
Host string `env:"HOST"`
}
type AppConfig struct {
Service1 HTTPConfig `env:",prefix=SERVICE1_"`
Service2 HTTPConfig `env:",prefix=SERVICE2_"`
}
📦 4. Inbuilt Loaders
xload isn’t just a tool; it’s a toolkit. It comes with a set of inbuilt loaders to make your life easier:
- OSLoader: Perfect for fetching data from environment variables.
- PrefixLoader: Need to add prefixes to keys before loading? This is your go-to loader.
- SerialLoader: When you need to load data from multiple sources, with the last non-empty value taking precedence.
🛠️ 5. Custom Loaders
For those special cases, xload allows you to craft custom loaders. As long as they implement the Loader
interface, you’re golden. Similar Loader can be implemented for - In-memory Cached, Vault, S3, etc.
🔍 Spotlight: CRUD-based List API
Let’s dive into a real-world example to see the transformation xload brings:
Before xload:
Parsing request options was a multi-step process. The code was filled with multiple parsers and error handling, the code is verbose and a lot to read through.
func GetList(w http.ResponseWriter, r *http.Request) {
qry := r.URL.Query()
search := qry.Get("search")
page, err := strconv.ParseInt(qry.Get("page"), 10, 32)
if err != nil {
// handle error
}
size, err := strconv.ParseInt(qry.Get("size"), 10, 32)
if err != nil {
// handle error
}
field := qry.Get("sort")
desc := false
if strings.HasPrefix(field, "-") {
field = strings.TrimPrefix(field, "-")
desc = true
}
//
}
After xload:
The code is streamlined. Define a struct, let xload populate it, and you’re good to go! The result? Cleaner, more maintainable code that’s a breeze to read.
type RequestOptions struct {
Search string `query:"search"`
Page int `query:"page"`
Size int `query:"size"`
Sort sort `query:"sort"`
}
type sort struct {
Field string
Desc bool
}
func GetList(w http.ResponseWriter, r *http.Request) {
var opts RequestOptions
if err := xload.Load(
r.Context(),
&opts,
xload.FieldTagName("query"),
xload.WithLoader(queryLoader(r.URL.Query())),
); err != nil {
// handle error
}
// opts contain typed requestOptions and
// any errors would also be caught here.
}
The transformation is clear: xload simplifies the data-parsing process and error handling, making the codebase more readable and maintainable.
🌐 Internationalization with Lokalize 🌐
With xload, you can load internationalization labels from files and then tweak them for each request using a remote service. This ensures your application speaks the language of your users, wherever they are.
type I18N struct {
Title string `i18n:"TITLE"`
CTA CTA `i18n:",prefix=CTA_"`
}
type CTA struct {
Submit string `i18n:"SUBMIT"`
Cancel string `i18n:"CANCEL"`
}
🎉 Wrap Up
That’s xload for you! A robust tool designed to simplify your data loading needs in Go. Dive in, explore, and let the magic unfold! 🌟
Check out the docs at pkg.go.dev for more examples.
xLoad is the brainchild of Ravi and I help out in maintaining it.