为了账号安全,请及时绑定邮箱和手机立即绑定

使用后台任务实现更新用户资料API的实用方法

APIs是现代应用的支柱,但有时不仅需要进行 CRUD 操作,还需要完成更多任务。

假设一个场景,你需要更新用户的资料,并且发送后台通知和电子邮件——高效且不干扰主要请求。

我们来看看如何通过一种这样的结构化且受函数式编程启发的方式做到这一点。

问题说明

我们需要实现一个用户更新API,它需要具备以下功能:

  1. 接受更新用户名或电话号码的请求。
  2. 识别更改的字段。
  3. 根据识别出的更改更新数据库。
  4. 获取用户的设备令牌,并在后台发送通知。
  5. 异步发送邮件给用户。
  6. 返回适当的响应给客户端。
为什么要用函数式方法?

约翰·卡马克,一位传奇游戏开发者,他认为函数式编程可以减少那些讨厌的副作用,让软件更可靠。

在他看来,软件开发中许多问题是因为程序员没有充分理解他们的代码可能进入的所有状态。这个问题在多线程环境中更加严重,竞态条件可能导致不可预测的结果。

函数式编程通过让状态变得清晰和减少不必要的副作用来解决这些问题。

即使我们不使用Haskell或Lisp,我们仍然可以在主流语言如JavaScript和TypeScript中应用函数式编程的思想。

正如卡马克所说的一样,"无论你使用什么编程语言,用函数式编程风格都会给你带来好处。"

通过采用纯函数、不可变数据和职责分离的设计,我们的 API 更易于测试、维护和扩展,从而提高了 API 的可测试性、可维护性和可扩展性。

图片描述

设计 API(应用程序接口):

1. 路线定义

在这些后端框架中,如 Nest.js、Express 或 Django,我们定义一个“接口”:

PATCH 请求 /api/user/update

切换到全屏,退出全屏

2. 请求体

请求中应包含需要更新的字段信息:

    {
      // "userId": "12345",
      "username": "newUser123",
      "phoneNumber": "9876543210"
    }

进入全屏。退出全屏。

3. Nest.js 示例实现

我们采用结构化的方法来分离关注点,确保我们的功能模块既易于测试又可重用。

    import { Controller, Patch, Body } from '@nestjs/common';
    import { UserService } from './user.service';
    import { NotificationService } from './notification.service';
    import { EmailService } from './email.service';

    @Controller('user')
    export class UserController {
      constructor(
        private readonly userService: UserService,
        private readonly notificationService: NotificationService,
        private readonly emailService: EmailService,
      ) {}

      @Patch('update')
      async updateUser(@Body() body: any) {
        const { userId, username, phoneNumber } = body;

        // 检查哪些字段发生了变化
        const updates: Partial<User> = {};
        if (username) updates['username'] = username;
        if (phoneNumber) updates['phoneNumber'] = phoneNumber;

        // 以纯函数方式更新用户信息到数据库
        const updatedUser = await this.userService.updateUser(userId, updates);

        // 获取用户token并在后台发送更新通知
        this.notificationService.sendUserUpdateNotification(userId);

        // 在后台发送更新邮件给用户
        this.emailService.sendUpdateEmail(updatedUser);

        return { message: '用户信息已成功更新', data: updatedUser };
      }
    }

进入全屏 退出全屏

4. 数据库更新功能模块(纯函数方式)

以下的函数更新数据库中的数据并保证数据的一致性。

    // 更新用户信息的异步方法
    async updateUser(userId: string, updates: Partial<User>) {
      // 更新用户的ID和更新的内容
      return this.userModel.findByIdAndUpdate(userId, updates, { new: true });
    }

全屏切换进入 全屏切换退出

约翰·卡马克强调,纯函数仅对其输入进行操作并返回计算结果,不会修改任何共享状态。这种方法确保了:

  • 线程安全: 没有意外的副作用。
  • 容易在新环境中使用:
  • 对于相同的输入,总是给出相同的输出。

5. 背景通知管理

我们先拿到用户的令牌,然后发送推送消息,不会影响到主要请求。

    async sendUserUpdateNotification(userId: string) {
      const userDevice = await this.userDeviceModel.findOne({ userId });
      if (userDevice?.token) {
        pushNotificationService.send(userDevice.token, '您的资料已被更新。如果不是您自己更新的,请联系客服。');
      }
    }

点击进入全屏,点击退出全屏

6. 发邮件(后台运行)

电子邮件传输可能很慢,所以我们将其放入任务队列中,比如 BullMQ / Kafka

    async sendUpdateEmail(user: User) {
      emailQueue.add('sendEmail', {
        to: user.email,
        subject: '资料更新',
        body: '您的资料已成功更新。如果不是您本人更新,请联系客服。',
      });
    }

切换到/退出全屏

务实的平衡

并不是所有的东西都可以纯粹是为了功能性——实际的应用程序需要与数据库、文件系统和外部服务交互。

正如卡马克所言,“从更广泛的角度看,避免最糟糕的情况发生通常比在有限情况下追求完美更重要。”

与其在每个地方都强制保持严格纯洁性,我们的目标是在我们应用的关键部分减少副作用,同时以可控的方式处理必要的更改。

如图所示

这种方法的好处有
  • 非阻塞特性: 后台任务确保 API 始终保持响应。
  • 职责分离: 更新逻辑、通知和邮件处理各自独立。
  • 函数式思维: 数据库更新函数是一个纯函数,这使得测试更加容易。
  • 可扩展性: 后台处理比同步执行具有更好的扩展能力。
  • 代码可靠性: 减少副作用使调试更加简单。
接下来会怎样?

如果你对后端开发中的函数式编程感兴趣,并想更深入地探索,可以考虑可以参考一些在线教程或书籍。

  • 实现后台任务中的重试机制和错误处理。
  • 使用 Kafka 或 RabbitMQ 的事件驱动架构。
  • 研究 JavaScript 中的函数式编程库,如 Ramda 和 Lodash/fp 这样的库。

约翰·卡马克曾说过,用函数式编程风格编写代码确实有好处。

你最好在方便的时候就去做,并且在不方便的时候要仔细考虑一下这个决定。


我正在开发一个叫做 LiveAPI 的超级方便工具。

LiveAPI 帮助你在几分钟内整理好所有后端 API 的文档

使用LiveAPI(实时API),您可以轻松生成互动的API文档,让使用者在浏览器中直接运行API。

如图所示

如果你烦透了手动创建API的文档,这个工具可能让你更轻松地搞定API文档。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号

举报

0/150
提交
取消