尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Redwood Service 层实战指南:Service Validations 与服务缓存全覆盖,快速搭出毫秒级业务层
Redwood Service 层实战指南Service Validations 与服务缓存全覆盖快速搭出毫秒级业务层【免费下载链接】redwoodRedwoodGraphQL项目地址: https://gitcode.com/gh_mirrors/re/redwood给应用加一个创建用户接口你要同时挡住格式错误、挡住越权操作、挡住重复邮箱还希望读接口快到毫秒级。在 Redwood 里这四件事都落在同一层Service 层。本文以一个真实函数为主线把 Service Validations 字段校验、唯一性事务、服务缓存三大件拆给你看。一个 Service 函数的分层解剖工具想摸清得先读一个完整的 Service再逐层拆开。浏览器请求如何抵达 Service调用链其实不复杂先看这张图浏览器把请求发给 GraphQL 端点而 schema 里的 resolver 就是你在api/src/services里写的那些函数——GraphQL 语境下的resolver与 Redwood 的 Service 一一对应。这里有个容易忽略的细节GraphQL 端点本身就是一个 serverless functionService 就藏在它后面。为什么业务规则必须放服务端想象一下银行的取款校验跑在客户端服务端只接收转 1 万元的指令而不再复核——银行上线即破产。所以永远不要信任客户端判断逻辑全部住在服务端在 Redwood 里就是 Service 层。而且 Service 在 web 侧根本用不了一方面 Redwood 的构建流程不支持跨端引入另一方面 Prisma 这类数据库客户端在浏览器里跑不了就算能跑浏览器也会照单全收你塞给它的任何命令——db.user.deleteMany()一执行整表就没了。Service 只是函数。正因为如此它能被三处复用作为 GraphQL resolver、被另一个 Service 调用、被api/src/functions下的 serverless function 调用。比如转账Service 是负责在两个账户间移动资金的唯一实现GraphQL 转账接口和 webhook 触发的 serverless function 都只是调用它业务规则在全代码库里只有一份。同一个函数逐层读下来调用链讲完了现在看函数本体。下面是一个完整的创建用户Service按入口静态校验 → 上下文校验 → 唯一性事务 → 数据写入四层展开这也是 Redwood 写接口 Service 的标准骨架import { validate, validateWith, validateWithSync, validateUniqueness, } from redwoodjs/api export const createUser async ({ input, context }) { // 1. 入口静态校验格式不对大门就进不来 validate(input.email, Email Address, { presence: true, email: { message: Please enter a valid email address }, length: { max: 255 }, }) validate(input.role, Role, { inclusion: [Member, Manager] }) // 2. 上下文校验谁能操作取决于登录态 validateWithSync(() { if (input.role Manager !context.currentUser.roles.includes(admin)) { throw Only Admins can create new Managers } }) // 3. 唯一性事务查重 写入同一事务内完成 return validateUniqueness(user, { email: input.email }, (db) { return db.user.create({ data: input }) }) }逐层说职责validate只看输入本身不碰数据库也不碰登录态validateWithSync看谁在操作管理员权限validateUniqueness看数据库里是否已存在并保证查重和写入不被打断。剩下两层回调里的db.user.create就是真正的数据读写而这个导出函数放进api/src/services后代码生成会自动把它包装成 GraphQL resolver暴露为 resolver不需要任何额外配置。至于缓存包裹读接口把慢查询套一层cache()即可缓存章节细讲函数签名保持不变。校验工具箱按场景选武器上面那个函数是四件武器的组合拳。按场景挑对工具比背下所有 API 更重要。字段格式类数据入库前如何完成字段校验从最轻的一类说起只需要看字段本身。validate()是字段校验的主函数有两参数和三参数两种形式区别在于错误消息由谁提供// 二参数形式没有字段名message 必须自己写 validate(input.email, { email: { message: Please provide a valid email address } }) // 三参数形式给字段名默认消息由它生成 validate(input.email, Email Address, { email: true })三参数形式会输出Email Address must be formatted like an email address这样的消息二参数形式则原样输出你写的内容。一个字段同时挂多条规则时只要部分规则带了自定义消息就必须用三参数形式——没带消息的规则需要字段名来生成消息。高频的五个校验器各给一个例子// presence默认空字符串 能通过必填字段记得设 false validate(input.lastName, Last Name, { presence: { allowEmptyString: false } }) // lengthmin / max / equal / between 可任意组合 validate(input.name, Name, { length: { min: 2, max: 255 } }) // email内置极宽松的正则覆盖绝大多数场景 validate(input.email, Email Address, { email: true }) // exclusion禁用值caseSensitive 默认 true validate(input.name, Name, { exclusion: [Admin, Owner] }) // numericality先确保是 number再做范围与奇偶判断 validate(input.age, Age, { numericality: { integer: true, greaterThan: 0, lessThanOrEqual: 150 }, })其余校验器全部进这张速查表。所有校验器都支持message消息支持${name}、${min}这类插值注意必须写普通字符串反引号模板串会在值就绪前被提前插值校验器作用主要选项absence字段必须不存在null/undefinedallowEmptyStringacceptance值为true或位于允许值列表inemail邮箱格式内置宽松正则—exclusion值不得在禁用列表in、caseSensitiveformat匹配给定正则对象形式漏传pattern会直接抛错避免误通过patterninclusion值必须在允许列表in、caseSensitivelength字符串长度min、max、equal、betweennumericality必须是数字且满足数值条件integer、lessThan、greaterThanOrEqual、even、positive等presence字段必须存在allowNull、allowUndefined、allowEmptyStringcustom自定义函数抛错即失败with两个容易踩的细节presence的默认行为等价于{ allowNull: false, allowUndefined: false, allowEmptyString: true }空字符串默认能通过表单必填字段这是第一检查点custom会捕获with抛出的任何异常消息优先级是你提供的message 异常自带 message 抛出的字符串。动态业务类为什么必须用它们抛错上一类静态规则不需要任何上下文就能判断。但当前用户能不能创建 Manager这个用户邀请名额用完了没依赖登录态或数据库状态。你会说自己写 if 然后throw不就完了问题在于错误要经过 GraphQL——能安全透传到前端的只有 ServiceValidationError 及其子类。裸抛的错误会被安全机制吞掉错误消息可能泄露源码细节用户只看到 Something went wrong。validateWithSync()和validateWith()存在的意义就是把你抛出的错误捕获并重新包装成 ServiceValidationError放进白名单。前者同步后者支持await异步操作// 同步依赖登录态 validateWithSync(() { if (input.role Manager !context.currentUser.roles.includes(admin)) { throw Only Admins can create new Managers } }) // 异步依赖数据库状态 await validateWith(async () { const count await db.user.count({ where: { invitedBy: context.currentUser.id } }) if (count 10) throw You have already invited your max of 10 users })错误怎么落到前端字段下方看错误类定义错误被构造成 GraphQLBAD_USER_INPUT风格的错误码并附带properties.messages扩展web 侧的FormError组件据此把消息渲染到对应字段下——scaffold 页面的表单保存失败时提示直接出现在字段旁边靠的就是这条链路。唯一性事务类一次事务里查重加写入这个邮箱库里有没有和前面两类不同结果取决于数据库还带竞态。两个请求同时查到一个空邮箱、同时写入总有一个要踩坑。validateUniqueness()的写法是把查重 写入塞进同一个 Prisma 事务先按给定字段findFirst查到就抛错否则执行回调。查和写必须在同一事务内否则查重形同虚设。// 创建查 email 唯一性后写入 return validateUniqueness(user, { email: input.email }, (db) db.user.create({ data: input }) ) // 更新$self 排除记录自身避免自己占用自己误报 return validateUniqueness( user, { email: input.email, $self: { id: input.id } }, (db) db.user.update({ where: { id: input.id }, data: input }) ) // 限定范围只查同一家公司内部两家公司可以有同名产品 return validateUniqueness( product, { name: input.name, $scope: { companyId: input.companyId } }, (db) db.product.create({ data: input }) )两个特殊键的语义很直接$scope追加进查询条件的 AND收窄检查范围$self追加进 NOT排除自己。可选的第三个参数还能传message自定义错误消息和db自定义 PrismaClient——如果你实例化时覆盖了DATABASE_URL就必须传它否则回调默认用原生new PrismaClient()读不到你的自定义配置。为什么不在数据库上直接建 UNIQUE 索引了事Prisma 在违反唯一约束时抛出的错误同样会被 GraphQL 吞掉你没法告诉用户这个邮箱已被注册而validateUniqueness抛出的错误在白名单里可以原样发到浏览器。在无法加唯一索引的场景遗留 schema它更是唯一正解。实现细节见 validations.ts 中的 validateUniqueness。让 Service 快到毫秒级缓存实战写链路稳了读链路呢Service 里只要有开销大、且不是每个请求都变的计算服务缓存就是为它准备的。什么数据值得缓存标准不高记住两条计算贵、结果变慢。比如商店首页的最热门商品列表可能要聚合购买量、浏览量、停留时长、社交热度算几秒很正常但列表本身变化缓慢。包进cache()之后除第一个用户外所有人都毫秒级返回连第一个用户的等待都能省掉——用服务器上的定时进程周期性调用该 Service预热缓存让进程而不是用户去承担这次等待。一个前提要记牢服务缓存只能存字符串。数据在写入和读取时各经历一次JSON.stringify()/JSON.parse()往返见 缓存入口 中的serialize。Date 实例回来是2022-08-24T17:50:05.679Z这样的字符串需要重新new Date()函数更是无法存活。大多数 Prisma 查询结果没问题留意含二进制数据的 BLOB 即可。缓存 Key 设计三步从名字到版本号定了要缓存什么真正的坑才出现key。老话讲计算机科学只有两大难题——缓存失效和命名而失效问题的根源几乎都在 key 上。推演一遍完整演进。第一版拿产品名当 key比如led light strip——不行两家制造商可以有同名产品key 撞车。第二版用数据库 ID41443——唯一了但哪天加了用户缓存用户 41443 和产品 41443 就互相踩脚你以为取的是用户拿到的却是产品。第三版在 key 里加类型product-41443——产品和用户各自命名空间撞车问题解决了。但第三版暴露了真问题缓存只能读到已存在的 key或创建新 key无法更新已有 key。产品描述变了旧 key 怎么知道答案只有一条路把变化信号编进 key让 key 自己变成版本号。变化信号通常有三种取法updatedAt 时间戳Prisma 在记录变化时自动更新它任何一列变了它就变。用getTime()转成整数key 变成product-41443-1661464626032。副作用是可能过度敏感views这种页面一打开就变的列会让缓存频繁失效——如果页面上根本不展示这个计数把该列拆到独立表里用外键指回来。人工版本号key 前加v1做大改版需要强制刷新时改成v2不碰数据库就整体失效。过期时间如果你能接受用户一小时内看不到变化也没关系直接expires: 3600key 到点自动失效连哪列会变都不用想。三者组合就是 key 的最终形态版本号 类型 ID 变化信号一个都不缺// 缓存 key【免费下载链接】redwoodRedwoodGraphQL项目地址: https://gitcode.com/gh_mirrors/re/redwood创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED

相关推荐

样章-第08讲-用状态机管理设备生命周期

样章-第08讲-用状态机管理设备生命周期

第 8 讲 | 用状态机管理设备生命周期【本讲要点】当设备有 6 个阶段、10 个事件时,if-else 会变成一团解不开的乱麻。状态机不是"架构洁癖",它是小内存系统的救命稻草。一、标志位地狱 先看一段真实项目里被"迭代"出来的代码&#x…

📅 2026/10/31 6:52:45
Folia PWA 手机化完整指南:3 分钟把全屏歌词动画播放器装进主屏幕

Folia PWA 手机化完整指南:3 分钟把全屏歌词动画播放器装进主屏幕

Folia PWA 手机化完整指南:3 分钟把全屏歌词动画播放器装进主屏幕 【免费下载链接】folia-major 专注于绚丽的歌词动画效果的本地音乐/navidrome/第三方多平台在线音乐播放器 项目地址: https://gitcode.com/GitHub_Trending/fo/folia-major Folia 是一款主打…

📅 2026/10/31 6:52:45
mermaid-live-editor:免费在线 Mermaid 图表编辑器,几行文本即可预览、导出并分享

mermaid-live-editor:免费在线 Mermaid 图表编辑器,几行文本即可预览、导出并分享

mermaid-live-editor:免费在线 Mermaid 图表编辑器,几行文本即可预览、导出并分享 【免费下载链接】mermaid-live-editor Edit, preview and share mermaid charts/diagrams. New implementation of the live editor. 项目地址: https://gitcode.com/G…

📅 2026/10/31 6:52:45
MORE NEWS

更多资讯

📰

10款免费网络安全抓包工具详解与选型指南

1. 网络安全抓包工具概述在网络安全领域,抓包工具就像网络工程师的"听诊器",能够实时监听和分析网络通信数据。这类工具通过捕获网络接口传输的数据包,帮助安全人员诊断网络问题、分析协议交互、检测安全漏洞。对于刚入门网络安全的…

📰

MySQL高可用架构实战:主主复制+HAProxy+Keepalived

1. MySQL高可用架构核心设计解析在企业级数据库应用中,单点故障是绝对不能容忍的风险。我经历过多次深夜被叫醒处理数据库宕机的惨痛教训后,彻底认识到高可用架构的重要性。本次搭建的MySQL高可用方案采用主主复制HAProxyKeepalived的组合,这…

📰

灰度放量与准入控制:GATER五段式门控框架落地实践

做灰度放量和准入控制这件事&#xff0c;我前前后后折腾了好几年。最早是在业务代码里塞if user_id % 100 < 5&#xff0c;后来是配置文件里写一堆开关&#xff0c;再后来是一堆人围着发布窗口熬夜盯监控。真正让我下决心把这件事抽象出来&#xff0c;是因为一次不太体面的线…

📰

Hive在电商大数据处理中的核心应用与优化策略

1. Hive在电商行业的核心价值解析电商行业每天产生的数据量级早已突破传统数据库的处理极限。以某头部电商平台为例&#xff0c;其单日用户行为日志就超过20TB&#xff0c;传统MySQL这类关系型数据库在这种场景下完全无能为力。这正是Hive这类大数据工具的价值所在——它不仅能…

📰

Node-RED 集成 InfluxDB v2.x 实战:写入查询闭环与生产避坑指南

简介&#xff1a;本资源是 Node-RED 生态中专用于 InfluxDB 时间序列数据库交互的官方扩展节点包&#xff08;node-red-contrib-influxdb&#xff09;&#xff0c;面向物联网开发、工业数据采集及边缘计算场景下的中级以上开发者&#xff0c;解决在低代码流式编程环境中高效写入…

📰

如何准确获取真实屏幕分辨率:跨平台实战指南

1. 项目概述&#xff1a;为什么“屏幕分辨率”突然成了2020年的生活刚需&#xff1f;2020年那会儿&#xff0c;我帮三个不同行业的客户做远程协作系统适配&#xff0c;结果全卡在同一个地方——不是网络延迟&#xff0c;不是软件崩溃&#xff0c;而是屏幕分辨率不匹配导致的界面…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬