英文:
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
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论