I've had a case where a GO based server had received requests over the network.
The requests include a request type in the URL, and the parameters as JSON in the request body.
I could use the echo library for this.
The echo library ask for each handler to handle the data conversion itself, for example:
func handler(c echo.Context) { u := new(User) if err = c.Bind(u); err != nil { return } // do thing with the User input }
But something felt wrong...
Why should I manually convert the input to a specific type?
Instead I would prefer to send the input structure directly to the function.
Something like this:
func handler(u User) { // do thing with the User input }
The reflection API activate the related function as well as handles the type conversion.
This is very similar to Java based Spring REST controller.
The Dynamic Activator
The dynamic activator handles casting the bytes input to the function input structure, as well as activation of a the function. It holds list of registered functions:
type Dynamic struct { functions map[string]interface{} } func (d *Dynamic) Init() { d.functions = make(map[string]interface{}) }
To register a function, we add the pointer of the function:
func (d *Dynamic) Register(function interface{}) { fullName := runtime.FuncForPC(reflect.ValueOf(function).Pointer()).Name() sections := strings.Split(fullName, ".") name := sections[len(sections)-1] d.functions[name] = function}
And to run the function, we use GO reflection.
The reflection loads the type of the function parameter from the function itself, and then uses the type to de-serialize the bytes array from JSON into the related fields.
func (d *Dynamic) Execute(name string, data []byte) { function := d.functions[name] functionValue := reflect.ValueOf(function) functionType := functionValue.Type() parameterType := reflect.New(functionType.In(0)) err := json.Unmarshal(data, parameterType.Interface()) if err != nil { panic(err) } parameterValue := parameterType.Elem() transactionResult := functionValue.Call([]reflect.Value{parameterValue}) returnValue := transactionResult[0].Interface() if returnValue != nil { err := returnValue.(error) panic(err) } }
That's all, so simple.
Usage Example
Let see how can we use the Dynamic Activator.
First, let create 2 functions.
Notice:
- Each function receives different type of input structure
- The input parameters structures must use upper case fields to enable JSON serialization
type Parameters1 struct { Price int `json:"price"`} type Parameters2 struct { Id string `json:"id"` Amount int `json:"amount"`} func func1(p *Parameters1) error { fmt.Printf("the price is %v\n", p.Price) return nil} func func2(p *Parameters2) error { if p.Id == "-1" { return errors.New("bad id") } fmt.Printf("the amount is %v\n", p.Amount) return nil}
Now let register the functions, and dynamically activate each function.
d := Dynamic{} d.Init()
d.Register(func1) d.Register(func2)
d.Execute("func1", []byte(`{"price":1}`)) d.Execute("func2", []byte(`{"id":"5","Amount":7}`)) d.Execute("func2", []byte(`{"id":"-1","Amount":2}`))
and the output is:
the price is 1 the amount is 7 panic: bad id
Final Thoughts
Maybe in one of the next versions, the GO echo library developers would take this step forward, and even start the next GO-SPRING...
No comments:
Post a Comment