Validating your Models in Go
Go is one of the recent programming languages developed by Google and has become popular quite soon, with the Go community growing everyday. Go’s principles and philosophies are a bit different from other modern programming languages. There are various reasons for choosing Go over others, such as it’s ease of use, speed, support for concurrency and so on.

I have been working with Go more recently, while there are things I miss, like Generics, I have been enjoying writing in Go. It has worked well so far for our projects, mostly because they are not so domain heavy. Small microservices that deals with only one thing and does it well.

While working with Go, one pattern I have found useful is writing a reusable validator for your model objects(or structs).

Let’s say, you are building an API to store and retrieve a user configuration. A json request might look like,

{

"firstName": "Bob",

"lastName": "Medium",

"age": 65,

"preferredTheme": "DarkTheme",

"backgroundColor": "Cream"

}

This corresponding struct in Go would look like,

//UserConfiguration ...

type UserConfiguration struct {

FirstName string `json:"firstName"`

LastName string `json:"lastName"`

Age int `json:"age"`

PreferredTheme string `json:"preferredTheme"`

BackgroundColor string `json:"backgroundColor"`

}

In languages like Kotlin, when using Springboot, you can make use of annotations to add validations to each field. In Go, one way do that is to have a validator object, that can take custom validations and validate them against the request object(your struct).

One of the features of Go is the ability to create higher order functions. Higher order functions are those that can take another function as a parameter and/or return another function.

An example of a higher order function would look like,

//GetHandler ...

func GetHandler() func() error {

return func() error {

// Do something

return nil

}

}

GetHandler is the function that returns another function of type func() error . GetHandler can take parameters and use those inside the returned function too.

//GetHandler ...

func GetHandler(value *string) func() error {

return func() error {

// Do something

if value == nil {

return errors.New("nil string)"

} return nil

}

}

We can make use of this concept to build a framework of validation. We can define a validator, that takes a list of these functions, evaluates each of these and returns all the errors at the end. Each of these functions can validate a single field in your struct.

We start with a struct that can hold all these validators, and has a validate method,

//Constraint is type alias for func() error

type Constraint func() error //Validator ...

type Validator struct {

constraints []Constraint

}

I have defined a type alias constraint for func() error to make it easier. The validate method looks like this,

//Validate does the validation

func (validator *Validator) Validate() error { errors := []string{}

for _, constraint := range validator.constraints {

err := constraint()

if err != nil {

errors = append(errors, err.Error())

}

} if len(errors) == 0 {

return nil

} return fmt.Errorf(strings.Join(errors, ", "))

}

It iterates over all the constraints , each constraint is a function that returns an error, so we can just call them like an normal function constraint() , collecting any errors. Finally, we join all the errors and return a single error or simply return nil if there were no errors.

You can then define few constraints on string type and int type or any other type.

//NotEmptyConstraint checks for non-empty string

func NotEmptyConstraint(value string, fieldName string) Constraint {

return func() error {

if strings.TrimSpace(value) == "" {

return fmt.Errorf("%s cannot be empty", fieldName)

}

return nil

}

} //PositiveNonZeroConstraint checks for value > 0

func PositiveNonZeroConstraint(value int, fieldName string) Constraint {

return func() error {

if value <= 0 {

return fmt.Errorf("%s cannot be zero or negative", fieldName)

}

return nil

}

}

Your model will now have a validate method, that has a validator with constraints for the necessary fields,

func (userConfiguration *UserConfiguration) validate() error {

validator := validation.Validator{} return validator. AddConstraint(validation.NotEmptyConstraint(userConfiguration.FirstName, "firstName")).

AddConstraint(validation.PositiveNonZeroConstraint(userConfiguration.Age, "age")).

Validate()

}

You can then also create custom constraints and add them to the validation,

func validColor(color string) func() error{

if(isAValidColor(color)) {

return nil

}

return fmt.Errorf("%s is not a valid color", color)

}

Add this to your validator using the AddConstraint method,

...

AddConstraint(validColor(userConfiguration.BackgroundColor))

...

And that’s how we can add validations to our models.

This pattern is particularly useful when you have many request objects that need validation. All you need to do is add these validate methods to your models with the necessary constraints, and in your handlers, you just call the validate method to validate the model.

func handlePost(request Request) {

// ...

var userConfiguration UserConfiguration

err := json.Unmarshal([]byte(*requestBody), &userConfiguration)

// ...

err = userConfiguration.validate()

// ...

}

This looks cleaner and easier to maintain.

So happy coding, and as always #cleancode.
['Kishore Kumar']
['Validator', 'Design Patterns', 'Framework', 'Golang']