Tutorial: Criar uma aplicação com um serviço de front-end da API Java e um serviço de back-end com monitorização de estado no Azure Service Fabric

Este tutorial é a primeira parte de uma série. Quando tiver terminado, terá uma aplicação de Voto com um front-end Web Java que guarda os resultados da votação num serviço de back-end com monitorização de estado no Azure Service Fabric. Esta série de tutoriais requer que tenha um computador de programador Mac OSX ou Linux. Se não quiser criar manualmente a aplicação de voto, pode transferir o código fonte da aplicação concluída e avançar para Percorrer a aplicação de exemplo de votação. Além disso, considere seguir o Início Rápido para serviços fiáveis java.

Exemplo de votação do Service Fabric

Nesta série de tutoriais, ficará a saber como:

Na primeira parte da série, saiba como:

  • Criar um serviço Java fiável e com estado
  • Criar um serviço de aplicações Web em Java sem estado
  • Utilizar a comunicação remota do serviço para comunicar com o serviço com estado
  • Implementar a aplicação num cluster do Service Fabric local

Pré-requisitos

Antes de começar este tutorial:

  • Se não tiver uma subscrição do Azure, crie uma conta gratuita.
  • Configure o ambiente de desenvolvimento para Mac ou Linux. Siga as instruções para instalar o plug-in do Eclipse, Gradle, o SDK do Service Fabric e a CLI do Service Fabric (sfctl).

Criar o serviço front-end em Java sem estado

Em primeiro lugar, crie o front-end para a Web da aplicação de Voto. Uma IU da Web com tecnologia AngularJS envia pedidos para o serviço sem estado Java, que executa um servidor HTTP leve. Este serviço processa cada pedido e envia uma chamada de procedimento remoto para o serviço com monitorização de estado para armazenar os votos.

  1. Abra o Eclipse.

  2. Crie um projeto com o projeto Ficheiro>Novo>Outro>Service Fabric>do Service Fabric.

    Novo projeto do Service Fabric no Eclipse

  3. Na caixa de diálogo Assistente do Projeto ServiceFabric , atribua o nome Project Voting e selecione Seguinte.

    Escolher o serviço sem estado em Java na caixa de diálogo do novo serviço

  4. Na página Adicionar Serviço , selecione Serviço Sem Estado e dê um nome ao seu serviço VotingWeb. Selecione Concluir para criar o projeto.

    Criar um serviço sem estado para o seu projeto do Service Fabric

    O Eclipse cria uma aplicação e um projeto de serviço e apresenta-os no Package Explorer (Explorador de Pacotes).

    Package Explorer do Eclipse após a criação da aplicação

A tabela mostra uma descrição breve de cada item no Package Explorer da captura de ecrã anterior.

Item do Explorador de Pacotes Descrição
PublishProfiles Contém os ficheiros JSON que descrevem os detalhes do perfil de clusters locais e do Azure Service Fabric. O plug-in utiliza o conteúdo destes ficheiros durnte a implementação da aplicação.
Scripts Contém scripts de programa auxiliar que podem ser utilizados na linha de comandos para gerir rapidamente a sua aplicação com um cluster.
VotingApplication Contém a aplicação do Service Fabric que é enviada por push para o cluster do Service Fabric.
VotingWeb Contém os ficheiros de origem do serviço sem estado do front-end, juntamente com o ficheiro de compilação do gradle relacionados.
build.gradle Ficheiro do Gradle utilizado para gerir o projeto.
settings.gradle Contém os nomes de projetos do Gradle nesta pasta.

Adicionar HTML e JavaScript ao serviço VotingWeb

Para adicionar uma IU que pode ser composta pelo serviço sem estado, adicione um ficheiro HTML. Este ficheiro HTML é, então, processado pelo servidor HTTP simples que está incorporado no serviço de Java sem estado.

  1. Expanda o diretório VotingApplication para ver o diretório VotingApplication/VotingWebPkg/Code.

  2. Clique com o botão direito do rato no diretório Código e selecione Nova>Pasta.

  3. Atribua um nome à pasta wwwroot e selecione Concluir.

    Criar pasta wwwroot no Eclipse

  4. Adicione um ficheiro ao wwwroot chamado index.html e cole os seguintes conteúdos neste ficheiro.

<!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>

Atualizar o ficheiro VotingWeb.java

No sub-projeto VotingWeb, abra o ficheiro VotingWeb/src/statelessservice/VotingWeb.java. O serviço VotingWeb é o gateway para o serviço sem estado e é responsável por configurar o serviço de escuta de comunicação para a API de front-end.

Substitua o método createServiceInstanceListeners existente no ficheiro pelo seguinte e guarde as alterações.

@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;
}

Adicionar o ficheiro HTTPCommunicationListener.java

O serviço de escuta de comunicação HTTP atua como um controlador que configura o servidor HTTP e expõe as APIs que definem as ações de voto. Clique com o botão direito do rato no pacote statelessservice na pasta VotingWeb/src/statelessservice e, em seguida, selecione Novo>Ficheiro. Atribua o nome HttpCommunicationListener.java ao ficheiro e selecione Concluir.

Substitua o conteúdo do ficheiro pelo seguinte e, em seguida, guarde as alterações. Mais adiante, em Atualizar o ficheiro HttpCommunicationListener.java, este ficheiro é modificado para compor, ler e escrever dados de voto do serviço back-end. Por agora, o serviço de escuta devolve simplesmente o HTML estático da aplicação de Voto.

// ------------------------------------------------------------
//  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();
    }
}

Configurar a porta de escuta

Quando o serviço front-end VotingWeb é criado, o Service Fabric seleciona uma porta na qual o serviço escuta. O serviço VotingWeb atua como front-end desta aplicação e aceita tráfego externo, pelo que vamos vincular este serviço a uma porta fixa e conhecida. No Explorador de Pacotes, abra VotingApplication/VotingWebPkg/ServiceManifest.xml. Localize o recurso ponto final na secção Recursos e altere o valor porta para 8080 (continuaremos a utilizar esta porta ao longo do tutorial). Para implementar e executar a aplicação localmente, a porta de escuta da aplicação tem de estar aberta e disponível no seu computador. Cole o seguinte fragmento de código no elemento ServiceManifest (imediatamente abaixo do elemento <DataPackage>).

<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>

Adicionar um serviço de back-end com monitorização de estado à sua aplicação

Agora que a estrutura do serviço de API Web em Java está concluída, vamos avançar e concluir o serviço back-end com estado.

O Service Fabric permite-lhe armazenar de forma consistente e fiável os seus dados diretamente dentro do seu serviço através das Reliable Collections. As coleções fiáveis são um conjunto de classes de coleção de elevada disponibilidade e fiáveis. A utilização destas classes é familiar para qualquer pessoa que tenha utilizado coleções de Java.

  1. No Explorador de Pacotes, clique com o botão direito do rato em Voto no projeto da aplicação e selecioneAdicionar Serviço do Service Fabric ao Service Fabric>.

  2. Na caixa de diálogo Adicionar Serviço , selecione Serviço com Monitorização de Estado e dê ao serviço o nome VotingDataService e selecione Adicionar Serviço.

    Uma vez criado o projeto de serviço, terá dois serviços na sua aplicação. À medida que continua a criar a sua aplicação, pode adicionar mais serviços com o mesmo método. Pode dar uma versão e atualizar cada serviço de forma independente.

  3. O Eclipse cria um projeto de serviço e apresenta-o no Package Explorer.

    Eclipse Project Explorer

Adicione o ficheiro VotingDataService.java

O ficheiro VotingDataService.java inclui os métodos que contêm a lógica para obter, adicionar e remover votos de coleções fiáveis. Adicione os seguintes métodos de classe VotingDataService ao ficheiro VotingDataService/src/statefulservice/VotingDataService.java.

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()));
    }

}

A estrutura do serviço front-end sem estado e do serviço back-end está agora criada.

Criar a interface de comunicação para a aplicação

O próximo passo é ligar o serviço sem estado de front-end e o serviço de back-end. Ambos os serviços utilizam uma interface denominada VotingRPC que define as operações da aplicação Voting. Esta interface é implementada por ambos os serviços front-end e back-end para ativar as chamadas de procedimento remoto (RPC) entre os dois serviços. Infelizmente, o Eclipse não suporta a adição de subprojetos Gradle, pelo que o pacote que contém esta interface tem de ser adicionado manualmente.

  1. Clique com o botão direito do rato no Projeto de votação no Explorador de Pacotes e selecione Nova>Pasta. Dê à pasta o nome VotingRPC/src/rpcmethods.

    Criar pacote VotingRPC no Explorador de Pacotes do Eclipse

  2. Crie um ficheiro em Voto/VotingRPC/src/rpcmethods com o nome VotingRPC.java e cole o seguinte no interior do ficheiro VotingRPC.java.

    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. Crie um ficheiro vazio com o nome build.gradle no diretório Voting/VotingRPC e cole o seguinte no mesmo. Este ficheiro gradle é utilizado para criar o ficheiro jar que é importado pelos outros serviços.

    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. No ficheiro Voto/settings.gradle, adicione uma linha para incluir o projeto recentemente criado na compilação.

    include ':VotingRPC'
    
  5. No ficheiro Voting/VotingWeb/src/statelessservice/HttpCommunicationListener.java, substitua o bloco de comentário pelo seguinte.

    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. Adicione a declaração de importação adequada à parte superior do ficheiro Voto/VotingWeb/src/statelessservice/HttpCommunicationListener.java.

    import rpcmethods.VotingRPC; 
    

Nesta fase, as funções das interfaces de front-end, back-end e RPC estão concluídas. A fase seguinte é configurar os scripts de Gradle corretamente antes de implementar um cluster do Service Fabric.

Percorrer a aplicação de votação de exemplo

A aplicação de votação é composta por dois serviços:

  • Serviço front-end para a Web (VotingWeb) - um serviço front-end para a Web em Java, que serve a página Web e expõe as APIs para comunicar com o serviço back-end.
  • Serviço back-end (VotingDataService) - um serviço Web em Java, que define os métodos que são invocados através de Chamadas de Procedimento Remoto (RPC) para manter os votos.

Diagrama de exemplo de votação

Quando efetua uma ação na aplicação (adicionar item, votar, remover item) ocorrem os seguintes eventos:

  1. Um JavaScript envia o pedido adequado para a API Web no serviço front-end para a Web como um pedido HTTP.

  2. O serviço front-end para a Web utiliza a funcionalidade incorporada de Comunicação Remota do Serviço do Service Fabric para localizar e reencaminhar o pedido para o serviço back-end.

  3. O serviço back-end define os métodos que atualizam o resultado num dicionário fiável. O conteúdo deste dicionário fiável é replicado para vários nós dentro do cluster e é mantido no disco. Todos os dados da aplicação são armazenados no cluster.

Configurar scripts de Gradle

Nesta secção, vai configurar os scripts de Gradle para o projeto.

  1. Substitua os conteúdos do ficheiroVoto/build.gradle pelo seguinte.

    apply plugin: 'java'
    apply plugin: 'eclipse'
    
    subprojects {
        apply plugin: 'java'
    }
    
    defaultTasks 'clean', 'jar', 'copyDeps'
    
  2. Substitua os conteúdos do ficheiro Voting/VotingWeb/build.gradle pelo seguinte.

    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. Substitua os conteúdos do ficheiro Voting/VotingDataService/build.gradle.

    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'
    

Implementar aplicação no cluster local

Neste momento, a aplicação está pronta para ser implementada num cluster do Service Fabric local.

  1. Clique com o botão direito do rato no projeto Voting no Explorador de Pacotes e selecioneAplicação de Compilação do Service Fabric> para criar a sua aplicação.

  2. Execute o seu cluster do Service Fabric local. Este passo depende do seu ambiente de desenvolvimento (Mac ou Linux).

    Se estiver a utilizar um Mac, execute o cluster local com o seguinte comando: substitua o comando transmitido no parâmetro -v pelo caminho para a sua área de trabalho.

    docker run -itd -p 19080:19080 -p 8080:8080 -p --name sfonebox mcr.microsoft.com/service-fabric/onebox:latest
    

    Veja instruções mais detalhadas no guia de configuração do OS X.

    Se estiver a executar num computador Linux, inicie o cluster local com o seguinte comando:

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

    Veja instruções mais detalhadas no guia de configuração do Linux.

  3. No Explorador de Pacotes para Eclipse, clique com o botão direito do rato no projeto De voto e selecionePublicar Aplicação do Service Fabric>

  4. Na janela Publicar Aplicação , selecione Local.json na lista pendente e selecione Publicar.

  5. Vá para o seu browser e aceda a http://localhost:8080 para ver a sua aplicação em execução no cluster do Service Fabric local.

Passos seguintes

Nesta parte do tutorial, ficou a saber como:

  • Criar o serviço Java como um serviço fiável com estado
  • Criar o serviço Java como um serviço Web sem estado
  • Adicionar uma interface de Java para processar as Chamadas de Procedimento Remoto (RPC) entre os serviços
  • Configurar os scripts de Gradle
  • Criar e implementar a aplicação num cluster do Service Fabric local

Avance para o tutorial seguinte: