我正在编写一个用于执行基本 CRUD 操作的 API(基本上是 struct <=> mysql 表)。这是一个映射到我的数据库中的表的示例结构。我对字段使用指针,以便我可以处理 nil
作为 NULL/不存在:
type Foo struct {
Id *int32
Name *string
Description *string
CreateDate *string
}
Id
field 是应该由数据库分配的自动增量字段。 Name
字段是可写的并且是必需的。 Description
字段可写且可为空。 CreateDate
字段由 MySQL 在插入时分配,不应写入。当用户 POST 一个要创建的新 Foo 时,请求正文在 JSON 中如下所示:
POST /Foo
{"Name": "test name", "Description": "test description"}
很容易解码这个并使用一个 Foo 结构
foo := Foo{}
json.NewDecoder(requestBody).Decode(&foo)
我正在使用 https://github.com/coopernurse/gorp库以简化插入/更新/删除,但如果我希望使用字段反射来概括查询创建,即使我正在编写原始 sql,我的问题仍然存在。
gorpDbMap.Insert(&foo)
我的 第一个问题如果用户提供只读字段,则会出现。如果这个请求体被 POST 了,这个结构就会愉快地接受
Id
当我进行插入时,它会覆盖自动增量值。我知道这在某种程度上是我使用 ORM 而不是手动编写 SQL 的错 insert
但我希望我可以以某种方式在对结构进行补充时强制只对那些可写字段进行解码,而忽略其他任何字段(或导致错误):POST /Foo
{"Id": 1, "Name": "test name"}
除了手动检查水合结构并取消设置我不希望用户提供的任何只读字段之外,我找不到其他简单的方法。
第二个问题我遇到的是确定用户何时取消设置值(传递
NULL
以更新字段)与未提供值的时间。这是 RESTful 术语中的部分更新/补丁。例如,假设存在 id=1 的 Foo。用户现在希望更新
Name
来自 test name
至 new name
和 Description
来自 test description
至 NULL
.PATCH /Foo/1
{"Name": "new name", "Description": NULL}
由于我的结构对其字段使用指针,因此我可以确定
Description
如果 foo.Description == nil
,则在创建时应设置为 null .但是在这次局部更新中,如何区分Description
的情况?没有提供(因此应该保持原样)和上面的情况,调用者希望设置 Description
的值至 NULL
?我知道有很多方法可以通过围绕我定义的每个结构编写大量自定义代码来解决这个问题,但我希望有一个不需要这么多样板的通用解决方案。我也看到过为 PATCH 请求采用不同正文格式的解决方案,但我必须满足现有契约(Contract),因此我不能为部分更新采用不同的格式。
我正在考虑几个选项,但都不让我满意。
interface
-typed 映射并编写代码来检查每个字段并根据需要断言类型。这样我就可以确定一个字段是否和 NULL
vs 根本没有提供。看起来工作量很大。 例如
type FooWrite struct {
Name *string
Description *string
}
type FooRead struct {
FooWrite
Id int32
CreateDate string
}
这篇文章解决了部分问题并使我走到了这一步,但没有解决我现在遇到的两个问题:https://willnorris.com/2014/05/go-rest-apis-and-pointers
我见过的大多数建议都围绕着改变我的架构设计和避免 NULL 值,但我没有能力修改它,因为它已经被其他消费者使用了。
最佳答案
这里的一种选择是使用自定义类型,特殊情况下 JSON 编码。例如,如果您想要一个在 JSON 中只读的整数,您可以执行以下操作:
type JsonReadOnlyInt int32
func (i JsonReadOnlyInt) MarshalJSON() ([]byte, error) {
return json.Marshal(int32(i))
}
func (i *JsonReadOnlyInt) UnmarshalJSON([]byte) error {
return nil // ignore attempts to set
}
如果您在其中一个结构中使用此类型,则整数将能够编码为 JSON,但在相反方向将被忽略:http://play.golang.org/p/lW7xuXR6y0
不过,要使这种类型与 GORP 一起使用还需要做更多的工作。看起来那个包使用标准库数据库转换接口(interface),所以你需要实现
Scanner
from database/sql
和 Valuer
from database/sql/driver
.像这样的东西:func (i *JsonReadOnlyInt) Scan(value interface{}) error {
// And maybe also cases for string/[]byte, depending on the driver
v, ok := value.(int64)
if !ok {
return errors.New("Could not scan")
}
*i = JsonReadOnlyInt(v)
return nil
}
func (i JsonReadOnlyInt) Value() (driver.Value, error) {
return int64(i), nil
}
现在您应该能够将这种类型的值往返传输到数据库。
就补丁问题而言,您可以尝试两种选择:
UnmarshalJSON
方法设置一个标志来表示它已被设置。 哪一个更合适可能取决于您的代码的其余部分。
关于rest - 当某些字段为只读而其他字段可为空时,如何使用 Golang 结构在 API 中执行 CRUD?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25150290/