您可能忽略了REST API中的PUT方法
您可能忽略了 REST API 中的 PUT 方法
在构建 REST API 时,无论是您的第一个项目还是第百个项目,或者您正在学习相关教程,您可能会涉及到为端点实现 PUT HTTP 方法。为了更好地理解如何实现 PUT 方法的基本工作流程,我们将通过一个示例代码片段进行演示。本文中使用的是 Golang,但您可以使用自己熟悉的编程语言。以下是一个名为 Pokemons 的表的模式:
CREATE TABLE pokemons(
id bigserial PRIMARY KEY,
created_at TIMESTAMP(0) with time zone NOT NULL DEFAULT NOW(),
name TEXT NOT NULL,
region TEXT NOT NULL,
atk INTEGER NOT NULL,
def INTEGER NOT NULL
);
使用 GET 方法检索数据
在实现更新逻辑之前,我们需要先通过 GET 方法检索用户想要更新的记录。以下是一个示例代码:
return &pokemon, nil
}
func (m PokemonModel) Get(id int64) (*Pokemon, error) {
if id < 1 {
return nil, ErrRecordNotFound
}
query := `
SELECT id, created_at, name, region, atk, def
FROM pokemons
WHERE id=$1
`
var pokemon Pokemon
// 设置超时上下文,防止查询时间过长
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
// 查询数据库并将结果存储到 pokemon 结构体中
err := m.DB.QueryRowContext(ctx, query, id).Scan(&pokemon.ID, &pokemon.CreatedAt,
&pokemon.Name, &pokemon.Region, &pokemon.Atk, &pokemon.Def)
if err != nil {
switch {
case errors.Is(err, sql.ErrNoRows):
return nil, ErrRecordNotFound
default:
return nil, err
}
}
return &pokemon, nil
}
实现更新方法
在获取到记录后,我们可以通过以下代码实现更新操作:
func (m PokemonModel) Update(pokemon *Pokemon) error {
// 更新 SQL 查询
query := `
UPDATE pokemons
SET name=$1, region=$2, atk=$3, def=$4
WHERE id=$5
RETURNING id
`
// 占位符参数的值
args := []interface{}{pokemon.Name, pokemon.Region, pokemon.Atk,
pokemon.Def, pokemon.ID} // 设置超时上下文
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel() // 执行更新查询并返回结果
return m.DB.QueryRowContext(ctx, query, args...).Scan(&pokemon.ID)
}
到这里,您可能会认为更新逻辑已经完成。然而,如果我告诉您,这种实现方式可能会导致 数据竞争 问题,您会怎么想?
数据竞争问题的产生
数据竞争是指在多个线程同时访问共享资源时,由于操作顺序的不确定性而导致的冲突问题。在我们的示例中,Pokemons 表中的记录是一个共享资源,可能会被多个线程同时访问。
例如,Alice 和 Bob 同时对 id 为 376 的记录发起 GET 请求,并分别获取到相同的记录。当他们几乎同时发送 PUT 请求来更新记录时,就会发生数据竞争。
- Alice 想将 atk 字段更新为 330。
- Bob 想将 def 字段更新为 400。
由于请求处理的顺序不确定,最终可能只会保留 Alice 的更新,而 Bob 的更新被覆盖。这种情况显然是不理想的。
解决数据竞争的方法
为了解决数据竞争问题,我们可以采用以下两种主要方法:
- 悲观锁定:在操作资源时锁定记录,直到操作完成。
- 乐观锁定:通过版本控制机制检测并避免冲突。
在本文中,我们将使用 乐观锁定 来解决数据竞争问题。
在表中添加版本列
使用乐观锁定的第一步是为表添加一个 version 列,用于跟踪记录的版本号。初始版本号默认为 1:
ALTER TABLE pokemons
ADD COLUMN version INTEGER NOT NULL DEFAULT 1;
在查询中包含版本字段
接下来,我们需要在 GET 方法的查询中包含 version 字段:
query := `
SELECT id, created_at, name, region, atk, def, version
FROM pokemons
WHERE id=$1
`
// 将 version 字段存储到结构体中
err := m.DB.QueryRowContext(ctx, query, id).Scan(&pokemon.ID, &pokemon.CreatedAt,
&pokemon.Name, &pokemon.Region, &pokemon.Atk, &pokemon.Def, &pokemon.Version)
更新方法的改进
在更新记录时,我们需要确保版本号未被其他请求修改,同时将版本号递增 1。以下是改进后的更新方法:
// 自定义错误,用于通知数据竞争
var ErrEditConflict = errors.new("edit conflict")
func (m PokemonModel) Update(pokemon *Pokemon) error { query := `
UPDATE pokemons
SET name=$1, region=$2, atk=$3, def=$4, version=version+1
WHERE id=$5 AND version=$6
RETURNING version
` args := []interface{}{pokemon.Name, pokemon.Region, pokemon.Atk,
pokemon.Def, pokemon.ID, pokemon.Version} ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel() err := m.DB.QueryRowContext(ctx, query, args...).Scan(&pokemon.Version) if err != nil {
switch {
case errors.Is(err, sql.ErrNoRows):
return ErrEditConflict
default:
return err
}
}
return nil
}
通过在更新条件中加入版本号检查,我们可以确保只有在版本号未被修改的情况下才能进行更新操作。
后续操作
处理数据竞争的一个可能后续操作是使用自定义错误(如 ErrEditConflict),将冲突事件发送到事件队列中,并在后台工作器中重试失败的查询。无论您选择哪种方式,重要的是采取措施解决数据竞争问题。
尽管本文中的示例较为简单,但类似的错误在某些场景下可能会导致严重后果,例如更新账户余额时。因此,理解和解决这些问题是至关重要的。
希望本文能帮助您更好地理解在 REST API 中实现 PUT 方法时可能遇到的问题,并为您提供解决方案的思路。
原文链接: https://medium.com/@k1nho/you-might-be-overlooking-the-put-method-in-your-rest-api-156cd86c852d
最新文章
- 如何获取 tianqiip 开放平台 API Key 密钥(分步指南)
- Python实现表情识别:利用稠密关键点API分析面部情绪
- RWA 上链秒级碳信用合规评级 API:5 天
- 香港稳定币条例 GDPR 删除权 API:3 天合规实现
- Ktor 入门指南:用 Kotlin 构建高性能 Web 应用和 REST API
- 什么是API模拟?
- 基于NodeJS的KOA2框架实现restful API网站后台
- 2025 AI 股票/加密机器人副业|ChatGPT API 策略+TG Bot 信号 99 元/月变现
- 舆情服务API应用实践案例解析
- 为什么API清单是PCI DSS 4.0合规的关键
- 优化 ASP.NET Core Web API 性能方法
- API与REST API的区别?