当前位置: 首页 > 科技观察

个人信息保护法,有哪些重要细节?

时间:2023-03-15 17:01:24 科技观察

随着TypeScript的流行,类型系统逐渐进入了大家的视野,与类型安全相关的问题也受到了更多的关注。今天,我将从这个角度向大家展示Farrow在类型安全方面的出色解决方案。希望对大家有所启发。在这篇文章中,我将为您带来以下内容:TypeSafetyWhat&Why?Node.js主流Web框架现状当前API设计中的类型问题Farrow类型安全解决方案Farrow的未来规划好了,现在开始吧。类型安全关于类型安全,很多同学可能已经对它和Soundness[1]这个词有所了解,但应该也有很多同学不太了解。不知道的可以简单理解为:变量的行为与其类型相匹配,运行时不会出现类型错误。在JavaScript中执行以下操作:访问null属性、将字符串类型视为数字类型、调用对象上不存在的方法,所有这些都会在运行时抛出类型错误。那么我们为什么要追求类型安全呢?类型好的程序是不会出错的。——罗宾·米尔纳1978年《A Theory of Type Polymorphism in Programming》[2]正如上面这句话所说,类型系统可以有效提高程序的正确性:通过编译时的类型检查,尝试提前捕获可能的程序错误,提高代码的健壮性有了编辑器类型提示,类型检查比单元测试反馈更快、更早、更全面的实时测试。类型检查更优雅更清晰的优点不用多说,为了让我们的代码达到类型安全的状态,我们往往需要对要解决的问题进行建模,所以从这个角度来说,类型系统也可以帮助我们编写设计更好、质量更高的代码。主流框架现状我们之前在实际项目开发中遇到过Node.js框架选择的问题。经过研究,我们发现主流的Node.js框架:Express.js、Koa、EggJS、Hapi、Restify、Fastify等都是用JavaScript实现的,它们充分发挥了Javascript的能力,但是从类型安全,目前web框架的设计存在很多问题。API设计类型问题接下来我们以Express为例。意外的挂起请求(HangingRequest)我们发现像Express这样的中间件设计,它允许请求不被响应,并且无法通过TypeScript类型检查获得约束和提示。app.use((req,res,next)=>{//donothing});错误的对应内容(WrongResponse)同样,我们也不能保证像header->bodyapp.use((req,res,next)=>{//body是提前发送的responsesequenceres.end('xxx');//header必须在body之前发送,这里是error或者warningres.header('Content-Type','text/html');});也不能约束只发送一次bodyapp.use((req,res,next)=>{//body提前发送res.end('xxx');//body只能发送一次,这里报错或warningres.json({error:true});});而这些都会导致错误的响应内容。篡改对象属性(MonkeyPatching)在JavaScript中,我们可以任意修改对象属性,但是修改req/res或ctx会污染全链接中间件的类型。这将导致,在任何中间件中,你无法知道传递给当前中间件的对象中有哪些属性和方法。app.use((req,res,next)=>{//whattypeofres.localsres.locals.a=1;next();});app.use((req,res,next)=>{//whattypeofres.localsconsole.log(res.locals.a);});当然,有些框架支持一些类型注解的方案来解决类型提示的问题,但这并不能从根本上解决问题,从类型系统的角度来看,动态添加的属性或方法与静态注解的类型有着天生的矛盾。正确的做法是让静态类型决定属性是否可以赋值,而不是让属性赋值决定是否包含特定类型。NoRuntimeValidation(无运行时验证)在当前官方的TypeScript工具链中,TypeScript类型在编译后被擦除。确实,在大多数场景下,类型系统在编译阶段之前就完成了它的任务,但这也导致了一个严重的问题。请求内容未知,通常需要人工验证。app.use((req,res,next)=>{req.body;//bodytypeisany/unknownreq.query;//querytypeisany/unknownconstbody=req.bodyasMyBodyType;//typewllbeeliminated});如果请求参数比较复杂,通常需要编写复杂的验证逻辑。不友好的类型推导(PoorTypeInference)现有的框架在请求内容和响应内容的类型方面基本上没有提供更好的类型推导方案。很多时候,我们需要手动类型验证+类型转换。app.get('/user/:userId',(req,res,next)=>{req.params.userId;//notypeinferconstparams=req.paramsas{userId:string};constuserId=Number(params.userId);//每次都要手动转换});Problem到目前为止,我们在现有的API设计中提到了5类问题:HangingRequest(请求意外挂起)WrongResponse(错误的响应内容)MonkeyPatching(篡改对象属性)NoRuntimeValidation(没有运行时验证)PoorTypeInference(不友好的类型推导)虽然这些框架都没有使用TypeScript实现,但都提供了@types/*类型包,但是仍然存在很多类型问题,可见单靠*.d.ts无法获得足够的类型友好性和类型安全特性。我们对这些问题和现有的Node.js框架进行了系统的调研和思考,发现:基于Express/Koa,可以通过打补丁的方式解决一两类问题,但不能从根本上解决问题。Fastify提供了一种基于JSONSchema的解决方案,用于在运行时验证请求内容,但该方案与类型系统的契合度不高。要全面解决系统性问题,需要基于TypeScript的综合思考Type-FirstDevelopmentType-FirstDevelopmentType-DrivenDevelopment类型驱动开发Type-DrivenDevelopment为了做到这一点并解决上面提到的问题,我们需要一个新的类型安全的服务器端框架。类型安全的服务器端框架设计目标根据前面的问题,我们可以得到类型安全的服务器端框架设计目标:PreventHangingRequest(防止请求意外挂起)RefuseWrongResponse(拒绝错误响应内容)NoneedtoMonkey-Patching(无需篡改对象属性)EmbeddedRuntime-Validation(内置运行时验证)ExcellentTypeInference(优秀的类型推导)Farrow作者:以前做不到,做好之前能做到。于是就有了Farrow这样一个框架,接下来我就给大家介绍一下Farrow的一些设计,它是如何做上面提到的这些事情的。Farrow-HttpDesignPreventHangingRequest&RefuseWrongResponse首先,为了防止意外挂起请求和拒绝错误响应内容,Farrow重新设计了中间件,取消了响应参数,通过返回值来表达响应结果。import{Http,Response}from'farrow-http';consthttp=Http();http.use((request,next)=>{//responseisreturntypereturnResponse.text(request.pathname);});这样TypeScript也可以检查函数的返回值类型是否满足约束条件,如果没有响应或者响应类型不对,就会报错。防止错误响应为了进一步解决错误对应内容的问题,Farrow设计了VirtualResponse虚拟响应对象。Response.text等方法构造简单数据,类似VirtualDOM,不直接产生效果。它在多次使用后合并在一起。在Farrow框架中,header->body的顺序和类型会按照正确的顺序统一处理。import{Http,Response}from'farrow-http';consthttp=Http();http.use((request,next)=>{//responseisreturntypereturnResponse.text(request.pathname).header('abc','efg').text('changedtext');});不需要Monkey-Patching(Request)为了解决Monkey-Patching的问题,即不再推荐和引导开发者修改req请求对象,Farrow设计了VirtualRequest虚拟的request对象,所以传入中间件的request对象不是原来的req对象,而是从中提取出来的plaindata,所以可以通过next(newRequest)向后传递新的request对象,而不用修改原来的目的。import{Http,Response}from'farrow-http';consthttp=Http();http.use((request,next)=>{returnnext({...request,pathname:'/another/pathname',});});http.use((request,next)=>{request.pathname;//isequalto/another/pathname});不需要Monkey-Patching(Response)为了进一步解决Monkey-Patching的问题,Farrow重新设计了中间件管理机制,next会返回下游中间件的response对象,后面可以处理,这样不需要修改res/ctx.body,immutable比mutable更类型友好,preferimmutable。import{Http,Response}from'farrow-http';consthttp=Http();http.use((request,next)=>{letresponse=awaitnext();//合并,组合,过滤,组装一个新的响应returnResponse.header('abc','efg').merge(response);});http.use((request,next)=>{returnResponse.text('helloworld!');});不需要Monkey-Patching(Middleware)虽然前面的方面解决了修改request对象的问题,但是仍然没有解决中间件之间共享变量的问题,所以Farrow提供了Context+Hooks的解决方案,他们的工作机制类似于ReactContext和ReactHooks,类似于跨组件传递数据的方式,跨中间件传递Context数据,这样中间件之间的共享变量就不需要挂载到req对象上了,而且得益于Node.js的新特性Asynchooks,Farrow可以提供按需的、分布式的、细粒度的、关注点分离的、类型安全的ContextPassing机制。import{Http,Response,createContext}from'farrow-http';consthttp=Http();//创建上下文constAuthContext=createContext(null);//更新上下文http.use(async(request,next)=>{AuthContext.set(awaitgetAuth(request));returnext();});//无论中间插入多少个中间件,请求/响应类型都不会污染//消费上下文http.use((request,next)=>{//跨中间件访问上下文数据letauth=AuthContext.get();returnResponse.text('helloworld!');});EmbeddedRuntime-Validation&ExcellentTypeInference(Schema)以提供runtime验证和更友好的Type推导能力,Farrow设计了一套对TypeScript开发者非常友好的SchemaBuilder,从而提供了基于Schema的RuntimeValidation机制,让开发者可以使用SchemaBuilder来描述请求的形状,基于此shapeFarrow会自动推导出请求对象的类型,从而保证了请求对象的值object将在运行时满足Schema描述的形状。通过这种方式,我们提供了运行时验证和友好的类型推导。import{Http,Response}from'farrow-http';import{Int}from'farrow-schema';consthttp=Http();http.match({pathname:'/user',method:'post',body:{userId:Int,userName:String,userAge:Int,},}).use((request,next)=>{//request.bodyis{userId,userName,userAge}console.log('userId',request.body.userId);console.log('userName',request.body.userName);console.log('userAge',request.body.userAge);});EmbeddedRuntime-Validation&ExcellentTypeInference(URL)Later我们发现很多时候我们似乎并不需要这么复杂的数据结构,所以Farrow提供了一个更简单的描述:import{Http,Response}from'farrow-http';import{Int}from'farrow-schema';consthttp=Http();http.get('/greet/?&farrow=type-safety').use((request,next)=>{//typeinferforrequestfromurlconsole.log('name',request.params.name);console.log('age',request.query.age);console.log('farrow',request.query.farrow);});它是基于TypeScript4.1发布的Templateliteraltypefeature实现的,它从URL中提取TypeScript类型,然后自动识别它是params参数还是查询参数,并自动将String转换成带标签的Int、布尔和其他模式类型。基于此,我们还可以提供运行时验证和友好的类型推导。上面的farrow-http采用了Type-FirstDevelopment思想和受React启发的functional/immutable概念,系统地提高了WebFramework的类型安全级别,解决了以下问题:PreventHangingRequest(防止请求意外挂起)√RefuseWrongResponse(拒绝错误响应内容)√无需Monkey-Patching(无需篡改对象属性)√EmbeddedRuntime-Validation(内置运行时验证)√ExcellentTypeInference(出色的类型推导)√新挑战:end-to-endtypesynchronizationfarrow-Http优化了ServiceSide的类型安全,只解决了一半问题,End-to-endtyping将是一个新问题。ClientSide如何复用ServiceSide的类型?ClientSide类型如何与ServiceSide保持一致和同步?于是我们重新思考:BFF应该为前端提供什么?传统BFF:为前端提供数据现代BFF:为前端提供数据和类型后现代BFF:为前端提供数据、类型和代码为了做到这一点,Farrow提供了一个新的解决方案:farrow-api.Farrow-API设计Farrow采用Introspection+Codegen的方式为前端提供数据、类型和代码。提供类似于GraphQL的Introspection机制,支持拉取farrow-api的Schema数据,然后通过CodeGeneration生成TypeScriptType和HTTPClientCode。在服务器端,描述请求和响应的形状,然后聚合成FarrowAPI,然后为API实现请求处理功能,然后启动服务器,在客户端可以生成如下代码,开发者在客户端只需要导入生成的函数,然后调用即可。此外,farrow-api还支持其他描述API的属性,比如@deprecated标签。至此,Farrow实现了服务器的类型安全,也解决了C/S模式下的类型同步问题。Farrow蓝图和未来前景优势除了类型安全,Farrow的设计还带来了一些其他的优势。有了Schema之后,就可以形成接口的知识库了。知识库可以用来做很多事情,比如功能层面的接口监控和测试,版本控制等。未来规划Farrow目前在规划上主要有两个方向:第一个是生态,因为Farrow目前的开发团队比较小,所以无论是一些基础的工具库、文档,还是最佳实践,都是缺失和不完整的,但是缺乏这些内容导致很少有开发者能够理解和使用Farrow,所以这将是Farrow团队未来的主要工作方向。另外,就是基础能力。Farrow还不够系统,我们还没有完全发挥它的潜力,所以会投入大量精力继续探索它的能力边界。奖励:farrow-express&farrow-koa我要告诉你的好消息是:Farrow现在可以通过适配器重用Express/Koa和其他生态系统:farrow-express:在ExpressApp上运行farrow-httpfarrow-koa:willfarrow-httprunningonaKoaApp总结在这篇文章中,我们查看了类型安全的定义及其价值,我们看到了当前Node.js中存在的类型问题。我们看到了Farrow-API如何连接前端和后端类型我们了解了Farrow如何立即用于Express/Koa等应用程序我们了解了Farrow和其他追求类型安全问题的框架未来