如何在mgo(Go)中使用接口类型作为模型?

huangapple go评论69阅读模式
英文:

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

问题

假设你有一个由不同类型的嵌入节点组成的工作流程。由于节点是不同类型的,我考虑在这里使用Golang接口,并提出以下解决方案:

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){
   //发送电子邮件
   return 0, nil
}

func (n *TwitterNode) Exec() (int, error) {
   //发送推文
   return 0, nil
}

这些工作流程存储在MongoDB中,并且我在其中有一些示例种子数据。使用mgo时,当我尝试查找工作流程(根据其ID)时:

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

我得到错误 - 类型为bson.M的值无法赋值给类型Node。

对我来说,这也有点奇怪,mgo如何在没有任何类型信息的情况下将嵌入的Node文档反序列化为Go结构体。也许我需要从另一个角度来看这个问题。

非常感谢任何建议。

英文:

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

得分: 9

你不能在文档中使用接口,原因如你所述。解码器没有关于要创建的类型的信息。

处理这个问题的一种方法是定义一个结构体来保存类型信息:

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

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

在这个类型上实现SetBSON函数。这个函数应该解码类型字符串,根据该字符串创建一个正确类型的值,并对该值进行解组。

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

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

得分: 7

根据Simon Fox关于SetBSON实现的回答,这里给出一个更详细的答案。

让我们看一下原始的代码片段:

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){
   //发送电子邮件
   return 0, nil
}

func (n *TwitterNode) Exec() (int, error) {
   //发送推文
   return 0, nil
}

现在你想要做的是:一旦从Mongo解组BSON对象,你想要知道每个节点是EmailNode还是TwitterNode

由于你将节点存储为Node接口,mgo无法知道要实现哪个结构体,所以你必须明确告诉它。这就是SetBSON的作用。

在你的示例中,问题出在Workflow.Nodes上,它是一个Node接口的切片。由于它是一个简单的切片,最好创建一个自定义类型,以便mgo在解组BSON时能够调用它:

type NodesList []Node

// 更新后的Workflow结构体:
type Workflow struct {
    CreatedAt time.Time
    StartedAt time.Time
    CreatedBy string
    Nodes     NodesList
}

现在,你可以在这个NodesList上实现SetBSON,并描述它的工作原理。请注意,由于你使用了指针,你可以定义变量中包含的内容:

// 注意必须使用切片的指针
func (list *NodesList) SetBSON(raw raw.BSON) error {
    // 现在你需要根据自己的领域逻辑和"raw"中的内容创建对象:
    if raw.something {
        *list = append(*list, &TwitterNode{})
    } else {
        *list = append(*list, &EmailNode{})
    }

    return nil
}

以上是要翻译的内容。

英文:

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
}

huangapple
  • 本文由 发表于 2014年9月25日 21:14:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/26039580.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定