rest - 当某些字段为只读而其他字段可为空时,如何使用 Golang 结构在 API 中执行 CRUD?

标签 rest struct go

我正在编写一个用于执行基本 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 namenew nameDescription来自 test descriptionNULL .
PATCH /Foo/1
{"Name": "new name", "Description": NULL}

由于我的结构对其字段使用指针,因此我可以确定 Description如果 foo.Description == nil,则在创建时应设置为 null .但是在这次局部更新中,如何区分Description的情况?没有提供(因此应该保持原样)和上面的情况,调用者希望设置 Description 的值至 NULL ?

我知道有很多方法可以通过围绕我定义的每个结构编写大量自定义代码来解决这个问题,但我希望有一个不需要这么多样板的通用解决方案。我也看到过为 PATCH 请求采用不同正文格式的解决方案,但我必须满足现有契约(Contract),因此我不能为部分更新采用不同的格式。

我正在考虑几个选项,但都不让我满意。
  • 使用 interface -typed 映射并编写代码来检查每个字段并根据需要断言类型。这样我就可以确定一个字段是否和 NULL vs 根本没有提供。看起来工作量很大。
  • 为每个场景定义多个结构。这感觉有点干净,但也有点不必要的冗长。它只解决了我遇到的两个问题之一(强制只读),但不能确定 NULLable 字段何时在部分更新时实际被清空或只是未提供。

  • 例如
    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
    }
    

    现在您应该能够将这种类型的值往返传输到数据库。

    就补丁问题而言,您可以尝试两种选择:
  • 只需解码成一个结构,保存记录的旧值。 JSON 对象中缺少的任何字段都不会更新,并且可以使用上述策略保护您的只读字段。
  • 使用自定义结构类型来表示您的字段,而不是像上面那样的简单整数。使其零值对应unset,并使其UnmarshalJSON方法设置一个标志来表示它已被设置。

  • 哪一个更合适可能取决于您的代码的其余部分。

    关于rest - 当某些字段为只读而其他字段可为空时,如何使用 Golang 结构在 API 中执行 CRUD?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25150290/

    相关文章:

    javascript - 将 https 请求发送到本地 http(s) 服务器

    java - Spring MVC 图片上传使用 Mockmvc 和 JSON 集成测试失败

    c++ - C++ 结构中的第一个 double 自动缩短为两位小数

    image - 更改单个像素的颜色 - Golang 图像

    templates - 部分变量不可见

    go - Gorm Preload 功能在性能方面是一个很好的实践吗?

    java - Spring Boot启动后如何获取所有端点列表

    rest - WCF 代理使用 Post 即使指定了 WebGet 属性(仅当从另一个 WCF 服务调用时) - 导致 405 错误

    c - 从指针结构打印成员

    c++ - C/C++ : size of a typedef struct containing an int and enum == sizeof(int)?