go-entities is a support to api responses structs. It sits on the the top of model structs to prevent exposing unwanted fields in api response.
Sometimes you can return some sensitive data via your api if you directly returns your model struct in the response.
So it's always adviced to cast you models to api specific responses. go-entities helps you in casting your model structs to api specific responses to prevent leaking sensitive data.
Entities are meant to be reusable response formats / (structs for go). Entites can be used to cast fields, add extra fields during final presentation output, conditional casting, adding derived fields in response, nesting with other entities and much more.
List of fields you want to return in api response
type User struct {
UserName string `json:"UserName"`
Name string `json:"Name"`
Location string `json:"Location"`
}
As Entites are meant to be reusable so they should be present in a specific folder so that different controllers can use them.
├── entities # Entities (represents response types)
├── controllers # controllers for api's
├── classes # Bussiness logic layer
└── README.md
Under entities you can have all the entities which you wants to use in your service. (Surely you can do sub structuring under entities too if required).
Below is how a user enitity will look entities/user.go
type User struct {
UserName string `json:"UserName" entity:"UserName"`
Name string `json:"Name" entity:"Name"`
Location string `json:"Location" entity:"Location"`
}
While under classes User class will surely have more fields classes/user.go
type User struct {
ID int
UserName string
Name string
Location string
Phone string
}
It's pretty sure that in fetch basic details you will not want to return Id, Phone of user, so in controller you can cast your class struct to entities struct like below.
user := userHandle.GetUserBasicDetails(c)
c.Json(http.StatusOk, goentities.Present(user, entities.User{}))
It works using the value defined in entity tag in the User entity struct.
You can define methods to the output structs and those methods can be used to calculate value of any field.
There are a few conditions of defining methods
- Methods must be value based not pointer based
- Methods must be public
- Add a method tag to the struct field having value equal to function name
- Make sure the return type is same to the field you want to cast
classes/book.go
type Book struct {
Id int64
Title string
Discount float64
}
entities/book.go
// suppose your discount was in float 0-1
// and now you want to return discount by multiplying it with 100
// it can be done in your presentation layer like below
type BookEntity struct {
Title string `entity:"Title" json:"Title"`
Discount float64 `entity:"Discount" json:"-"`
FinalDiscount int `method:"CreateDiscount" json:"Discount"`
}
func (b BookEntity) CreateDiscount() {
b.FinalDiscount = int(100 * b.Discount)
return b.FinalDiscount
}
response := Book{
Id: 1,
Title: "Hooked",
Discount: 0.5,
}
c.Json(http.StatusOk, goentities.Present(response, BookEntity{}))
Just see the magic. With this it's easy to create derived fields in the presentation layer instead of writing them in bussiness logic.
entities
type struct User {
UserName string `json:"UserName" entity:"UserName"`
Name string `json:"Name" entity:"Name"`
Location string `json:"Location" entity:"Location"`
Address Address `json:"Address" entity:"Address"`
}
type struct Address {
City string `json:"City" entity:"City"`
State string`json:"State" entity:"State"`
PinCode int `json:"PinCode" entity:"Pin"`
}
classes
type User struct {
ID int
UserName string
Name string
Location string
Phone string
Address Address
}
type Address struct {
City string
State string
Pin int
}
Currently we support pointers in input struct only not in entity struct.
Like Below example is invalid
type Book struct {
Id int64
Title string
Author string
}
type BookEntity struct {
BookTitle *string `entity:"Title"`
AuthorOfBook *string `entity:"Author"`
}
But something like this will work
type Book struct {
Id int64
Title *string
Author *string
}
type BookEntity struct {
BookTitle string `entity:"Title"`
AuthorOfBook string `entity:"Author"`
}
bookTitle := "Hooked"
bookAuthor := "Nir Eyal"
response := Book{
Id: 1,
Title: &bookTitle,
Author: &bookAuthor,
}
// this will work
c.Json(http.StatusOk, goentities.Present(response, BookEntity{}))
// this will also work
c.Json(http.StatusOk, goentities.Present(&response, BookEntity{}))