Tutorial: Creación de una aplicación con un servicio front-end de la API de Java y un servicio back-end con estado en Service Azure FabricTutorial: Create an application with a Java API front-end service and a stateful back-end service on Azure Service Fabric

Este tutorial es la primera parte de una serie.This tutorial is part one of a series. Cuando termine, tendrá una aplicación de votación con un front-end web de Java que guarda los resultados de la votación en un servicio back-end con estado en 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. Esta serie de tutoriales requiere una máquina de desarrollador de Linux o Mac OSX en funcionamiento.This tutorial series requires that you have a working Mac OSX or Linux developer machine. Si no desea crear manualmente la aplicación de votación, puede descargar el código fuente de la aplicación terminada y pasar directamente al Tutorial de la aplicación de ejemplo de votación.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. Además, considere la posibilidad de seguir las instrucciones del artículo Inicio rápido: Implementación de una aplicación de Java en Azure Service Fabric con Linux.Also, consider following the Quickstart for Java reliable services..

Ejemplo de votación de Service Fabric

En esta serie de tutoriales, se aprende a:In this tutorial series you learn how to:

En la primera parte de la serie, se aprende a:In part one of the series, you learn how to:

  • Crear un servicio confiable de Java con estadoCreate a Java stateful reliable service
  • Crear un servicio de aplicación web sin estado de JavaCreate a Java stateless web application service
  • Usar una comunicación remota de servicio para comunicarse con el servicio con estadoUse service remoting to communicate with the stateful service
  • Implementar una aplicación en un clúster local de Service FabricDeploy application on a local Service Fabric cluster

Requisitos previosPrerequisites

Antes de empezar este tutorial:Before you begin this tutorial:

  • Si no tiene una suscripción a Azure, cree una cuenta gratuita.If you don't have an Azure subscription, create a free account.
  • Configure el entorno de desarrollo para Mac o Linux.Set up your development environment for Mac or Linux. Siga las instrucciones para instalar el complemento de Eclipse, Gradle, el SDK de Service Fabric y la CLI de Service Fabric (sfctl).Follow the instructions to install the Eclipse plug-in, Gradle, the Service Fabric SDK, and the Service Fabric CLI (sfctl).

Creación del servicio sin estado de Java del front-endCreate the front-end Java stateless service

En primer lugar, cree el front-end web de la aplicación de votación.First, create the web front end of the Voting application. Una interfaz de usuario web con AngularJS envía las solicitudes al servicio sin estado Java, que ejecuta un servidor HTTP ligero.A web UI powered by AngularJS sends requests to the Java stateless service, which runs a lightweight HTTP server. Este servicio procesa cada solicitud y envía una llamada a procedimiento remoto al servicio con estado para almacenar los votos.This service processes each request and sends a remote procedure call to the stateful service to store the votes.

  1. Abra Eclipse.Open Eclipse.

  2. Cree un proyecto con File (Archivo) > New (Nuevo) > Other (Otros) > Service Fabric > Service Fabric Project (Proyecto de Service Fabric) .Create a project with File > New > Other > Service Fabric > Service Fabric Project.

    Nuevo proyecto de Service Fabric en Eclipse

  3. En el cuadro de diálogo ServiceFabric Project Wizard (Asistente para proyecto de Service Fabric), asigne el nombre Voting al proyecto y seleccione Next (Siguiente).In the ServiceFabric Project Wizard dialog, name the Project Voting and select Next.

    Selección de un servicio sin estado de Java en el cuadro de diálogo de nuevo servicio

  4. En la página Add Service (Agregar servicio), seleccione Stateless Service (Servicio sin estado) y asigne el nombre VotingWeb al servicio.On the Add Service page, select Stateless Service, and name your service VotingWeb. Seleccione Finish (Finalizar) para crear el proyecto.Select Finish to create the project.

    Creación de un servicio sin estado para el proyecto de Service Fabric

    Eclipse crea un proyecto de una aplicación y un servicio, y los muestra en el Explorador de paquetes.Eclipse creates an application and a service project and displays them in Package Explorer.

    Explorador de paquetes de Eclipse después de la creación de una aplicación

La tabla ofrece una breve descripción de los elementos del Explorador de paquetes de la captura de pantalla anterior.The table gives a short description of each item in the package explorer from the previous screenshot.

Elemento del Explorador de paquetesPackage Explorer Item DescripciónDescription
PublishProfilesPublishProfiles Contiene archivos JSON que describen los detalles del perfil de los clústeres tanto de Azure Service Fabric como locales.Contains JSON files describing profile details of local and Azure Service Fabric clusters. El contenido de estos archivos lo usa el complemento al implementar la aplicación.The contents of these files is used by the plugin when deploying the application.
ScriptsScripts Contiene scripts auxiliares que pueden utilizarse desde la línea de comandos para administrar rápidamente la aplicación con un clúster.Contains helper scripts that can be used from the command line to quickly manage your application with a cluster.
VotingApplicationVotingApplication Contiene la aplicación de Service Fabric que se inserta en el clúster de Service Fabric.Contains the Service Fabric application that is pushed to the Service Fabric cluster.
VotingWebVotingWeb Contiene los archivos de origen del servicio sin estado del front-end junto con el archivo de compilación de Gradle relacionado.Contains the front-end stateless service source files along with the related gradle build file.
build.gradlebuild.gradle Archivo de Gradle que se usa para administrar el proyecto.Gradle file used to manage the project.
settings.gradlesettings.gradle Contiene los nombres de los proyectos de Gradle de esta carpeta.Contains names of Gradle projects in this folder.

Incorporación de HTML y Javascript al servicio VotingWebAdd HTML and Javascript to the VotingWeb service

Para agregar una interfaz de usuario que pueda ser representada por el servicio sin estado, agregue un archivo HTML.To add a UI that can be rendered by the stateless service, add an HTML file. Después, este archivo HTML lo representará el servidor HTTP liviano integrado en el servicio Java sin estado.This HTML file is then rendered by the lightweight HTTP server embedded into the stateless Java service.

  1. Expanda el directorio VotingApplication para acceder al directorio VotingApplication/VotingWebPkg/código.Expand the VotingApplication directory to reach the VotingApplication/VotingWebPkg/Code directory.

  2. Haga clic con el botón derecho en el directorio Code y, después, seleccione New > Folder (Nuevo > Carpeta).Right-click on the Code directory and select New > Folder.

  3. Asigne a la carpeta el nombre wwwroot y seleccione Finish (Finalizar).Name the folder wwwroot and select Finish.

    Crear la carpeta wwwroot en Eclipse

  4. Agregue a wwwroot un archivo llamado index.html y pegue en él el siguiente contenido.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>

Actualización del archivo VotingWeb.javaUpdate the VotingWeb.java file

En el subproyecto VotingWeb, abra el archivo VotingWeb/src/statelessservice/VotingWeb.java.In the VotingWeb subproject, open the VotingWeb/src/statelessservice/VotingWeb.java file. El servicio VotingWeb es la puerta de enlace para el servicio sin estado y es responsable de configurar el agente de escucha de comunicación de la API del front-end.The VotingWeb service is the gateway into the stateless service and is responsible for setting up the communication listener for the front-end API.

Reemplace el método createServiceInstanceListeners existente en el archivo por el siguiente y guarde los cambios.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;
}

Adición del archivo HTTPCommunicationListener.javaAdd the HTTPCommunicationListener.java file

El agente de escucha de la comunicación HTTP actúa como el controlador que configura el servidor HTTP y expone las API que definen las acciones de votación.The HTTP communication listener acts as a controller that sets up the HTTP server and exposes the APIs defining voting actions. Haga clic con el botón derecho en el paquete statelessservice de la carpeta VotingWeb/src/statelessservice y seleccione New****File (Nuevo****Archivo).Right-click on the statelessservice package in the VotingWeb/src/statelessservice folder, then select New****File. Asigne al archivo el nombre HttpCommunicationListener.java y seleccione Finish (Finalizar).Name the file HttpCommunicationListener.java and select Finish.

Reemplace el contenido del archivo por el siguiente y guarde los cambios.Replace the file contents with the following, then save your changes. Más adelante, en Actualización del archivo HttpCommunicationListener.java, este archivo se modifica para que represente, lea y escriba los datos de votación del servicio back-end.Later, in Update the HttpCommunicationListener.java file, this file is modified to render, read, and write voting data from the back-end service. Por ahora, el agente de escucha simplemente devuelve el código HTML estático para la aplicación Voting.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();
    }
}

Configuración del puerto de escuchaConfigure the listening port

Cuando se crea el servicio front-end VotingWeb, Service Fabric selecciona un puerto en el que el servicio realiza la escucha.When the VotingWeb service front-end service is created, Service Fabric selects a port for the service to listen on. Dado que el servicio VotingWeb actúa como front-end para esta aplicación y acepta tráfico externo, queremos enlazar dicho servicio a un puerto fijo y conocido.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. En el Explorador de paquetes, abra VotingApplication/VotingWebPkg/ServiceManifest.xml.In Package Explorer, open VotingApplication/VotingWebPkg/ServiceManifest.xml. Busque el recurso Punto de conexión en la sección Recursos y cambie el valor de Puerto a 8080 (vamos a seguir usando este puerto en el tutorial).Find the Endpoint resource in the Resources section and change the Port value to 8080 (we'll continue using this port throughout the tutorial). Para implementar y ejecutar la aplicación localmente, el puerto de escucha de la aplicación debe estar abierto y disponible en el equipo.To deploy and run the application locally, the application listening port must be open and available on your computer. Pegue el siguiente fragmento de código dentro del elemento ServiceManifest (es decir, justo debajo del elemento <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>

Adición de un servicio back-end con estado a la aplicaciónAdd a stateful back-end service to your application

Una vez que se ha completado el esqueleto del servicio Java Web API, pasemos a completar el servicio back-end con estado.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 permite almacenar de forma coherente y confiable su estado justo dentro de su servicio mediante Reliable Collections.Service Fabric allows you to consistently and reliably store your data right inside your service by using reliable collections. Las colecciones confiables son un conjunto de clases de colección de gran disponibilidad y confiabilidad.Reliable collections are a set of highly available and reliable collection classes. Cualquiera que haya usado colecciones de Java sabrá usar estas clases.The usage of these classes is familiar to anyone who has used Java collections.

  1. En el Explorador de paquetes, haga clic con el botón derecho en Voting en el proyecto de la aplicación y seleccione Service Fabric > Add Service Fabric Service (Service Fabric > Agregar servicio Service Fabric).In Package Explorer, right-click Voting within the application project and select Service Fabric > Add Service Fabric Service.

  2. En el cuadro de diálogo Add Service (Agregar servicio), seleccione Stateful Service (Servicio con estado), asigne el nombre VotingDataService al servicio y seleccione Add Service (Agregar servicio).In the Add Service dialog, select Stateful Service and name the service VotingDataService and select Add Service.

    Una vez creado el proyecto de servicio, tendrá dos servicios en la aplicación.Once your service project is created, you have two services in your application. Mientras continúa la creación de la aplicación, puede agregar más servicios de la misma forma.As you continue to build your application, you can add more services in the same way. Cada uno puede tener versiones y actualizaciones independientes.Each can be independently versioned and upgraded.

  3. Eclipse crea un proyecto de un servicio y lo muestra en el Explorador de paquetes.Eclipse creates a service project and displays it in Package Explorer.

    Explorador de proyectos de Eclipse

Agregue el archivo VotingDataService.javaAdd the VotingDataService.java file

El archivo VotingDataService.java tiene los métodos que contienen la lógica para recuperar, agregar y quitar votos de las colecciones confiables.The VotingDataService.java file contains the methods that contain logic to retrieve, add, and remove votes from the reliable collections. Agregue los siguientes métodos de clase VotingDataService al archivo 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()));
    }

}

Ahora se crea el esqueleto del servicio front-end sin estado y del servicio back-end.The skeleton for the front-end stateless service and the backend service is now created.

Creación de la interfaz de comunicación con la aplicaciónCreate the communication interface to your application

El siguiente paso es conectar el servicio front-end sin estado y el servicio back-end.The next step is connecting the front-end stateless service and the backend service. Ambos usan una interfaz denominada VotingRPC que define las operaciones de la aplicación Voting.Both the services utilize an interface called the VotingRPC that defines the operations of the Voting application. Esta interfaz la implementan los servicios front-end y back-end para habilitar las llamadas a procedimiento remoto (RPC) entre ambos servicios.This interface is implemented by both the front-end and back-end services to enable remote procedure calls (RPC) between the two services. Por desgracia, Eclipse no admite la adición de subproyectos de Gradle, de modo que el paquete que contiene esta interfaz debe agregarse manualmente.Unfortunately, Eclipse does not support the adding of Gradle subprojects, so the package that contains this interface has to be added manually.

  1. Haga clic con el botón derecho en el proyecto Voting en el Explorador de paquetes y seleccioneNew > Folder (Nuevo > Carpeta).Right-click on the Voting project in the Package Explorer and select New > Folder. Asigne a la carpeta el nombre VotingRPC/src/rpcmethods.Name the folder VotingRPC/src/rpcmethods.

    Creación de un paquete VotingRPC en el explorador de paquetes de Eclipse

  2. En Voting/VotingRPC/src/rpcmethods, cree un archivo llamado VotingRPC.java y pegue en él el siguiente contenido .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. Cree un archivo vacío denominado build.gradle en el directorio Voting/VotingRPC y pegue en él lo siguiente.Create an empty file named build.gradle in the Voting/VotingRPC directory and paste the following inside it. Este archivo de Gradle se usa para generar y crear el archivo jar que importan los otros servicios.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. En el archivo Voting/settings.gradle, agregue una línea para incluir el proyecto recién creado en la compilación.In the Voting/settings.gradle file, add a line to include the newly created project in the build.

    include ':VotingRPC'
    
  5. En el archivo Voting/VotingWeb/src/statelessservice/HttpCommunicationListener.java, reemplace el bloque de comentario por lo siguiente.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. Agregue la instrucción de importación pertinente al principio del archivo 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; 
    

En esta fase, la funcionalidad de las interfaces de RPC, back-end y front-end está completa.At this stage, the functionality for the front end, backend, and RPC interfaces are complete. La siguiente fase consiste en configurar correctamente los scripts de Gradle antes de implementar en un clúster de Service Fabric.The next stage is to configure the Gradle scripts appropriately before deploying to a Service Fabric cluster.

Tutorial de la aplicación de ejemplo de votaciónWalk through the voting sample application

La aplicación de votación consta de dos servicios:The voting application consists of two services:

  • Servicio front-end web (VotingWeb): un servicio front-end de Java que ofrece servicio a la página web y expone las API para comunicarse con el servicio back-end.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.
  • Servicio back-end (VotingDataService) - servicio de web de Java que define los métodos que se invocan a través de llamadas a procedimiento remoto (RPC) para conservar los votos.Back-end service (VotingDataService) - A Java web service, which defines methods that are invoked via Remote Procedure Calls (RPC) to persist votes.

Diagrama de ejemplo de votación

Al realizar una acción en la aplicación (agregar un elemento, votar o quitar un elemento) se producen los siguientes eventos:When you perform an action in the application (add item, vote, remove item) the following events occur:

  1. JavaScript envía la solicitud pertinente a la API web del servicio front-end web en forma de solicitud HTTP.A JavaScript sends the appropriate request to the web API in the web front-end service as an HTTP request.

  2. El servicio front-end web usa la funcionalidad integrada de comunicación remota de Service Fabric para localizar y reenviar la solicitud al servicio back-end.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. El servicio back-end define métodos que actualizan el resultado en un diccionario confiable.The back-end service defines methods that update the result in a reliable dictionary. El contenido de este diccionario se replica en varios nodos del clúster y se conserva en el disco.The contents of this reliable dictionary get replicated to multiple nodes within the cluster and persisted on disk. Todos los datos de la aplicación se almacenan en el clúster.All the application's data is stored in the cluster.

Configuración de scripts de GradleConfigure Gradle scripts

En esta sección se configuran los scripts de Gradle del proyecto.In this section, the Gradle scripts for the project are configured.

  1. Reemplace el contenido del archivo Voting/build.gradle por el siguiente.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. Reemplace el contenido del archivo Voting/VotingWeb/build.gradle por lo siguiente.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. Reemplace el contenido del archivo 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'
    

Implementación de una aplicación en un clúster localDeploy application to local cluster

En este momento, la aplicación está lista para implementarse en un clúster local de Service Fabric.At this point, the application is ready to be deployed to a local Service Fabric cluster.

  1. En el Explorador de paquetes, haga clic con el botón derecho en el proyecto Voting y seleccione Service Fabric > Build Application (Service Fabric > Compilar Application) para compilar la aplicación.Right-click on the Voting project in the Package Explorer and select Service Fabric > Build Application to build your application.

  2. Ejecute el clúster local de Service Fabric.Run your local Service Fabric cluster. Este paso depende del entorno de desarrollo (Mac o Linux).This step depends on your development environment (Mac or Linux).

    Si usa un equipo Mac, ejecute el clúster local con el siguiente comando: Reemplace el comando pasado al parámetro -v por la ruta de acceso a su propio espacio de trabajo.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
    

    Consulte instrucciones más detalladas en la Guía de instalación de OS X.See more detailed instructions in the OS X setup guide.

    Sin embargo, si usa un equipo Linux, inicie el clúster local con el siguiente comando: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
    

    Consulte instrucciones más detalladas en la Guía de instalación de Linux.See more detailed instructions in the Linux setup guide.

  3. En el Explorador de paquetes de Eclipse, haga clic con el botón derecho en el proyecto Voting y haga clic en Service Fabric > Publish Application (Service Fabric > Publicar aplicación).In the Package Explorer for Eclipse, right-click on the Voting project and select Service Fabric > Publish Application

  4. En la ventana Publish Application (Publicar aplicación), seleccione Local.json en la lista desplegable y seleccione Publish (Publicar).In the Publish Application window, select Local.json from the dropdown, and select Publish.

  5. Desde al explorador web acceda a http://localhost:8080 para ver la aplicación en ejecución en el clúster local de Service Fabric.Go to your web browser and access http://localhost:8080 to view your running application on the local Service Fabric cluster.

Pasos siguientesNext steps

En esta parte del tutorial, ha aprendido a:In this part of the tutorial, you learned how to:

  • Crear un servicio Java en forma de servicio confiable con estadoCreate Java service as a stateful reliable service
  • Crear un servicio Java en forma de servicio confiable sin estadoCreate Java service as a stateless web service
  • Agregar una interfaz de Java para controlar las llamadas a procedimiento remoto (RPC) entre los serviciosAdd a Java interface to handle the Remote Procedure Calls (RPC) between your services
  • Configurar scripts de GradleConfigure your Gradle scripts
  • Compilar e implementar una aplicación en un clúster local de Service FabricBuild and deploy your application to a local Service Fabric cluster

Avance hasta el siguiente tutorial:Advance to the next tutorial: