How to use interface type as a model in mgo (Go)?

旧巷老猫 提交于 2019-12-05 01:32:13

问题


Suppose you have a workflow that consists of multiple embedded nodes of different types. Since nodes are of different types, I thought of using Golang interfaces here and came up with following:

type Workflow struct {
   CreatedAt time.Time
   StartedAt time.Time
   CreatedBy string
   Nodes []Node
}

type Node interface {
  Exec() (int, error)
}

type EmailNode struct {
   From string
   To string
   Subject string
   Body string
}

type TwitterNode struct {
   Tweet string
   Image []byte
}

func (n *EmailNode) Exec() (int, error){
   //send email
   return 0, nil
}

func (n *TwitterNode) Exec() (int, error) {
   //send tweet
   return 0, nil
}

These workflows are stored in MongoDB and I have sample seed data in it. Using mgo, when I try to find a workflow (given its ID):

w = &Workflow{}
collection.FindID(bson.ObjectIdHex(id)).One(w)

I get the error - value of type bson.M is not assignable to type Node.

It also feels a bit weird to me that how would mgo unmarshal embedded Node documents into a Go struct without any type information. May be I need to look at the problem from another point of view.

Any suggestions would be highly appreciated.


回答1:


You cannot use an interface in a document for the reason you noted. The decoder has no information about the type to create.

One way to handle this is to define a struct to hold the type information:

type NodeWithType struct {
   Node Node `bson:"-"`
   Type string
}

type Workflow struct {
   CreatedAt time.Time
   StartedAt time.Time
   CreatedBy string
   Nodes []NodeWithType
}

Implement the SetBSON function on this type. This function should decode the type string, create a value of the correct type based on that string and unmarshal to that value.

func (nt *NodeWithType) SetBSON(r bson.Raw) error {
}



回答2:


Following Simon Fox's answer regarding the implementation of SetBSON, here is a more precise answer.

Let's take the original piece of code:

type Workflow struct {
   CreatedAt time.Time
   StartedAt time.Time
   CreatedBy string
   Nodes     []Node
}

type Node interface {
  Exec() (int, error)
}

type EmailNode struct {
   From    string
   To      string
   Subject string
   Body    string
}

type TwitterNode struct {
   Tweet string
   Image []byte
}

func (n *EmailNode) Exec() (int, error){
   //send email
   return 0, nil
}

func (n *TwitterNode) Exec() (int, error) {
   //send tweet
   return 0, nil
}

What you want to do now is: once you unmarshal the BSON object from Mongo, you want to be able to know if each node is either an EmailNode or a TwitterNode.

As you are storing the nodes as a Node interface, mgo has no way to know what struct to implement, so you have to tell it explicitly. Here comes SetBSON.

In your example, the problem comes from this Workflow.Nodes, which is a slice of Node interface. As it is a simple slice, the best is that you create a custom type that mgo will be able to call when unmarshalling the BSON:

type NodesList []Node

// The updated Workflow struct:
type Workflow struct {
    CreatedAt time.Time
    StartedAt time.Time
    CreatedBy string
    Nodes     NodesList
}

Now, you can implement SetBSON on this NodesList and describe how it works. Please note that as you are using a pointer, you can define what is contained inside the variable:

// Note that you must use a pointer to the slice
func (list *NodesList) SetBSON(raw raw.BSON) error {
    // Now you need to create the objects according to your
    // own domain logic and what's contained inside "raw":
    if raw.something {
        *list = append(*list, &TwitterNode{})
    } else {
        *list = append(*list, &EmailNode{})
    }

    return nil
}


来源:https://stackoverflow.com/questions/26039580/how-to-use-interface-type-as-a-model-in-mgo-go

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!