generics - go reflect for metaprogramming/templates -


i have code receives protobuf messages duplicated in couple places want put library. problem exact protobuf message that's used different each set of code.

edit: , don't have flexiblity of restructuring them.

i'm not entirely sure possible solve without duplicate code in go, did make attempt (below). doing wrong, or not that's not possible? (note: stripped down code, in real code objects have lots of additional fields)

example.proto:

package testmsg;  enum repstatus {     done_ok = 0;     done_error = 1; }  message reqheader {     optional int64 user_id = 1; }  message respheader {     optional repstatus status = 1;     optional string error_msg = 2; }  message postreq {     optional reqheader header = 1;     optional bytes post_data = 2; }  message postresp {     optional respheader header = 1; }  message statusreq {     optional reqheader header = 1;     optional string id = 2; }  message statusrep {     optional respheader header = 1;     optional string status = 2; } 

mini-service/service.go:

package miniservice  import "reflect" import "github.com/golang/protobuf/proto" import "testmsg"  type miniservice struct {     name string     reqtype reflect.type     reptype reflect.type }  func newservice(name string, reqport int, reqtype proto.message, reptype proto.message) *miniservice {     ms := new(miniservice)     ms.name = name     ms.reqtype = reflect.typeof(reqtype)     ms.reptype = reflect.typeof(reptype)     return ms }  func (ms *miniservice) handler(msgs []string) (string) {     resp := reflect.new(ms.reptype.elem())      msg := msgs[0]     req := reflect.new(ms.reqtype.elem())     err := proto.unmarshal([]byte(msg), req)     //add error handling, or set _      resp.header = &testmsg.respheader{}     //call handler function unique per service     //the signature like:     //handlerequest(reqtype, resptype) & called like:     //handlerequest(req, resp)     resp.header.status = testmsg.repstatus_done_ok.enum()      respmsg, _ := proto.marshal(resp)     return string(respmsg) } 

testservice.go:

package main import "github.com/golang/protobuf/proto" import "testmsg" import "mylibs/mini-service"  func main() {     //fake out zmq message     req := &testmsg.postreq{}     req.header = &testmsg.reqheader{}     req.header.messageid = proto.int64(10)     reqmsg, _ := proto.marshal(req)     reqmsgs := []string{string(reqmsg)}      ms := miniservice.newservice("tester", 5555, testmsg.postreq, testmsg.postresp)     //what called when receiving zmq request     resp := ms.handler(reqmsgs)     log.info(resp) } 

when try compile errors like:

resp.header undefined (type reflect.value has no field or method header)  cannot use resp (type reflect.value) type proto.message in argument proto.marshal: reflect.value not implement proto.message (missing protomessage method) 

which make complete sense since resp isn't connected ms.resptype.

from point of view, protobuf definition specific. cleaned down great deal. example: there no need have different request , response header per type when differ in content. obvious eliminated specific request , reponse types, because again, differed in semantic meaning, on other hand rather obvious surrounding code. way, have eliminated lot of redundancy. in sum, different types of requests con identified headers, either presence or absence of user_id field or evaluation of content field. of course, can expand headers value selection need.

// exchange.proto syntax = "proto2"; package main;  enum status {     done_ok = 0;     done_error = 1; }  message header {     required string name = 1;     oneof value {         int32 user_id = 2;         status status = 3;         string content= 4;     } }  message exchange {     repeated header header = 1;     optional bytes content = 2; } 

then, see miniservice rather odd. you'd set service things daos, maybe other services , have them handle individual requests taking in request object , returning response object. grpc services defined .proto file (staying within example)

service miniservice {   rpc userinfo(exchange) returns (exchange) } 

which after compiling .proto defines following interface

type miniservice interface {     userinfo(ctx context.context, in *exchange) (*exchange, error) } 

you don't have use grpc, shows how deal services, because else, daos, loggers , such needs field in struct implementing said interface. small example without grpc

//go:generate protoc --go_out=. exchange.proto  package main  import (     "fmt"     "log"     "os" )  var (     statusname = "status"     useridname = "uid" )  func main() {      logger := log.new(os.stderr, "srvc ", log.ltime|log.lshortfile)      logger.println("main: setting dao…")     dao := &daomock{         users:  []string{"alice", "bob", "mallory"},         logger: logger,     }      logger.println("main: setting service…")      service := &miniservice{         dao:    dao,         logger: logger,     }      // first, valid request     req1 := &exchange{         header: []*header{             &header{                 value: &header_userid{userid: 0},             },         },     }      if resp1, err := service.userinfo(req1); err != nil {         logger.printf("main: error returned on request: %s\n", err.error())     } else {         fmt.println(">", string(resp1.getcontent()))     }       // missing useridheader causes error returned     // header creation compacted brevity     nouseridheader := &exchange{header: []*header{&header{value: &header_content{content: "foo"}}}}      if resp2, err := service.userinfo(nouseridheader); err != nil {         logger.printf("main: error returned service: %s\n", err.error())     } else {         fmt.println(">", string(resp2.getcontent()))     }      // self explanatory     outofbounds := &exchange{header: []*header{&header{value: &header_userid{userid: 42}}}}      if resp3, err := service.userinfo(outofbounds); err != nil {         logger.printf("main: error returned service: %s\n", err.error())      } else {         fmt.println(">", string(resp3.getcontent()))     } }   type daomock struct {     users  []string     logger *log.logger }  func (d *daomock) get(id int) (*string, error) {      d.logger.println("dao: retrieving data…")     if id > len(d.users) {         d.logger.println("dao: user not in 'database'...")         return nil, fmt.errorf("id %d not in users", id)     }      d.logger.println("dao: returning data…")     return &d.users[id], nil }  type miniservice struct {     logger *log.logger     dao    *daomock }  func (s *miniservice) userinfo(in *exchange) (out *exchange, err error) {      var idhdr *header_userid      s.logger.println("userinfo: retrieving id header")      // here magic happens:     // identify different types of requests presence or absence     // of headers     _, hdr := range in.getheader() {         v := hdr.getvalue()         if i, ok := v.(*header_userid); ok {             idhdr =         }     }      if idhdr == nil {         s.logger.println("userinfo: invalid request")         return nil, fmt.errorf("invalid request")     }      u, err := s.dao.get(int(idhdr.userid))      if err != nil {         s.logger.printf("userinfo: accessing user data: %s", err.error())         return nil, fmt.errorf("error accessing user data: %s", err.error())     }      /* ----------------- create response ----------------- */     statusheader := &header{         name:  &statusname,         value: &header_status{status: status_done_ok},     }     userheader := &header{         name:  &useridname,         value: &header_userid{userid: idhdr.userid},     }      s.logger.println("userinfo: sending response")      return &exchange{         header:  []*header{statusheader, userheader},         content: []byte(*u),     }, nil } 

now, requests , responses more generic , suitable being used in various types of requests, without changing format , without need reflection. not saying golden bullet, however. others might come solutions better fit needs. hth.


Comments

Popular posts from this blog

sublimetext3 - what keyboard shortcut is to comment/uncomment for this script tag in sublime -

java - No use of nillable="0" in SOAP Webservice -

ubuntu - Laravel 5.2 quickstart guide gives Not Found Error -