单页应用程序和 RESTful API

Single Page Application and RESTful API

真正的 RESTful API 利用超媒体,以便客户端仅依赖服务器提供的动态超媒体来浏览应用程序(称为 HATEOAS 的概念)

这个概念很容易适用于 Web 应用程序,但是您如何将它应用到单页应用程序,因为 SPA 通常在内部管理它们的状态(就导航而言不依赖服务器)?

我的感觉是 SPA 无法充分利用 RESTful API 还是我错过了什么?

谢谢

莉安娜

SPA 的特殊性在于,它通过 UI 在客户端构建而不是在服务器上构建并仅在客户端呈现,从而提供更流畅的用户体验。

SPA 不一定需要保持自己的状态,服务器仍然可以驱动与 HATEOAS 的交互。对于超媒体驱动的 SPA 或以其他方式维护其自身状态并访问预定义资源,完全取决于应用程序。

组合也可以工作,例如 SPA 为某些部分维护自己的状态,而其他部分由服务器驱动。从这个意义上讲,考虑例如分页结果。 SPA 可能会转到特定的 URL(具有该资源的先验知识)以获取结果列表,并对这些结果进行分页。服务器在结果中嵌入链接,以便客户端可以将它们导航到下一页和上一页(您通过服务器提供的超媒体与应用程序交互)。

My feeling is that SPAs cannot fully leverage RESTful APIs or did I miss something ?

正如我上面所说,这取决于您的应用程序。如果应用程序由超媒体驱动有意义,那么它就可以这样构建。另一方面,如果拥有 SPA "drive itself" 更有意义,那么强制使用 HATEOS 可能不是一个好主意。

单页应用程序 (SPA) 可以充分利用 RESTful 启用 HATEOAS 的 API,示例 SPA(angularJS 与 ui-rauter 用于状态转换)

For computer to computer interaction we advertise protocols information by embedding links in representation much as we do in human web.

在消费者服务交互中:-

  1. 消费者向入口点提交初始请求 服务。
  2. 该服务处理请求并使用填充有链接的资源表示进行响应。
  3. 消费者选择其中一个链接过渡到下一步 在互动中。
  4. 以上几个这样的互动导致消费者进步 朝着它的目标前进。

带有示例代码的插图 服务入口点

var applicationServices = angular.module('applicationServices', ['ngResource']);
        userDetailServices.factory('DetailService', ['$resource',function($resource){
            return $resource('api/users', {},{
                        query : {
                            method : 'GET',
                            headers : {'Accept': 'application/json'},
                            isArray: true
                      }
              });
        }]);

Hypermedia is all about loose coupling, when developing service we abstract away details from consumers there by decreasing coupling, but not matter the degree of of loose coupling consumers must have enough information available in order to interact with our services

假设 api/users 是服务的入口点,并且是您的 SPA 唯一知道的 url,它将响应一个资源表示,其中包含用于进一步交互的链接。

{
    "links": [
        {
            "rel": "self",
            "href": "http://localhost:8080/persons{?page,size,sort}"
        }
    ],

    "users": [
        {
            "id": "3415NE11",
            "firstName": "somefirstname",
            "lastName": "lastname",
            "emailAddress": "someemail",
            "links": [
                {
                    "rel": "section1",
                    "href": "http://localhost:8080/persons/3415NE11/section1
                },
                {
                    "rel": "section2",
                    "href": "http://localhost:8080/persons/3415NE11/section2
                },
                {
                    "rel": "gallery,
                    "href": "http://localhost:8080/filesRepo/profile/3415NE11/images
                },
            ]
        }
    ],
    "page": {
        "size": 20,
        "totalElements": 2,
        "totalPages": 1,
        "number": 0
    }
}

您的 SPA 从有关资源的部分信息开始,它将按需提供额外的资源信息

  • 具有部分用户信息的用户列表(启动时知道url 之前)
  • 用户资源信息Section1(点播,从1中查找url 以上)
  • Section2用户资源信息(点播,从1中查找url 以上)
  • 第n段用户资源信息(点播,从1中查找url 以上)

带ui-路由器导航

angular.module('userApp', [
        'ui.bootstrap',
        'ui.router',
        'userControllers',
        'userServices'
     ])
     .config(
            [ '$stateProvider', '$urlRouterProvider', '$httpProvider', function($stateProvider,$urlRouterProvider, $httpProvider) {

                $stateProvider
                      .state('users', {
                        url: '/users-index',
                        templateUrl: 'partials/users-index.html',
                        resolve:{ // Your SPA needs this on start-up
                               DetailService:function(DetailService){
                                  return DetailService.query();
                           }
                        },
                        controller:'UserController',
                      })
                      .state('users.section1', {
                        url: '/user-section1',
                        templateUrl: 'partials/user-section1.html',

                      })
                      .state('users.section2', {
                        url: '/users-section2',
                        templateUrl: 'partials/users.section2.html'
                      })
                       .state('users.gallery', {
                        url: '/users-gallery,
                        templateUrl: 'partials/users-gallery.html'
                      });

                 $urlRouterProvider.otherwise('/');
  }])
  .run([
        '$rootScope', 
        '$location',
        '$state', 
        '$stateParams'
        function($rootScope, $location,$state, $stateParams) {
          $rootScope.$state = $state;
          $rootScope.$stateParams = $stateParams;
        }
        ]);

您的 angularJS SPA 的用户控制器

(function() {
    var userControllersApp = angular.module('userControllers', ['ngGeolocation']);
      userControllersApp.controller('UserController',
              ['$scope',
               '$rootScope',
               '$http',
               '$state',
               '$filter',
               'DetailService',
               function($scope,$rootScope,$http,$state,$filter,DetailService) {

             DetailService.$promise.then(function(result){
                 $scope.users = result.users;
             });


        $scope.userSection1= function(index){

           var somelink = $filter('filter')($scope.users[index].links, { rel: "section1" })[0].href;
          $http.get(somelink).success(function(data){
             $state.go("users.section1");
          });
         // $http.post(somelink, data).success(successCallback);

        };   

        $scope.userSection2= function(index){

           var somelink = $filter('filter')($scope.users[index].links, { rel: "section2" })[0].href;
         $http.get(somelink).success(function(data){
             $state.go("users.section2");
          });
         // $http.post(somelink, data).success(successCallback);

        };

        $scope.userSection3= function(index){
           var somelink = $filter('filter')($scope.users[index].links, { rel: "gallery" })[0].href;
          $http.get(somelink).success(function(data){
             $state.go("users.gallery");
          });
         // $http.post(somelink, data).success(successCallback);

        };

    } ]);

})();

The beauty of of hypermedia is that it allows us to convey protocol information in a declarative and just in-tame fashion as part of the application's resource representation

使用 $scope.users 嵌入式链接进一步 interactions.look 如何在 section1()section2()section(3) 函数中解析 somelink

您的 Angular SPA 导航(用户-index.html)可能是

 <div  ng-repeat="user in users">
   <label>{{user.firstname}}</label>
    <button type="button" class="btn  btn-xs "  ng-click="section1($index)">Section1</button>
<button type="button" class="btn  btn-xs "  ng-click="section2($index)">Section2</button>
<button type="button" class="btn  btn-xs "  ng-click="section3($index)">Section3</button>
    </div>

现在您的 SPA 的状态转换反过来依赖于服务器提供的动态链接来浏览应用程序,这表明 SPA 可以充分利用支持 HATEOAS 的 RESTful API。 my apologies for the very long explanation ,hope it helps.