分布式架构设计之Rest API

xiaoxiao2021-02-28  99

分布式架构设计之RestAPI

 

   近几年,以资源为中心的表述性状态转移(Representational StateTransfer,REST)越来越受欢迎,它完美地替代了传统的基于SOAP的Web服务方案,同时它关注的是数据的处理,而后者则关注于动作行为的处理。对于REST,常有人错误的将其视为“基于URL的Web服务”,也就将REST认为是另一种类型的远程调用(Remote Procedure Call,RPC)机制。实际上,REST与RPC几乎并没有任何关系,RPC是面向服务的,关注于行为和动作;而REST是面向资源的,关注在数据的描述、状态。当然,REST中会有行为,它们是通过HTTP方法定义,也就是GET、POST、PUT、DELETE及PATCH等构成了REST的动作,它们是用来操作更新资源信息的工具。

 

 

一、什么是REST

正如上图,标准的REST是有四部分构成:资源、表述、状态转移及统一接口,它们的关系上图描述的也很清楚,REST是以资源为中心,前后端通信时,需要对通信的资源进行匹配两端合适的表述,同样,通信过程中,操作的资源的状态会发生改变转移,而上面的一切需要实际的操作接口来完成,REST支持统一的HTTP协议接口,具体各部分描述请往下细看。

 

1、资源中心

资源(Resource),是一个抽象概念,它可以是一段文字、一张图片、一首歌曲,也可以是数据库的一张表等。而每个资源都会有一个与之对应的URI作为识别标志,对某个资源感兴趣的一端应用,则可以通过这个URI与之进行交互,以达到需求。

 

2、如何表述

表述(Representation),更严格的说应该是资源的表述,它是对资源在某个特定时刻的具体描述,也是前后端通信时进行信息交换的实体,它可以有多种形式的表述。比如:文本可以是TXT,也可以是HTML、XML及JSON形式;图片可以是JPG、BMP及PNG等。而这些资源的表述格式需要在前后端通信时进行协商确定,通过请求-响应来实现。

 

3、状态转移

状态转移(State Transfer),指的是前后端通信过程中,客户端能够通过资源的描述,实现操作资源的目的。比如:我们在浏览器中打开一网址时,就代表了前后端产生了一个交互过程,而在这个过程中,势必会涉及到数据状态的变化。

 

4、统一接口

统一资源接口(Uniform Interface),是客户端操作资源数据的方式,通常是基于HTTP方式,它主要提供四种操作:GET、POST、PUT及DELETE。它们分别对应的操作方式如下:

 

GET:用来获取资源;

POST:用来新建资源;

PUT:用来更新资源;

DELETE:用来删除资源;

 

所以,不论客户端通过HTTP的哪种资源操作,不管操作的URI是什么,资源有什么不同,但操作资源的接口是统一的。

 

二、实例的验证

在这里,我会结合实际的例子来演示Rest API的实现方法,这里我以Spring REST为例讲解,以实现书籍的CRUD操作示例。

 

首先,我们需要一个控制器,并且使用了@RestController标签,它提供了很多功能,省去了传统需要@ResponseBody标签来指定返回给前端的数据格式,如下所示:

@RestController

@RequestMapping("/rest")

public class RestApiAction extends BaseAction {

    // …

}

 

其次,我们最好定义一个BaseAction,把控制器请求的异常处理,以及返回请求头的代码封装在这里,具体原因大家应该明白,所以这里不赘述了,如下所示:

// 控制器异常处理

@ExceptionHandler(DataNotFoundException.class)

@ResponseStatus(HttpStatus.NOT_FOUND)

public ApiError dataNotFound(DataNotFoundException e) {

    long id = e.getId();

    if(0 != id) {

       return new ApiError(e.getCode(),"Data'Id ["+id+"] Not Found!");

    } else {

       return new ApiError(e.getCode(),"Data Is Not Found!");

    }

}

// 返回请求头信息

public HttpHeaders genHeaders(UriComponentsBuilder ucb,long id,HttpServletRequest request) {

    StringrootPath = request.getServletPath().split("/")[1];

    HttpHeadersheaders = new HttpHeaders();

    URI localUri = ucb

              .path("/"+rootPath+"/")

              .path(String.valueOf(id)).build().toUri();

    headers.setLocation(localUri);

    return headers;

}

 

说明下,控制器异常处理,主要是当前端请求接口时,后端接口返回数据为空时,直接返回NOT_FOUND异常数据给前端。而返回请求头信息,则主要是处理我们在创建一新资源并返回该资源时,一般建议在请求头HttpHeaders中将资源的URI同时返回,供前端灵活使用。

 

好了,下面就开始具体的业务操作了,具体如下:

 

1、检索所有书籍

后端:

// 检索所有书本

@RequestMapping(value="/IReadBooks",method=RequestMethod.GET,consumes="application/json")

    public List<Book> readBooks() throws Exception {

       List<Book>result = bookService.readBooks();

       if(null == result || result.size() == 0) {

           throw newDataNotFoundException();

       }

       return result;

}

 

前端:

// 检索所有书籍

         function readBooks(){

            $.ajax({

                   url:'IReadBooks',

                   data:null,

                   type:"get",

                   dataType:'json',

                   contentType:'application/json',

                   success:function(result){

                      var result = JSON.stringify(result);

                      $("#result").html(result);

                   }

            });

         }

 

 

结果:

 

说明:

接口为IReadBooks,这里并没有参数,请求协议方式为GET方式,请求内容格式为application/json,并且要求服务接口返回数据格式为json格式,返回的result结果通过JSON.stringify()转换为json格式,因为这样才能直接$("#result").html(result);

并显示下面的结果截图。另外,下面的说明部分与此类似,不再进行赘述说明。

 

 

2、检索指定书籍

后端:

// 根据书号检索一本书

@RequestMapping(value="/IReadBook/{id}",method=RequestMethod.GET,consumes="application/json")

    public Book readBook(@PathVariable long id) throws Exception {

       Bookresult = bookService.readBook(id);

       if(null == result) {

           throw newDataNotFoundException(id,10001);

       }

       return result;

    }

 

这里使用了@PathVariable注解,来接收前端传递的书本号,并将其添加到资源URI中,实现接口查询功能。

 

前端:

// 根据书号检索一本书

         function readBook(){

            $.ajax({

                   url:'IReadBook/1',

                   data:null,

                   type:"get",

                   dataType:'json',

                   contentType:'application/json',

                   success:function(result){

                      var result = JSON.stringify(result);

                      $("#result").html(result);

                   }

            });

         }

 

 

结果:

 

 

3、上架一本书籍

后端:

// 上架一本书

@RequestMapping(value="/ICreateBook",method=RequestMethod.POST,produces="application/json")

    public ResponseEntity<Book> createBook(@RequestBody Book book,UriComponentsBuilder ucb,HttpServletRequest request) throws Exception {

       Bookresult = bookService.createBook(book);

       if(null == result) {

           throw newDataNotFoundException();

       }

      

       return newResponseEntity<Book>(book,genHeaders(ucb,book.getId(),request),HttpStatus.CREATED);

    }

 

这里使用了@RequestBody注解,它的作用就是将前端传递过来的json内容通过消息转换器转变为对应的POJO实体,并调用DAO插入到数据库表中。如果刚插入的书本失败了,则返回的Book结果就为空,那么直接返回NOT_FOUND信息数据给前端处理。

 

前端:

// 上架一本书

           function createBook(){

              var jsonStr = "{\"name\":\"Python进阶实战教材》\",\"tag\":\"编程语言\",\"price\":68.59}";

              $.ajax({

                      url:'ICreateBook',

                      data:jsonStr,

                      type:"post",

                      dataType:'json',

                      contentType:'application/json',

                      success:function(result){

                         var result = JSON.stringify(result);

                         $("#result").html(result);

                      }

              });

           }

 

结果:

 

 

4、更新一本书籍

后端:

// 更新一本书

@RequestMapping(value="/IUpdateBook",method=RequestMethod.PUT,consumes="application/json")

    public Book updateBook(@RequestBody Book book) throws Exception {

       Bookresult = bookService.updateBook(book);

       if(null == result) {

           throw newDataNotFoundException();

       }

       return result;

    }

 

 

前端:

// 更新一本书

           function updateBook(){

              var jsonStr = "{\"id\":11,\"name\":\"Web进阶实战教材》\",\"tag\":\"编程语言\",\"price\":68.59}";

              $.ajax({

                      url:'IUpdateBook',

                      data:jsonStr,

                      type:"put",

                      dataType:'json',

                      contentType:'application/json',

                      success:function(result){

                         var result = JSON.stringify(result);

                         $("#result").html(result);

                      }

              });

           }

 

结果:

 

 

5、下架一本书籍

后端:

// 下架一本书

    @RequestMapping(value="/IDeleteBook",method=RequestMethod.DELETE,produces="application/json")

    public Book deleteBook(@RequestBody Book book) throws Exception {

       Bookresult = bookService.deleteBook(book.getId());

       if(null == result) {

           throw newDataNotFoundException();

       }

       return result;

    }

 

前端:

// 下架一本书

           function deleteBook(){

              var jsonStr = "{\"id\":11,\"name\":\"WEB进阶实战教材》\",\"tag\":\"编程语言\",\"price\":68.59";

              $.ajax({

                      url:'IDeleteBook',

                      data:jsonStr,

                      type:"delete",

                      dataType:'json',

                      contentType:'application/json',

                      success:function(result){

                         var result = JSON.stringify(result);

                         $("#result").html(result);

                      }

              });

           }

 

结果:

 

 

三、REST优与劣

1、优势

借助于资源表述、状态转移及统一接口,REST能够将客户端的请求、服务端的响应基于资源形式联系在一起,形成了一种以资源为中心,以HTTP为操作方式的,语言无关、平台无关的通信方式。另外,因为HTTP本身无状态性,能够有效保持服务或应用的无状态性,利于水平拓展。

 

2、不足

随着业务不断增长,服务端响应的内容逐渐复杂,对于如何使资源标准结构化,以及如何处理资源的相关连接等问题,应该引起注意。当然,在后续的文章会继续介绍如何来弥补这些不足。

 

 

 

好了,由于作者水平有限,如有不正确或是误导的地方,请不吝指出讨论(技术交流群:497552060(新))

 

 

转载请注明原文地址: https://www.6miu.com/read-23912.html

最新回复(0)