MEAN Stack:创建RESTful web service

本文在个人博客上的地址为URL,欢迎品尝。

前段时间做了DTREE项目中的前后端数据存储功能,在原有的ngController上进行HTTP请求,后端接受到请求后再存储到mongoDB上。现将学习所得记录成这篇文章。大致内容为REST的相关概念的介绍,以及结合项目实践的一些实战经验,最后一个RESTful的Web Service就成功开发出来了(大雾)。


  1. REST 
    REST(Representational State Transfer)是一种软件设计架构风格,它定义了一堆概念,这些抽象的概念和相关规则能有效地降低业务复杂度。而日常上网使用HTTP就是REST风格最好的践行,RESTful Web服务就是将HTTP当做应用层的协议来遵守使用,而不像其他(比如SOA)将HTTP当做传输层的工具,然后在HTTP之上建立自己的一套应用层协议。

    当我们在浏览器地址栏键入的URL/URI就是REST提供的资源(resource)定义,而资源在REST中是一个的概念(concepts),当浏览器发出请求时,它想要得到的是一个概念的特定表示(representation),我们常见的网页就是资源的具体表示。REST这一些定义的概念、原则为了就是最小化服务与使用服务的应用程序之间的耦合。我们设计RESTful Web服务的原则也只是遵守了REST的部分原则。图片来自"浅谈REST",我们可以在REST Triangle图中看出,URL就是REST所谓的名词(资源的位置),一些HTTP方法就是动词,JSON、XML数据格式充当资源的具体表示。

    下面简述4个RESTful Web Service基本原则,摘自IBM的"基于REST的 Web 服务基础"

    显式地使用 HTTP 方法

    设计RESTful时,使用4种比较多态的方法(POST、GET、PUT、DELETE)实现数据存储的CRUD操作。HTTP协议已经对这些动词(POST、GET、PUT 和 DELETE)进行了定义。

    • HTTP GET方法用于获取资源,无论调用的次数都不会改变资源的状态,可能会得到不同的结果,但它本身不产生副作用(即对存储在source服务器的数据不产生的修改)。
    • HTTP DELETE方法用于删除资源虽然有副作用,但多次调用时产生的副作用应该是相同的,即URI所对应资源的删除。
    • HTTP POST方法用于接受创建的资源,如果资源已经在服务器上创建,服务器响应应该是表示Created的201状态以及资源的URI,避免POST请求会在服务器创建多份相同的资源。
    • HTTP PUT方法用于创建或更新资源,多次操作的副作用与一个PUT操作是相同的。即若服务器没有PUT的资源则创建一个,否则更新已有资源即可。

    这些动作的结果是必须有预期,不应该出现语义问题,很典型的例子就是在应用中只使用GET充当一切前后端交互的方法,导致的结果就是一些Web爬虫的行为无意间导致服务器端的资源更改。

    无状态

    网络传输的无状态使得每个请求是独立完整的,这样能够将请求从一个服务器路由到另一个服务器而无状态上的协调。当然请求是有状态的,将大部分状态维护职责转移给客户端应用程序,能够节省带宽和最小化服务器端应用程序状态改进了性能。

    公开目录结构式的URI

    URI是具有在节点上连接在一起的下级和上级分支的树,这种树形结构能够直观地表达交互类型和资源名称,也可将URI看做文档说明的接口。

    传输格式使用XML、JavaScript Object Notation(JSON)等数据格式

    这些数据格式能够使得服务可以运行在不同平台和设备上,并采用不同的语言编写。

  2. ngResource 
    一直以来在前端进行向后端数据交互都是使用$http服务,这次师哥教育到要学习使用ngResource,提倡"keeping $http out of the controllers and leave that job to services"。使用ngResource就不用去关心更底层的$http服务,它帮我们封装好用一种更简单的方式来发送XHR请求。

    ngResource提供$resource服务(service)来与RESTful后端交互。首先我们需要将angular-resource.js引入,然后建立工厂方法,在里面返回$resource(‘URI‘,,params, methods)获得的值,这样一个自定义的服务就产生了。使得用短短的几行代码就可以创建一个RESTful客户端,简化controllers。

  3. Code

    首先我们设计Node接受前端发来的请求时的路由分配,暂时只提供了下列针对单个dtree对象的CRUD操作。

    ///store API
    app.post(‘/dtree‘, dtreeCtrl.createDTree);//create
    app.get(‘/dtree/:dtree_id‘, dtreeCtrl.readDTree);//read
    app.put(‘/dtree/:dtree_id‘, dtreeCtrl.updateDTree);//update
    app.delete(‘/dtree/:dtree_id‘, dtreeCtrl.deleteDTree); // delete
    

    我们在dtreeCtrl暴露出处理请求API,对于POST操作处理如下。

    
    

    对于GET操作,我们使用dtree_id当做_id进行搜索,调用mongoose API .lean() 将结果转换为plain javascript objects。

    //execute route GET /dtree/:dtree_id
    exports.readDTree = function (req, res) {
        var checkId = new ObjectId(req.params.dtree_id);
        //存储到mongoDB前的预处理
        //..
        Dtree.findById(checkId).lean().exec(function (err, dtree) {
            if(err){
                console.log(‘Error: readDTree: DB failed to findById due to ‘, err);
                res.send({‘success‘:false, ‘err‘: err});
            }else{
                console.log(‘Info: readDTree: DB findById successfully dtree = ‘, dtree);
                //发送到客户端的预处理
                //...
                res.send({‘success‘:true, dtree: dtree});
            }
        });
    };
    

    对于PUT操作,以前我写过v为versionKey,可以充当数据库文档的版本控制flag,但mongoose的.update()方法有些许bug,执行操作后并没有修改v的值,暂时的解决方法是通过$inc: {key: value}来手动设置。

    //execute route PUT /dtree/:dtree_id
    exports.updateDTree = function (req, res) {
        //存储到mongoDB前的预处理
        //..
        Dtree.update({"_id": id},  {
             modifiedKey: modifiedData,
             $inc: {__v: 1}
        }, function(err){
            if(err){
                console.log(‘Error: updateDTree: DB failed to update due to ‘, err);
                res.send({‘success‘:false, ‘err‘:err});
            }else{
                console.log(‘Info: updateDTree: DB updated successully dtree‘);
                res.send({‘success‘:true});
            }
        });
    };
    

    DELETE操作相对就比较简单了。

    //execute route DELETE /dtree/:dtree_id
    exports.deleteDTree = function(req, res){
        Dtree.findByIdAndRemove(req.params.dtree_id, function(err, dropDtree){
            if(err){
                console.log(‘Error: deleteDTree: DB failed to delete due to ‘, err);
                res.send({‘success‘: false, ‘err‘: err});
            }else{
                console.log(‘Info: deleteDTree: DB deleted successfully dtree = ‘, dropDtree);
                res.send({‘success‘: true});
            }
        });
    }
    

    至此,后端已经建立以一套RESTful的API提供给客户端HTTP请求调用。我们首先需要使用前面提到的$resource服务定义决策树CRUD操作的服务dtreeCrudService,这里URI使用的是cors的写法,其实没有必要,单独使用/dtree/:dtree_id也可,这就是访问本机的资源。CORS(Cross Origin Resource Sharing)是与SOP(Same Origin Policy,指的是在客户端上只能访问服务器自身域的文档或脚本,不能获取或修改另一个域的文档的属性)相对,它支持跨域请求。NodeJS通过cors依赖包的调用开启CORS。如果浏览器端检测到相应的设置,就可以允许XHR进行跨域的访问。

    $resource(‘http://localhost\\:4000/dtree/:dtree_id‘, {},{
            ‘get‘: {
                method:‘GET‘
            },
            update: {
                method: ‘PUT‘ //a PUT request
            },
            ‘delete‘: {
                method:‘DELETE‘
            }
    });
    

    这里定义了HTTP操作的方法,在controller中正确"注入"服务即可调用服务的方法。

    app.controller(‘createDTreeCtrl‘, [
        ‘$scope‘,
        ‘dtreeCrudService‘
        function (
            $scope,
            dtreeCrudService
        ) {
        //...具体实现
    ]);
    

    这里通过定义CRUD的方法来实现业务逻辑的划分。

    //将决策树数据传到后端存入数据库
    $scope.createDtreeData = function(){
        //前端处理后数据
        var data = someOperate(data);
        console.log(‘Info: createDtreeData: data = ‘, data);
        dtreeCrudService.save(data, function(res){
            if(res.success){
                console.log(‘Info: createDtreeData: Back-end successfully saved dtree = ‘, data);
            }else{
                console.log(‘Error: createDtreeData: Back-end failed to save dtree due to ‘, res.err);
            }
        }, function(error){
            console.log("Error: createDtreeData: Fail to create due to ", error);
        });
    }
    //读取数据
    $scope.readDtreeData = function (){
        dtreeCrudService.get({dtree_id: ‘54a4c0947752adcc1764f0d4‘}, function(res) {
            if(res.success){
                console.log("Info: readDtreeData: Back-end successfully read dtree = ", res.dtree);
            }else{
                console.log(‘Error: readDtreeData: Back-end failed to read dtree due to ‘, res.err);
            }
        }, function(error){
            console.log("Error: readDtreeData: Fail to read due to ", error);
        });
    }
    //更新数据
        $scope.updateDtreeData = function () {
            //数据规整成json对象。
            var data = someChange(data);
            console.log(‘Info: updateDtreeData: data = ‘, data);
            dtreeCrudService.update({dtree_id: data._id},data, function(res){
                if(res.success){
                    console.log("Info: updateDtreeData: Back-end successfully update dtree = ", data);
                }else{
                    console.log(‘Error: updateDtreeData: Back-end failed to read dtree due to ‘, res.err);
                }
            }, function(error){
                console.log("Error: updateDtreeData: Fail to update due to ", error);
            });
        }
    //删除某个ID的决策树
    $scope.deleteDtreeData = function () {
        dtreeCrudService.delete({dtree_id: $scope.operate_dtreeId}, function(res){
            if(res.success){
                console.log("Info: deleteDtreeData: Back-end successfully delete dtree");
            }else{
                console.log(‘Error: updateDtreeData: Back-end failed to read dtree due to ‘, res.err);
            }
        }, function(error){
            console.log("Error: deleteDtreeData: Fail to delete due to ", error);
        });
    }
    

    这4个方法对应的请求如图,使用ng-click指令即可调用这些方法。

    ngResource的CRUD方法的使用就是这么的方便,以后修改也特别方便。

    $resource(url, [paramDefaults], [actions], options);

    再提及一下这个$resource方法中的pramas参数的作用,当这个参数不为空时就会在运行HTTP请求方法被覆盖掉。举个例子对于 $resource("/dtree/:id", {id: @dtreeid}, methods) 这一段资源服务,我们POST的数据为{"dtreeid": 2333, "dtreeData": YoYo }时,这个URL就会成为 /dtree/2333。而当没有这个@符号时,URL就是直接为的 /dtree/dtree_id。

    还有一点就是我们前后端传输的数据采用的是JSON格式,而JSON是不支持循环结构,即 a.b = c; c.d = b; 这种数据结构,但在前端使用D3画图过程中,有所需要保持这种结构才能实时更新界面上的图片。若强行使用$resource传递,Javascript会先将数据转换为JSON对象,然后就报TypeError的错误。

    这时我们不用慌张,直接遍历决策树数据将循环结构干掉就可以了。


总结

这次功能模块的实现分为前后端,后端的Node提供RESTful的资源获取API,AngularJS在前端使用$resource进行HTT请求的封装来获取资源。这一套HTTP + CRUD Method + URL只是REST部分概念的实现,REST的真正价值在于低耦合的设计理念。

References:

REST相关

  1. 浅谈REST
  2. 基于REST的 Web 服务基础
  3. HTTP幂等性概念和应用

MEAN

  1. simple-device-management-app
  2. tv-traker
  3. update操作无法增加"__v"
  4. 貌似需FQ的$resource文档
时间: 2024-11-10 00:26:01

MEAN Stack:创建RESTful web service的相关文章

使用Java创建RESTful Web Service(转)

REST是REpresentational State Transfer的缩写(一般中文翻译为表述性状态转移).2000年Roy Fielding博士在他的博士论文“Architectural Styles and the Design of Network-based Software Architectures”<体系结构与基于网络的软件架构设计>中提出了REST. REST是一种体系结构.而HTTP是一种包含了REST架构属性的协议. REST基础概念 在REST中所有东西都被看作资源.

使用JAX-RS创建RESTful Web Service

http://blog.csdn.net/withiter/article/details/7349795 本章介绍REST架构.RESTful web service和JAX-RS(Java API for RESTful Web Service,JSR 311).JAX-RS的参考实现Jersey实现了对JSR 311中定义的注解的支持,使得使用Java编程语言开发RESTful web service变得简单.如果是使用GalssFish服务器,可以使用Update Tool安装Jerse

用spring boot快速创建 Restful Web Service

新建项目目录:hello 项目结构目录:mkdir src\main\java\hello 创建Gradle项目构建文件: build.gradle buildscript {     repositories {         mavenCentral()     }     dependencies {        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.4.3.RELEASE")     

如何封装RESTful Web Service

所谓Web Service是一个平台独立的,低耦合的,自包含的.可编程的Web应用程序,有了Web Service异构系统之间就可以通过XML或JSON来交换数据,这样就可以用于开发分布式的互操作的应用程序.Web Service使得运行在不同机器上的不同应用无须借助附加的.专门的第三方软件或硬件就可相互交换数据或集成,无论它们各自所使用的语言.平台或内部协议是什么,都可以相互交换数据.Web Service为整个企业甚至多个组织之间的业务流程的集成提供了一个通用机制. ??REST(REpre

使用 Spring 3 来创建 RESTful Web Services(转)

使用 Spring 3 来创建 RESTful Web Services 在 Java™ 中,您可以使用以下几种方法来创建 RESTful Web Service:使用 JSR 311(311)及其参考实现 Jersey.使用 Restlet 框架和从头开始开发.Spring 是流行的 Java EE 应用开发框架,现在它的 MVC 层也支持 REST 了.本文将介绍使用 Spring 开发 RESTful Web Services 的方法.读者将了解如何使用 Spring API 和注释来开发

怎样封装RESTful Web Service

所谓Web Service是一个平台独立的,低耦合的.自包括的.可编程的Web应用程序.有了Web Service异构系统之间就能够通过XML或JSON来交换数据,这样就能够用于开发分布式的互操作的应用程序. Web Service使得执行在不同机器上的不同应用无须借助附加的.专门的第三方软件或硬件就可相互交换数据或集成.不管它们各自所使用的语言.平台或内部协议是什么,都能够相互交换数据.Web Service为整个企业甚至多个组织之间的业务流程的集成提供了一个通用机制. ??REST(REpr

RESTful Web Service

1. 何为REST 理解RESTful架构 2. 如何创建RESTful Web Service 使用 Spring 3 来创建 RESTful Web Services 3. RESTful Web Serivce 和 SOAP Web Service 对比 Web 服务编程,REST 与 SOAP SOAP webserivce 和 RESTful webservice 对比及区别

使用 Spring 3 来创建 RESTful Web Services

来源于:https://www.ibm.com/developerworks/cn/web/wa-spring3webserv/ 在 Java? 中,您可以使用以下几种方法来创建 RESTful Web Service:使用 JSR 311(311)及其参考实现 Jersey.使用 Restlet 框架和从头开始开发.Spring 是流行的 Java EE 应用开发框架,现在它的 MVC 层也支持 REST 了.本文将介绍使用 Spring 开发 RESTful Web Services 的方法

Spring 3 来创建 RESTful Web Services

Spring 3 创建 RESTful Web Services 在 Java™ 中,您可以使用以下几种方法来创建 RESTful Web Service:使用 JSR 311(311)及其参考实现 Jersey.使用 Restlet 框架和从头开始开发.Spring 是流行的 Java EE 应用开发框架,现在它的 MVC 层也支持 REST 了.本文将介绍使用 Spring 开发 RESTful Web Services 的方法.读者将了解如何使用 Spring API 和注释来开发 RES