Руководство. Создание приложения с интерфейсной службой API Java и серверной службой с отслеживанием состояния в Azure Service FabricTutorial: Create an application with a Java API front-end service and a stateful back-end service on Azure Service Fabric

Это руководство представляет первую часть цикла.This tutorial is part one of a series. Выполнив инструкции из этого руководства, вы получите приложение для голосования с клиентской частью в виде веб-приложения Java, которое сохраняет результаты голосования во внутренней службе с отслеживанием состояния в Azure Service Fabric.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 on Azure Service Fabric. В этой серии руководств требуется, чтобы у вас был работающий компьютер для разработчиков 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 в Azure Service Fabric.Also, consider following the Quickstart for Java reliable services..

Пример для голосования Service Fabric

Из этого цикла руководств вы узнаете, как выполнять следующие задачи:In this tutorial series you learn how to:

В первой части цикла вы узнаете, как выполнять такие задачи:In part one of the series, you learn how to:

  • Создание надежной службы с отслеживанием состояния Java.Create a Java stateful reliable service
  • Создание службы веб-приложения без отслеживания состояния Java.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, создайте бесплатную учетную запись Azure.If you don't have an Azure subscription, create a free account.
  • Настройте среду разработки для Mac или Linux.Set up your development environment for Mac or Linux. Следуя инструкциям, установите подключаемый модуль Eclipse, Gradle, пакет SDK для Service Fabric и интерфейс командной строки Service Fabric (sfctl).Follow the instructions to install the Eclipse plug-in, Gradle, the Service Fabric SDK, and the Service Fabric CLI (sfctl).

Создание интерфейсной службы без отслеживания состояния JavaCreate the front-end Java stateless service

Сначала создайте веб-интерфейс приложения для голосования.First, create the web front end of the Voting application. Пользовательский веб-интерфейс на базе AngularJS отправляет запросы в службу без отслеживания состояния Java, которая запускает упрощенный HTTP-сервер.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.Open Eclipse.

  2. Создайте проект, последовательно выбрав Файл > Создать > Другое > Service Fabric > Проект Service Fabric.Create a project with File > New > Other > Service Fabric > Service Fabric Project.

    Новый проект Service Fabric в Eclipse

  3. В диалоговом окне ServiceFabric Project Wizard (Мастер проектов Service Fabric) назовите проект Voting и нажмите кнопку Далее.In the ServiceFabric Project Wizard dialog, name the Project Voting and select Next.

    Выбор службы без отслеживания состояния Java в диалоговом окне создания службы

  4. На странице Добавление службы выберите Служба без отслеживания состояния и укажите имя службы VotingWeb.On the Add Service page, select Stateless Service, and name your service VotingWeb. Нажмите кнопку Готово, чтобы создать проект.Select Finish to create the project.

    Создание службы без отслеживания состояния для проекта Service Fabric

    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 в службу VotingWebAdd HTML and Javascript to the VotingWeb service

Чтобы добавить пользовательский интерфейс, который может быть преобразован для просмотра службой без отслеживания состояния, добавьте HTML-файл.To add a UI that can be rendered by the stateless service, add an HTML file. Этот HTML-файл преобразовывается для просмотра на упрощенном HTTP-сервере, встроенном в службу Java без отслеживания состояния.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. Щелкните правой кнопкой мыши каталог Код, а затем последовательно выберите Создать > Папка.Right-click on the Code directory and select New > Folder.

  3. Назовите папку wwwroot и выберите Готово.Name the folder wwwroot and select Finish.

    Eclipse создает папку wwwroot

  4. Добавьте файл в wwwroot, который называется index.html, и вставьте в него следующее содержимое.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.javaUpdate 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.javaAdd 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. Щелкните правой кнопкой мыши пакет statelessservice в папке VotingWeb/src/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 select 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. Теперь прослушиватель просто возвращает статические 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

Когда создается интерфейсная служба VotingWebService, 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.xml.In Package Explorer, open VotingApplication/VotingWebPkg/ServiceManifest.xml. Найдите ресурс Endpoint в разделе Resources и задайте для свойства Port значение 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

Теперь, когда схема службы веб-API Java завершена, продолжим и завершим серверную службу с отслеживанием состояния.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 позволяет согласованно и надежно хранить данные прямо в службе с помощью надежных коллекций.Service Fabric allows you to consistently and reliably store your data right inside your service by using 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 > Add Service Fabric Service (Добавить службу Service Fabric).In Package Explorer, right-click Voting within the application project and select Service Fabric > Add Service Fabric Service.

  2. В диалоговом окне Добавить службу выберите Служба с отслеживанием состояния, назовите службу VotingDataService и щелкните Добавить службу.In the Add Service dialog, select Stateful Service and name the service VotingDataService and select 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.

    Обозреватель проектов Eclipse

Добавление файла VotingDataService.javaAdd the VotingDataService.java file

Файл VotingDataService.java содержит методы, в которых содержится логика для извлечения, добавления и удаления голосов из надежных коллекций.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, который определяет операции приложения для голосования.Both the services utilize an interface called the VotingRPC that defines the operations of the Voting application. Этот интерфейс реализуется с помощью интерфейсных и серверных служб, включая удаленные вызовы процедур между двумя службами.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 select New > Folder. Назовите папку VotingRPC/src/rpcmethods.Name the folder VotingRPC/src/rpcmethods.

    Создание пакета VotingRPC в обозревателе пакетов Eclipse

  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. Создайте пустой файл build.gradle в каталоге Voting/VotingRPC и вставьте в него следующий код.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. Добавьте соответствующую инструкцию импорта в верхнюю часть файла 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. Следующий этап — это настроить скрипты Gradle соответствующим образом прежде, чем развертывать их в кластер Service Fabric.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:

  • Служба веб-интерфейса (VotingWeb) — это служба веб-интерфейса Java, которая обслуживает веб-страницу и предоставляет доступ к 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, которая определяет методы, вызываемые в удаленных вызовах процедуры для сохранения голосов.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 отправляет соответствующий запрос о голосовании веб-API в службе веб-интерфейса в виде HTTP-запроса.A JavaScript sends the appropriate request to the web API in the web front-end service as an HTTP request.

  2. Служба веб-интерфейса использует встроенную функцию удаленного взаимодействия со службой 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.

Настройка скриптов GradleConfigure 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 select 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 mcr.microsoft.com/service-fabric/onebox:latest
    

    Более подробные инструкции см. в статье Настройка среды разработки для Mac 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 select Service Fabric > Publish Application

  4. В окне Публикация приложения выберите Local.json в раскрывающемся списке и выберите Опубликовать.In the Publish Application window, select Local.json from the dropdown, and select Publish.

  5. В веб-браузере перейдите по адресу 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:

  • Создание службы Java как надежной службы с отслеживанием состояния.Create Java service as a stateful reliable service
  • Создание службы Java как веб-службы без отслеживания состояния.Create Java service as a stateless web service
  • Добавление интерфейса Java для обработки удаленных вызовов процедур между службами.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:

Tutorial: Debug a Java application deployed on a local Service Fabric cluster (Руководство. Отладка приложения Java, развернутого в локальном кластере Service Fabric)Debug and log applications on local cluster