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

后端 未结 2 1968
说谎
说谎 2021-01-03 04:18

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 ca

相关标签:
2条回答
  • 2021-01-03 04:57

    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 {
    }
    
    0 讨论(0)
  • 2021-01-03 05:20

    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
    }
    
    0 讨论(0)
提交回复
热议问题