您现在访问的是微软AZURE全球版技术文档网站,若需要访问由世纪互联运营的MICROSOFT AZURE中国区技术文档网站,请访问 https://docs.azure.cn.

教程:在 Service Fabric 上创建包含 Java Web API 前端服务和有状态后端服务的应用程序Tutorial: Create an application with a Java web API front-end service and a stateful back-end service on Service Fabric

本教程是一个系列中的第一部分。This tutorial is part one of a series. 完成后,将生成一个带 Java Web 前端的 Voting 应用程序,用于将投票结果保存到群集的有状态后端服务中。When you are finished, you have a Voting application with a Java web front end that saves voting results in a stateful back-end service in the cluster. 本教程系列要求你有一台工作的 Mac OSX 或 Linux 开发人员计算机。This tutorial series requires that you have a working Mac OSX or Linux developer machine. 如果不想手动创建投票应用程序,可以下载已完成应用程序的源代码,跳到大致了解投票示例应用程序If you don't want to manually create the voting application, you can download the source code for the completed application and skip ahead to Walk through the voting sample application. 此外,请考虑以下 Java 可靠服务快速入门。Also, consider following the Quickstart for Java reliable services.

本地 Voting 应用

在此系列教程中,你会学习如何:In this tutorial series you learn how to:

在该系列的第一部分中,你会学习如何:In part one of the series, you learn how to:

  • 创建 Java 有状态 Reliable ServicesCreate a Java stateful reliable service
  • 创建 Java 无状态 Web 应用程序服务Create a Java stateless web application service
  • 使用服务远程处理功能与有状态服务通信Use service remoting to communicate with the stateful service
  • 将应用程序部署到本地 Service Fabric 群集Deploy application on a local Service Fabric cluster

先决条件Prerequisites

在开始学习本教程之前:Before you begin this tutorial:

  • 如果还没有 Azure 订阅,可以创建一个免费帐户If you don't have an Azure subscription, create a free account.
  • 设置适用于 MacLinux 的开发环境。Set up your development environment for Mac or Linux. 遵照说明安装 Eclipse 插件、Gradle、Service Fabric SDK 和 Service Fabric CLI (sfctl)。Follow the instructions to install the Eclipse plug-in, Gradle, the Service Fabric SDK, and the Service Fabric CLI (sfctl).

创建前端 Java 无状态服务Create the front-end Java stateless service

首先,请创建 Voting 应用程序的 Web 前端。First, create the web front end of the Voting application. 受 AngularJS 支持的 Web UI 会向运行轻型 HTTP 服务器的 Java 无状态服务发送请求。A web UI powered by AngularJS sends requests to the Java stateless service, which runs a lightweight HTTP server. 此服务会处理每个请求,并向有状态服务发送远程过程调用以存储投票。This service processes each request and sends a remote procedure call to the stateful service to store the votes.

  1. 启动 Eclipse。Launch Eclipse.

  2. 通过单击“文件”->“新建”->“其他”->“Service Fabric”->“Service Fabric 项目”来创建项目。Create a project with File->New->Other->Service Fabric->Service Fabric Project.

    Eclipse 中的“新建项目”对话框

  3. 在“ServiceFabric 项目向导”对话框中,将项目命名为 Voting,然后单击“下一步”。In the ServiceFabric Project Wizard dialog, name the Project Voting and press Next.

    在新建服务对话框中选择 Java 无状态服务

  4. 在“添加服务”页中,选择“无状态服务”,然后将服务命名为“VotingWeb”。On the Add Service page, choose Stateless Service, and name your service VotingWeb. 单击“完成”以创建该项目。Click Finish to create the project.

    创建无状态服务

    Eclipse 会创建应用程序和服务项目,并在包资源管理器中显示它们。Eclipse creates an application and a service project and displays them in Package Explorer.

    创建应用程序之后的 Eclipse 包资源管理器

下表对上一屏幕截图中包资源管理器的每个项目进行了简短的说明。The table gives a short description of each item in the package explorer from the previous screenshot.

包资源管理器项目Package Explorer Item 说明Description
PublishProfilesPublishProfiles 包含的 JSON 文件介绍了本地和 Azure Service Fabric 群集的配置文件详细信息。Contains JSON files describing profile details of local and Azure Service Fabric clusters. 部署此应用程序时,这些文件的内容供插件使用。The contents of these files is used by the plugin when deploying the application.
脚本Scripts 包含帮助程序脚本。可以通过命令行使用这些脚本,以便通过群集快速管理应用程序。Contains helper scripts that can be used from the command line to quickly manage your application with a cluster.
VotingApplicationVotingApplication 包含的 Service Fabric 应用程序可以推送到 Service Fabric 群集。Contains the Service Fabric application that is pushed to the Service Fabric cluster.
VotingWebVotingWeb 包含前端无状态服务源文件,以及相关的 gradle 生成文件。Contains the front-end stateless service source files along with the related gradle build file.
build.gradlebuild.gradle 用于管理项目的 Gradle 文件。Gradle file used to manage the project.
settings.gradlesettings.gradle 包含此文件夹中 Gradle 项目的名称。Contains names of Gradle projects in this folder.

将 HTML 和 Javascript 添加到 VotingWeb 服务Add HTML and Javascript to the VotingWeb service

若要添加无状态服务可以呈现的 UI,请添加 HTML 文件。To add a UI that can be rendered by the stateless service, add an HTML file. 然后,通过已嵌入到无状态 Java 服务中的轻型 HTTP 服务器呈现此 HTML 文件。This HTML file is then rendered by the lightweight HTTP server embedded into the stateless Java service.

  1. 展开 VotingApplication 目录,以便访问 VotingApplication/VotingWebPkg/Code 目录。Expand the VotingApplication directory to reach the VotingApplication/VotingWebPkg/Code directory.

  2. 右键单击 Code 目录,然后单击“新建”->“文件夹”。Right-click on the Code directory and click New->Folder.

  3. 将文件夹命名为 wwwroot 并单击“完成”。Name the folder wwwroot and click Finish.

    Eclipse 创建 wwwroot 文件夹

  4. 将名为 index.html 的文件添加到 wwwroot,然后将以下内容粘贴到该文件夹中。Add a file to the wwwroot called index.html and paste the following contents into this file.

<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.13.4/ui-bootstrap-tpls.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<body>

<script>
var app = angular.module('VotingApp', ['ui.bootstrap']);
app.controller("VotingAppController", ['$rootScope', '$scope', '$http', '$timeout', function ($rootScope, $scope, $http, $timeout) {
    $scope.votes = [];
    
    $scope.refresh = function () {
        $http.get('getStatelessList')
            .then(function successCallback(response) {
        $scope.votes = Object.assign(
            {},
            ...Object.keys(response.data) .
            map(key => ({[decodeURI(key)]: response.data[key]}))
        )
        },
        function errorCallback(response) {
            alert(response);
        });
    };

    $scope.remove = function (item) {
       $http.get("removeItem", {params: { item: encodeURI(item) }})
            .then(function successCallback(response) {
                $scope.refresh();
            },
            function errorCallback(response) {
                alert(response);
            });
    };

    $scope.add = function (item) {
        if (!item) {return;}
        $http.get("addItem", {params: { item: encodeURI(item) }})
            .then(function successCallback(response) {
                $scope.refresh();
            },
            function errorCallback(response) {
                alert(response);
            });
    };
}]);
</script>

<div ng-app="VotingApp" ng-controller="VotingAppController" ng-init="refresh()">
    <div class="container-fluid">
        <div class="row">
            <div class="col-xs-8 col-xs-offset-2 text-center">
                <h2>Service Fabric Voting Sample</h2>
            </div>
        </div>

        <div class="row">
            <div class="col-xs-offset-2">
                <form style="width:50% ! important;" class="center-block">
                    <div class="col-xs-6 form-group">
                        <input id="txtAdd" type="text" class="form-control" placeholder="Add voting option" ng-model="item" />
                    </div>
                    <button id="btnAdd" class="btn btn-default" ng-click="add(item)">
                        <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
                        Add
                    </button>
                </form>
            </div>
        </div>

        <hr />

        <div class="row">
            <div class="col-xs-8 col-xs-offset-2">
                <div class="row">
                    <div class="col-xs-4">
                        Click to vote
                    </div>
                </div>
                <div class="row top-buffer" ng-repeat="(key, value)  in votes">
                    <div class="col-xs-8">
                        <button class="btn btn-success text-left btn-block" ng-click="add(key)">
                            <span class="pull-left">
                                {{key}}
                            </span>
                            <span class="badge pull-right">
                                {{value}} Votes
                            </span>
                        </button>
                    </div>
                    <div class="col-xs-4">
                        <button class="btn btn-danger pull-right btn-block" ng-click="remove(key)">
                            <span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
                            Remove
                        </button>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

</body>
</html>

更新 VotingWeb.java 文件Update the VotingWeb.java file

VotingWeb 子项目中,打开 VotingWeb/src/statelessservice/VotingWeb.java 文件。In the VotingWeb subproject, open the VotingWeb/src/statelessservice/VotingWeb.java file. VotingWeb 服务是通往无状态服务的网关,负责设置前端 API 的通信侦听器。The VotingWeb service is the gateway into the stateless service and is responsible for setting up the communication listener for the front-end API.

将文件中的现有 createServiceInstanceListeners 方法替换为以下内容,然后保存所做的更改。Replace the existing createServiceInstanceListeners method in the file with the following and save your changes.

@Override
protected List<ServiceInstanceListener> createServiceInstanceListeners() {

    EndpointResourceDescription endpoint = this.getServiceContext().getCodePackageActivationContext().getEndpoint(webEndpointName);
    int port = endpoint.getPort();

    List<ServiceInstanceListener> listeners = new ArrayList<ServiceInstanceListener>();
    listeners.add(new ServiceInstanceListener((context) -> new HttpCommunicationListener(context, port)));
    return listeners;
}

添加 HTTPCommunicationListener.java 文件Add the HTTPCommunicationListener.java file

HTTP 通信侦听器充当一个控制器,可设置 HTTP 服务器并公开用于定义投票操作的 API。The HTTP communication listener acts as a controller that sets up the HTTP server and exposes the APIs defining voting actions. 右键单击 VotingWeb/src/statelessservice 文件夹中的 statelessservice 包,然后选择“新建”>“文件”。Right-click on the statelessservice package in the VotingWeb/src/statelessservice folder, then select New->File. 将文件命名为 HttpCommunicationListener.java,然后单击“完成”。Name the file HttpCommunicationListener.java and click Finish.

将文件内容替换为以下内容,然后保存所做更改。Replace the file contents with the following, then save your changes. 稍后在更新 HttpCommunicationListener.java 文件时会修改此文件,以便呈现、读取和写入来自后端服务的投票数据。Later, in Update the HttpCommunicationListener.java file, this file is modified to render, read, and write voting data from the back-end service. 目前,此侦听器会直接返回 Voting 应用的静态 HTML。For now, the listener simply returns the static HTML for the Voting app.

// ------------------------------------------------------------
//  Copyright (c) Microsoft Corporation.  All rights reserved.
//  Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------

package statelessservice;

import com.google.gson.Gson;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.Headers;

import java.io.File;
import java.io.OutputStream;
import java.io.FileInputStream;

import java.net.InetSocketAddress;
import java.net.URI;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
import java.util.logging.Logger;

import microsoft.servicefabric.services.communication.runtime.CommunicationListener;
import microsoft.servicefabric.services.runtime.StatelessServiceContext;
import microsoft.servicefabric.services.client.ServicePartitionKey;
import microsoft.servicefabric.services.remoting.client.ServiceProxyBase;
import microsoft.servicefabric.services.communication.client.TargetReplicaSelector;
import system.fabric.CancellationToken;

public class HttpCommunicationListener implements CommunicationListener {

    private static final Logger logger = Logger.getLogger(HttpCommunicationListener.class.getName());

    private static final String HEADER_CONTENT_TYPE = "Content-Type";
    private static final int STATUS_OK = 200;
    private static final int STATUS_NOT_FOUND = 404;
    private static final int STATUS_ERROR = 500;
    private static final String RESPONSE_NOT_FOUND = "404 (Not Found) \n";
    private static final String MIME = "text/html";
    private static final String ENCODING = "UTF-8";

    private static final String ROOT = "wwwroot/";
    private static final String FILE_NAME = "index.html";
    private StatelessServiceContext context;
    private com.sun.net.httpserver.HttpServer server;
    private ServicePartitionKey partitionKey;
    private final int port;

    public HttpCommunicationListener(StatelessServiceContext context, int port) {
        this.partitionKey = new ServicePartitionKey(0);
        this.context = context;
        this.port = port;
    }

    // Called by openAsync when the class is instantiated
    public void start() {
        try {
            logger.log(Level.INFO, "Starting Server");
            server = com.sun.net.httpserver.HttpServer.create(new InetSocketAddress(this.port), 0);
        } catch (Exception ex) {
            logger.log(Level.SEVERE, null, ex);
            throw new RuntimeException(ex);
        }

        // Responsible for rendering the HTML layout described in the previous step
        server.createContext("/", new HttpHandler() {
            @Override
            public void handle(HttpExchange t) {
                try {
                    File file = new File(ROOT + FILE_NAME).getCanonicalFile();

                    if (!file.isFile()) {
                      // Object does not exist or is not a file: reject with 404 error.
                      t.sendResponseHeaders(STATUS_NOT_FOUND, RESPONSE_NOT_FOUND.length());
                      OutputStream os = t.getResponseBody();
                      os.write(RESPONSE_NOT_FOUND.getBytes());
                      os.close();
                    } else {
                      Headers h = t.getResponseHeaders();
                      h.set(HEADER_CONTENT_TYPE, MIME);
                      t.sendResponseHeaders(STATUS_OK, 0);
    
                      OutputStream os = t.getResponseBody();
                      FileInputStream fs = new FileInputStream(file);
                      final byte[] buffer = new byte[0x10000];
                      int count = 0;
                      while ((count = fs.read(buffer)) >= 0) {
                        os.write(buffer,0,count);
                      }

                      fs.close();
                      os.close();
                    }
                } catch (Exception e) {
                    logger.log(Level.WARNING, null, e);
                }
            }
        });

        /*
        [Replace this entire comment block in the 'Connect the services' section]
        */

        server.setExecutor(null);
        server.start();
    }

    //Helper method to parse raw HTTP requests
    private Map<String, String> queryToMap(String query){
        Map<String, String> result = new HashMap<String, String>();
        for (String param : query.split("&")) {
            String pair[] = param.split("=");
            if (pair.length>1) {
                result.put(pair[0], pair[1]);
            }else{
                result.put(pair[0], "");
            }
        }
        return result;
    }

    //Called closeAsync when the service is shut down
    private void stop() {
        if (null != server)
            server.stop(0);
    }

    //Called by the Service Fabric runtime when this service is created on a node
    @Override
    public CompletableFuture<String> openAsync(CancellationToken cancellationToken) {
        this.start();
                    logger.log(Level.INFO, "Opened Server");
        String publishUri = String.format("http://%s:%d/", this.context.getNodeContext().getIpAddressOrFQDN(), port);
        return CompletableFuture.completedFuture(publishUri);
    }

    //Called by the Service Fabric runtime when the service is shut down
    @Override
    public CompletableFuture<?> closeAsync(CancellationToken cancellationToken) {
        this.stop();
        return CompletableFuture.completedFuture(true);
    }

    //Called by the Service Fabric runtime to forcibly shut this listener down
    @Override
    public void abort() {
        this.stop();
    }
}

配置侦听端口Configure the listening port

创建 VotingWeb 服务前端服务后,Service Fabric 会选择一个可供服务侦听的端口。When the VotingWeb service front-end service is created, Service Fabric selects a port for the service to listen on. VotingWeb 服务充当此应用程序的前端并接受外部流量,因此让我们将此服务绑定到已知的固定端口。The VotingWeb service acts as the front end for this application and accepts external traffic, so let's bind that service to a fixed and well-know port. 在包资源管理器中,打开 VotingApplication/VotingWebPkg/ServiceManifest.xmlIn Package Explorer, open VotingApplication/VotingWebPkg/ServiceManifest.xml. 在“资源”部分中找到“终结点”资源,然后将“端口”值更改为 8080(我们会继续在本教程中使用此端口)。Find the Endpoint resource in the Resources section and change the Port value to 8080 (we'll continue using this port throughout the tutorial). 若要在本地部署和运行应用程序,应用程序侦听端口必须为打开状态且在你的计算机上可用。To deploy and run the application locally, the application listening port must be open and available on your computer. 将以下代码片段粘贴到 ServiceManifest 元素中(放在 <DataPackage> 元素下方)。Paste the following code snippet within the ServiceManifest element (i.e. just below the <DataPackage> element).

<Resources>
    <Endpoints>
        <!-- This endpoint is used by the communication listener to obtain the port on which to
            listen. Please note that if your service is partitioned, this port is shared with
            replicas of different partitions that are placed in your code. -->
        <Endpoint Name="WebEndpoint" Protocol="http" Port="8080" />
    </Endpoints>
  </Resources>

向应用程序添加有状态后端服务Add a stateful back-end service to your application

Java Web API 服务的框架完成后,即可继续操作,去完成有状态后端服务。Now that the skeleton of the Java Web API service is complete, let's go ahead and complete the stateful back-end service.

Service Fabric 允许使用 Reliable Collections 直接在服务内以一致、可靠的方式存储数据。Service Fabric allows you to consistently and reliably store your data right inside your service by using reliable collections. Reliable Collections 是一组高度可用的可靠集合类。Reliable collections are a set of highly available and reliable collection classes. 用过 Java 集合的用户都对这些类的使用很熟悉。The usage of these classes is familiar to anyone who has used Java collections.

  1. 在包资源管理器中,右键单击应用程序项目中的“Voting”,然后选择“Service Fabric”>“添加 Service Fabric 服务”。In Package Explorer, right-click Voting within the application project and choose Service Fabric > Add Service Fabric Service.

  2. 在“添加服务”对话框中,选择“有状态服务”,将服务命名为“VotingDataService”,然后单击“添加服务”。In the Add Service dialog, choose Stateful Service and name the service VotingDataService and click Add Service.

    创建服务项目后,应用程序中会有两个服务。Once your service project is created, you have two services in your application. 随着继续生成应用程序,可采用相同的方式添加更多服务。As you continue to build your application, you can add more services in the same way. 每个服务都可以单独进行版本控制和升级。Each can be independently versioned and upgraded.

  3. Eclipse 会创建一个服务项目,并在包资源管理器中显示该项目。Eclipse creates a service project and displays it in Package Explorer.

    解决方案资源管理器

添加 VotingDataService.java 文件Add the VotingDataService.java file

VotingDataService.java 文件包含多种方法,其中的逻辑可用于在 Reliable Collections 中检索、添加和删除投票。The VotingDataService.java file contains the methods that contain logic to retrieve, add, and remove votes from the reliable collections. 将以下 VotingDataService 类方法添加到 VotingDataService/src/statefulservice/VotingDataService.java 文件中。Add the following VotingDataService class methods to the VotingDataService/src/statefulservice/VotingDataService.java file.

package statefulservice;

import java.util.HashMap;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;

import microsoft.servicefabric.services.communication.runtime.ServiceReplicaListener;

import microsoft.servicefabric.services.runtime.StatefulService;

import microsoft.servicefabric.services.remoting.fabrictransport.runtime.FabricTransportServiceRemotingListener;

import microsoft.servicefabric.data.ReliableStateManager;
import microsoft.servicefabric.data.Transaction;
import microsoft.servicefabric.data.collections.ReliableHashMap;
import microsoft.servicefabric.data.utilities.AsyncEnumeration;
import microsoft.servicefabric.data.utilities.KeyValuePair;

import system.fabric.StatefulServiceContext;

import rpcmethods.VotingRPC;

class VotingDataService extends StatefulService implements VotingRPC {
    private static final String MAP_NAME = "votesMap";
    private ReliableStateManager stateManager;

    protected VotingDataService (StatefulServiceContext statefulServiceContext) {
        super (statefulServiceContext);
    }

    @Override
    protected List<ServiceReplicaListener> createServiceReplicaListeners() {
        this.stateManager = this.getReliableStateManager();
        ArrayList<ServiceReplicaListener> listeners = new ArrayList<>();

        listeners.add(new ServiceReplicaListener((context) -> {
            return new FabricTransportServiceRemotingListener(context,this);
        }));

        return listeners;
    }

    // Method that will be invoked via RPC from the front end to retrieve the complete set of votes in the map
    public CompletableFuture<HashMap<String,String>> getList() {
        HashMap<String, String> tempMap = new HashMap<String, String>();

        try {

            ReliableHashMap<String, String> votesMap = stateManager
                    .<String, String> getOrAddReliableHashMapAsync(MAP_NAME).get();

            Transaction tx = stateManager.createTransaction();
            AsyncEnumeration<KeyValuePair<String, String>> kv = votesMap.keyValuesAsync(tx).get();
            while (kv.hasMoreElementsAsync().get()) {
                KeyValuePair<String, String> k = kv.nextElementAsync().get();
                tempMap.put(k.getKey(), k.getValue());
            }

            tx.close();


        } catch (Exception e) {
            e.printStackTrace();
        }

        return CompletableFuture.completedFuture(tempMap);
    }

    // Method that will be invoked via RPC from the front end to add an item to the votes list or to increase the
    // vote count for a particular item
    public CompletableFuture<Integer> addItem(String itemToAdd) {
        AtomicInteger status = new AtomicInteger(-1);

        try {

            ReliableHashMap<String, String> votesMap = stateManager
                    .<String, String> getOrAddReliableHashMapAsync(MAP_NAME).get();

            Transaction tx = stateManager.createTransaction();
            votesMap.computeAsync(tx, itemToAdd, (k, v) -> {
                if (v == null) {
                    return "1";
                }
                else {
                    int numVotes = Integer.parseInt(v);
                    numVotes = numVotes + 1;
                    return Integer.toString(numVotes);
                }
            }).get();

            tx.commitAsync().get();
            tx.close();

            status.set(1);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return CompletableFuture.completedFuture(new Integer(status.get()));
    }

    // Method that will be invoked via RPC from the front end to remove an item
    public CompletableFuture<Integer> removeItem(String itemToRemove) {
        AtomicInteger status = new AtomicInteger(-1);
        try {
            ReliableHashMap<String, String> votesMap = stateManager
                    .<String, String> getOrAddReliableHashMapAsync(MAP_NAME).get();

            Transaction tx = stateManager.createTransaction();
            votesMap.removeAsync(tx, itemToRemove).get();
            tx.commitAsync().get();
            tx.close();

            status.set(1);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return CompletableFuture.completedFuture(new Integer(status.get()));
    }

}

现在已创建前端无状态服务和后端服务的框架。The skeleton for the front-end stateless service and the backend service is now created.

创建应用程序的通信接口Create the communication interface to your application

下一步是连接前端无状态服务和后端服务。The next step is connecting the front-end stateless service and the backend service. 这两个服务都利用一个名为 VotingRPC 的接口来定义 Voting 应用程序的操作。Both the services utilize an interface called the VotingRPC that defines the operations of the Voting application. 此接口由前端和后端服务来共同实现,用于在这两项服务之间进行远程过程调用 (RPC)。This interface is implemented by both the front-end and back-end services to enable remote procedure calls (RPC) between the two services. 遗憾的是,Eclipse 不支持添加 Gradle 子项目,因此必须手动添加包含此接口的包。Unfortunately, Eclipse does not support the adding of Gradle subprojects, so the package that contains this interface has to be added manually.

  1. 在包资源管理器中右键单击“Voting”项目,然后单击“新建”->“文件夹”。Right click on the Voting project in the Package Explorer and click New -> Folder. 将文件夹命名为 VotingRPC/src/rpcmethodsName the folder VotingRPC/src/rpcmethods.

    创建 VotingRPC 包

  2. Voting/VotingRPC/src/rpcmethods 下创建名为 VotingRPC.java 的文件,然后将以下内容粘贴到该 VotingRPC.java 文件中。Create a file under Voting/VotingRPC/src/rpcmethods named VotingRPC.java and paste the following inside the VotingRPC.java file.

    package rpcmethods;
    
    import java.util.ArrayList;
    import java.util.concurrent.CompletableFuture;
    import java.util.List;
    import java.util.HashMap;
    
    import microsoft.servicefabric.services.remoting.Service;
    
    public interface VotingRPC extends Service {
        CompletableFuture<HashMap<String, String>> getList();
    
        CompletableFuture<Integer> addItem(String itemToAdd);
    
        CompletableFuture<Integer> removeItem(String itemToRemove);
    }
    
  3. 在 Voting/VotingRPC 目录中创建名为 build.gradle 的空文件,并将以下内容粘贴到其中。Create an empty file named build.gradle in the Voting/VotingRPC directory and paste the following inside it. 此 gradle 文件用于生成和创建由其他服务导入的 jar 文件。This gradle file is used to build and create the jar file that is imported by the other services.

    apply plugin: 'java'
    apply plugin: 'eclipse'
    
    sourceSets {
      main {
         java.srcDirs = ['src']
         output.classesDir = 'out/classes'
          resources {
           srcDirs = ['src']
         }
       }
    }
    
    clean.doFirst {
        delete "${projectDir}/out"
        delete "${projectDir}/VotingRPC.jar"
    }
    
    repositories {
        mavenCentral()
    }
    
    dependencies {
        compile ('com.microsoft.servicefabric:sf-actors:1.0.0')
    }
    
    jar {
        from configurations.compile.collect {
            (it.isDirectory() && !it.getName().contains("native")) ? it : zipTree(it)}
    
        manifest {
                attributes(
                'Main-Class': 'rpcmethods.VotingRPC')
            baseName "VotingRPC"
            destinationDir = file('./')
        }
    
        exclude 'META-INF/*.RSA', 'META-INF/*.SF','META-INF/*.DSA'
    }
    
    defaultTasks 'clean', 'jar'
    
  4. Voting/settings.gradle 文件中添加一行内容,以便将新创建的项目包括到内部版本中。In the Voting/settings.gradle file, add a line to include the newly created project in the build.

    include ':VotingRPC'
    
  5. Voting/VotingWeb/src/statelessservice/HttpCommunicationListener.java 文件中,将注释块替换为以下内容。In the Voting/VotingWeb/src/statelessservice/HttpCommunicationListener.java file, replace the comment block with the following.

    server.createContext("/getStatelessList", new HttpHandler() {
        @Override
        public void handle(HttpExchange t) {
            try {
                t.sendResponseHeaders(STATUS_OK,0);
                OutputStream os = t.getResponseBody();
    
                HashMap<String,String> list = ServiceProxyBase.create(VotingRPC.class, new URI("fabric:/VotingApplication/VotingDataService"), partitionKey, TargetReplicaSelector.DEFAULT, "").getList().get();
                String json = new Gson().toJson(list);
                os.write(json.getBytes(ENCODING));
                os.close();
            } catch (Exception e) {
                logger.log(Level.WARNING, null, e);
            }
        }
    });
    
    server.createContext("/removeItem", new HttpHandler() {
        @Override
        public void handle(HttpExchange t) {
            try {
                OutputStream os = t.getResponseBody();
                URI r = t.getRequestURI();
    
                Map<String, String> params = queryToMap(r.getQuery());
                String itemToRemove = params.get("item");
    
                Integer num = ServiceProxyBase.create(VotingRPC.class, new URI("fabric:/VotingApplication/VotingDataService"), partitionKey, TargetReplicaSelector.DEFAULT, "").removeItem(itemToRemove).get();
    
                if (num != 1)
                {
                    t.sendResponseHeaders(STATUS_ERROR, 0);
                } else {
                    t.sendResponseHeaders(STATUS_OK,0);
                }
    
                String json = new Gson().toJson(num);
                os.write(json.getBytes(ENCODING));
                os.close();
            } catch (Exception e) {
                logger.log(Level.WARNING, null, e);
            }
    
        }
    });
    
    server.createContext("/addItem", new HttpHandler() {
        @Override
        public void handle(HttpExchange t) {
            try {
                URI r = t.getRequestURI();
                Map<String, String> params = queryToMap(r.getQuery());
                String itemToAdd = params.get("item");
    
                OutputStream os = t.getResponseBody();
                Integer num = ServiceProxyBase.create(VotingRPC.class, new URI("fabric:/VotingApplication/VotingDataService"), partitionKey, TargetReplicaSelector.DEFAULT, "").addItem(itemToAdd).get();
                if (num != 1)
                {
                    t.sendResponseHeaders(STATUS_ERROR, 0);
                } else {
                    t.sendResponseHeaders(STATUS_OK,0);
                }
    
                String json = new Gson().toJson(num);
                os.write(json.getBytes(ENCODING));
                os.close();
            } catch (Exception e) {
                logger.log(Level.WARNING, null, e);
            }
        }
    });
    
  6. 将适当的 import 语句添加到 Voting/VotingWeb/src/statelessservice/HttpCommunicationListener.java 文件顶部。Add the appropriate import statement at the top of the Voting/VotingWeb/src/statelessservice/HttpCommunicationListener.java file.

    import rpcmethods.VotingRPC; 
    

在此阶段,前端、后端和 RPC 接口的功能都已完成。At this stage, the functionality for the front end, backend, and RPC interfaces are complete. 下一阶段是在部署到 Service Fabric 群集之前,先正确配置 Gradle 脚本。The next stage is to configure the Gradle scripts appropriately before deploying to a Service Fabric cluster.

大致了解投票示例应用程序Walk through the voting sample application

投票应用程序由以下两个服务组成:The voting application consists of two services:

  • Web 前端服务 (VotingWeb) - Java Web 前端服务,可提供网页服务,并公开用于与后端服务通信的 API。Web front-end service (VotingWeb)- A Java web front-end service that serves the web page and exposes APIs to communicate with the backend service.
  • 后端服务 (VotingDataService) - Java Web 服务,其定义的方法可以通过远程过程调用 (RPC) 来调用,以便保存投票结果。Back-end service (VotingDataService) - A Java web service, which defines methods that are invoked via Remote Procedure Calls (RPC) to persist votes.

应用程序关系图

在应用程序中执行操作(添加项目、投票、删除项目)时,会发生以下事件:When you perform an action in the application (add item, vote, remove item) the following events occur:

  1. JavaScript 将相应的请求作为 HTTP 请求发送给 Web 前端服务中的 Web API。A JavaScript sends the appropriate request to the web API in the web front-end service as an HTTP request.

  2. Web 前端服务使用 Service Fabric 的内置服务远程处理功能来查找请求,并将其转发给后端服务。The web front-end service uses the built-in Service Remoting functionality of Service Fabric to locate and forward the request to the back-end service.

  3. 后端服务定义的方法用于更新可靠字典中的结果。The back-end service defines methods that update the result in a reliable dictionary. 此可靠字典的内容复制到群集内的多个节点,并保存在磁盘上。The contents of this reliable dictionary get replicated to multiple nodes within the cluster and persisted on disk. 应用程序的所有数据都存储在群集中。All the application's data is stored in the cluster.

配置 Gradle 脚本Configure Gradle scripts

此部分配置项目的 Gradle 脚本。In this section, the Gradle scripts for the project are configured.

  1. Voting/build.gradle 文件的内容替换为以下内容。Replace the contents of the Voting/build.gradle file with the following.

    apply plugin: 'java'
    apply plugin: 'eclipse'
    
    subprojects {
        apply plugin: 'java'
    }
    
    defaultTasks 'clean', 'jar', 'copyDeps'
    
  2. Voting/VotingWeb/build.gradle 文件的内容替换为以下内容。Replace the contents of Voting/VotingWeb/build.gradle file with the following.

    apply plugin: 'java'
    apply plugin: 'eclipse'
    
    sourceSets {
      main {
         java.srcDirs = ['src']
         output.classesDir = 'out/classes'
          resources {
           srcDirs = ['src']
         }
       }
    }
    
    clean.doFirst {
        delete "${projectDir}/../lib"
        delete "${projectDir}/out"
        delete "${projectDir}/../VotingApplication/VotingWebPkg/Code/lib"
        delete "${projectDir}/../VotingApplication/VotingWebPkg/Code/VotingWeb.jar"
    }
    
    repositories {
        mavenCentral()
    }
    
    dependencies {
        compile ('com.microsoft.servicefabric:sf-actors:1.0.0-preview1')
        compile project(':VotingRPC')
    }
    
    task explodeDeps(type: Copy, dependsOn:configurations.compile) { task ->
        configurations.compile.filter {!it.toString().contains("native")}.each{
            from it
        }
    
        configurations.compile.filter {it.toString().contains("native")}.each{
            from zipTree(it)
        }
        into "../lib/"
        include "lib*.so", "*.jar"
    }
    
    task copyDeps<< {
        copy {
            from("../lib/")
            into("../VotingApplication/VotingWebPkg/Code/lib")
            include('lib*.so')
        }
    }
    
    compileJava.dependsOn(explodeDeps)
    
    jar {
        from configurations.compile.collect {(it.isDirectory() && !it.getName().contains("native")) ? it : zipTree(it)}
    
        manifest {
            attributes(
                'Main-Class': 'statelessservice.VotingWebServiceHost')
            baseName "VotingWeb"
            destinationDir = file('../VotingApplication/VotingWebPkg/Code/')
        }
    
        exclude 'META-INF/*.RSA', 'META-INF/*.SF','META-INF/*.DSA'
    }
    
    defaultTasks 'clean', 'jar', 'copyDeps'
    
  3. 替换 Voting/VotingDataService/build.gradle 文件的内容。Replace the contents of Voting/VotingDataService/build.gradle file.

    apply plugin: 'java'
    apply plugin: 'eclipse'
    
    sourceSets {
      main {
         java.srcDirs = ['src']
         output.classesDir = 'out/classes'
          resources {
           srcDirs = ['src']
         }
       }
    }
    
    clean.doFirst {
        delete "${projectDir}/../lib"
        delete "${projectDir}/out"
        delete "${projectDir}/../VotingApplication/VotingDataServicePkg/Code/lib"
        delete "${projectDir}/../VotingApplication/VotingDataServicePkg/Code/VotingDataService.jar"
    }
    
    repositories {
        mavenCentral()
    }
    
    dependencies {
        compile ('com.microsoft.servicefabric:sf-actors:1.0.0-preview1')
        compile project(':VotingRPC')
    }
    
    task explodeDeps(type: Copy, dependsOn:configurations.compile) { task ->
        configurations.compile.filter {!it.toString().contains("native")}.each{
            from it
        }
    
        configurations.compile.filter {it.toString().contains("native")}.each{
            from zipTree(it)
        }
        into "../lib/"
        include "lib*.so", "*.jar"
    }
    
    compileJava.dependsOn(explodeDeps)
    
    task copyDeps<< {
        copy {
            from("../lib/")
            into("../VotingApplication/VotingDataServicePkg/Code/lib")
            include('lib*.so')
        }
    }
    
    jar {
        from configurations.compile.collect {
            (it.isDirectory() && !it.getName().contains("native")) ? it : zipTree(it)}
    
        manifest {
            attributes('Main-Class': 'statefulservice.VotingDataServiceHost')
    
            baseName "VotingDataService"
            destinationDir = file('../VotingApplication/VotingDataServicePkg/Code/')
        }
    
        exclude 'META-INF/*.RSA', 'META-INF/*.SF','META-INF/*.DSA'
    }
    
    defaultTasks 'clean', 'jar', 'copyDeps'
    

将应用程序部署到本地群集Deploy application to local cluster

现在可以将应用程序部署到本地 Service Fabric 群集了。At this point, the application is ready to be deployed to a local Service Fabric cluster.

  1. 在包资源管理器中右键单击“Voting”项目,然后单击“Service Fabric”->“生成应用程序”以生成应用程序。Right click on the Voting project in the Package Explorer and click Service Fabric -> Build Application to build your application.

  2. 运行本地 Service Fabric 群集。Run your local Service Fabric cluster. 此步骤取决于开发环境(Mac 或 Linux)。This step depends on your development environment (Mac or Linux).

    如果使用的是 Mac,则使用以下命令运行本地群集:将传递到 -v 参数的命令替换为你自己的工作区的路径。If you are using a Mac, you run the local cluster with the following command: Replace the command passed into the -v parameter with the path to your own workspace.

    docker run -itd -p 19080:19080 -p 8080:8080 -p --name sfonebox servicefabricoss/service-fabric-onebox
    

    请参阅 OS X 设置指南中的更多详细说明。See more detailed instructions in the OS X setup guide.

    如果是在 Linux 计算机上运行,请通过以下命令启动本地群集:If you are running on a Linux machine, you start the local cluster with the following command:

    sudo /opt/microsoft/sdk/servicefabric/common/clustersetup/devclustersetup.sh
    

    请参阅 Linux 设置指南中的更多详细说明。See more detailed instructions in the Linux setup guide.

  3. 在适用于 Eclipse 的包资源管理器中右键单击“Voting”项目,然后单击“Service Fabric”->“发布应用程序...”In the Package Explorer for Eclipse, right click on the Voting project and click Service Fabric -> Publish Application ...

  4. 在“发布应用程序”窗口的下拉列表中,选择“Local.json”并单击“发布”。In the Publish Application window, select Local.json from the dropdown, and click Publish.

  5. 转到 Web 浏览器并访问 http://localhost:8080,以便查看在本地 Service Fabric 群集上运行的应用程序。Go to your web browser and access http://localhost:8080 to view your running application on the local Service Fabric cluster.

后续步骤Next steps

本教程的此部分介绍了如何:In this part of the tutorial, you learned how to:

  • 以有状态 Reliable Services 形式创建 Java 服务Create Java service as a stateful reliable service
  • 以无状态 Web 服务形式创建 Java 服务Create Java service as a stateless web service
  • 添加一个 Java 接口,用于处理服务之间的远程过程调用 (RPC)。Add a Java interface to handle the Remote Procedure Calls (RPC) between your services
  • 配置 Gradle 脚本Configure your Gradle scripts
  • 生成应用程序并将其部署到本地 Service Fabric 群集Build and deploy your application to a local Service Fabric cluster

转到下一教程:Advance to the next tutorial: