Partilhar via


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

Este tutorial é a primeira parte de uma série. Quando terminar, terá um aplicativo de votação com um front-end web em Java que salva os resultados da votação em um serviço back-end com estado no Azure Service Fabric. Esta série de tutoriais requer que você tenha uma máquina de desenvolvimento Mac OSX ou Linux em funcionamento. Se você não quiser criar manualmente o aplicativo de votação, você pode baixar o código-fonte do aplicativo concluído e pular para Percorrer o aplicativo de exemplo de votação. Além disso, considere seguir o Guia de início rápido para serviços confiá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:

  • Crie um serviço Java com estado mantido e fiável
  • Criar um serviço de aplicação web Java sem estado
  • Usar a comunicação remota de serviço para se comunicar com o serviço com monitoração de estado
  • Implantar aplicativo em um cluster local do Service Fabric

Pré-requisitos

Antes de começar este tutorial:

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

Crie o serviço Java sem estado front-end

Primeiro, crie o front-end da Web do aplicativo de votação. Uma interface do usuário da Web alimentada por AngularJS envia solicitações para o serviço sem estado Java, que executa um servidor HTTP leve. Este serviço processa cada solicitação e envia uma chamada de procedimento remoto para o serviço stateful para armazenar os votos.

  1. Abra o Eclipse.

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

    Novo projeto do Service Fabric no Eclipse

  3. Na caixa de diálogo Assistente de Projeto do ServiceFabric , nomeie a Votação do Projeto e selecione Avançar.

    Escolhendo o serviço sem estado Java no novo diálogo de serviço

  4. Na página Adicionar Serviço , selecione Serviço sem Estado e nomeie 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 um aplicativo e um projeto de serviço e os exibe no Gerenciador de Pacotes.

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

A tabela fornece uma breve descrição de cada item no explorador de pacotes da captura de tela anterior.

Item do Explorador de Pacotes Descrição
PublishProfiles Contém arquivos JSON que descrevem detalhes de perfil de clusters locais e do Azure Service Fabric. O conteúdo desses arquivos é usado pelo plug-in ao implantar o aplicativo.
Scripts Contém scripts auxiliares que podem ser usados a partir da linha de comando para gerenciar rapidamente seu aplicativo com um cluster.
Aplicativo de Votação Contém a aplicação do Service Fabric que é enviada para o cluster do Service Fabric.
VotingWeb Contém os arquivos de origem do serviço sem estado front-end juntamente com o arquivo de compilação gradle relacionado.
build.gradle Arquivo Gradle usado para gerenciar o projeto.
configurações.gradle Contém nomes de projetos Gradle nesta pasta.

Adicionar HTML e JavaScript ao serviço VotingWeb

Para adicionar uma interface de utilizador que possa ser renderizada pelo serviço sem estado, adicione um ficheiro HTML. Este ficheiro HTML é então renderizado pelo servidor HTTP leve incorporado no serviço Java sem estado.

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

  2. Clique com o botão direito do mouse na pasta Código e selecione Nova>pasta.

  3. Nomeie a pasta wwwroot e selecione Concluir.

    Eclipse criar pasta wwwroot

  4. Adicione um arquivo ao wwwroot chamado index.html e cole o seguinte conteúdo nesse arquivo.

<!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 arquivo VotingWeb.java

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

Substitua o método createServiceInstanceListeners existente no arquivo pelo seguinte e salve 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 ouvinte de comunicação HTTP atua como um controlador que configura o servidor HTTP e expõe as APIs que definem as ações de votação. Clique com o botão direito do mouse no pacote statelessservice na pasta VotingWeb/src/statelessservice e selecione Novo>arquivo. Nomeie o arquivo HttpCommunicationListener.java e selecione Concluir.

Substitua o conteúdo do arquivo pelo seguinte e salve as alterações. Mais tarde, em Atualizar o arquivo HttpCommunicationListener.java, esse arquivo é modificado para renderizar, ler e gravar dados de votação do serviço back-end. Por enquanto, o ouvinte simplesmente retorna o HTML estático para o aplicativo de votação.

// ------------------------------------------------------------
//  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 do serviço VotingWeb é criado, o Service Fabric seleciona uma porta para o serviço escutar. O serviço VotingWeb atua como o front-end para este aplicativo e aceita tráfego externo, então vamos vincular esse serviço a uma porta fixa e bem conhecida. No Explorador de Pacotes, abra VotingApplication/VotingWebPkg/ServiceManifest.xml. Encontre o recurso Endpoint na seção Recursos e altere o valor da porta para 8080 (continuaremos usando essa porta ao longo do tutorial). Para implantar e executar o aplicativo localmente, a porta de escuta do aplicativo deve estar aberta e disponível no seu computador. Cole o seguinte trecho de código dentro do elemento ServiceManifest (ou seja, logo abaixo do <DataPackage> elemento ).

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

Adiciona um serviço back-end com monitoração de estado à tua aplicação

Agora que o esqueleto do serviço Java Web API está completo, vamos prosseguir e concluir o serviço de back-end com estado.

O Service Fabric permite que você armazene seus dados de forma consistente e confiável dentro do seu serviço usando coleções confiáveis. Coleções confiáveis são um conjunto de classes de coleção altamente disponíveis e confiáveis. O uso dessas classes é familiar para qualquer pessoa que tenha usado coleções Java.

  1. No 'Package Explorer', clique com o botão direito do rato em Votação dentro do projeto da aplicação e selecione Service Fabric>Adicionar Serviço do Service Fabric.

  2. Na caixa de diálogo Adicionar Serviço , selecione Serviço com Estado, nomeie o serviço VotingDataService e selecione Adicionar Serviço.

    Depois que seu projeto de serviço é criado, você tem dois serviços em seu aplicativo. À medida que você continua a criar seu aplicativo, você pode adicionar mais serviços da mesma maneira. Cada um pode ser versionado e atualizado de forma independente.

  3. O Eclipse cria um projeto de serviço e o exibe no Explorador de Pacotes.

    Explorador do Projeto Eclipse

Adicionar o ficheiro VotingDataService.java

O arquivo VotingDataService.java contém os métodos que contêm lógica para recuperar, adicionar e remover votos das coleções confiáveis. Adicione os seguintes métodos de classe VotingDataService ao arquivo 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()));
    }

}

O esqueleto para o serviço front-end sem estado e o serviço back-end já foi criado.

Crie a interface de comunicação para o seu aplicativo

A próxima etapa é conectar o serviço sem estado do front-end e o serviço de back-end. Ambos os serviços utilizam uma interface chamada VotingRPC que define as operações do aplicativo de votação. Essa interface é implementada pelos serviços front-end e back-end para habilitar chamadas de procedimento remoto (RPC) entre os dois serviços. Infelizmente, o Eclipse não suporta a adição de subprojetos Gradle, portanto, o pacote que contém essa interface deve ser adicionado manualmente.

  1. Clique com o botão direito do mouse no projeto Voting no Explorador de Pacotes e selecione Nova>Pasta. Nomeie a pasta VotingRPC/src/rpcmethods.

    Criar pacote VotingRPC no Explorador de Pacotes do Eclipse

  2. Crie um arquivo em Voting/VotingRPC/src/rpcmethods chamado VotingRPC.java e cole o seguinte dentro do arquivo 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 arquivo vazio chamado build.gradle no diretório Voting/VotingRPC e cole o seguinte dentro dele. Este arquivo gradle é usado para construir e criar o arquivo 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 arquivo Voting/settings.gradle , adicione uma linha para incluir o projeto recém-criado na compilação.

    include ':VotingRPC'
    
  5. No arquivo Voting/VotingWeb/src/statelessservice/HttpCommunicationListener.java , substitua o bloco de comentários 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 instrução de importação apropriada na parte superior do arquivo Voting/VotingWeb/src/statelessservice/HttpCommunicationListener.java .

    import rpcmethods.VotingRPC; 
    

Neste estágio, a funcionalidade para as interfaces front-end, back-end e RPC está completa. A próxima etapa é configurar os scripts do Gradle adequadamente antes de implantar em um cluster do Service Fabric.

Percorra o aplicativo de exemplo de votação

O pedido de voto consiste em dois serviços:

  • Web front-end service (VotingWeb) - Um serviço de front-end web em Java que serve a página da web e expõe APIs para se comunicar com o serviço de back-end.
  • Serviço de back-end (VotingDataService) - Um serviço web em Java, que define métodos que são chamados remotamente via RPC para armazenar votos.

Diagrama de amostra de votação

Quando você executa uma ação no aplicativo (adicionar item, votar, remover item) ocorrem os seguintes eventos:

  1. Um JavaScript envia a solicitação apropriada para a API da Web no serviço front-end da Web como uma solicitação HTTP.

  2. O serviço front-end da Web usa a funcionalidade interna de comunicação remota do Service Fabric para localizar e encaminhar a solicitação para o serviço back-end.

  3. O serviço back-end define métodos que atualizam o resultado em um dicionário confiável. O conteúdo desse dicionário confiável é replicado para vários nós dentro do cluster e persiste no disco. Todos os dados do aplicativo são armazenados no cluster.

Configurar scripts Gradle

Nesta seção, os scripts Gradle para o projeto são configurados.

  1. Substitua o conteúdo do arquivo Voting/build.gradle pelo seguinte.

    apply plugin: 'java'
    apply plugin: 'eclipse'
    
    subprojects {
        apply plugin: 'java'
    }
    
    defaultTasks 'clean', 'jar', 'copyDeps'
    
  2. Substitua o conteúdo do arquivo 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 o conteúdo do arquivo 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'
    

Implantar aplicativo no cluster local

Neste ponto, o aplicativo está pronto para ser implantado em um cluster local do Service Fabric.

  1. Clique com o botão direito do mouse no projeto de votação no Explorador de Pacotes e selecione Aplicativo de compilação do Service Fabric> para criar seu aplicativo.

  2. Execute seu cluster local do Service Fabric. Esta etapa depende do seu ambiente de desenvolvimento (Mac ou Linux).

    Se você estiver usando um Mac, execute o cluster local com o seguinte comando: Substitua o comando passado para o parâmetro -v pelo caminho para seu próprio espaço de trabalho.

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

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

    Se você estiver executando em uma máquina 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 mouse no projeto de votação e selecione Aplicativo de publicação do Service Fabric>

  4. Na janela Publicar aplicativo , selecione Local.json na lista suspensa e selecione Publicar.

  5. Vá para o navegador da Web e acesse http://localhost:8080 para exibir seu aplicativo em execução no cluster local do Service Fabric.

Próximos passos

Nesta parte do tutorial, você aprendeu como:

  • Criar serviço Java como um serviço confiável com estado
  • Criar um serviço Java como um serviço web sem estado
  • Adicione uma interface Java para lidar com as chamadas de procedimento remoto (RPC) entre seus serviços
  • Configure os seus scripts Gradle
  • Crie e implante seu aplicativo em um cluster local do Service Fabric

Avance para o tutorial seguinte: