Delen via


Zelfstudie: Een toepassing maken met een Java API-front-endservice en een stateful back-endservice in Azure Service Fabric

Deze zelfstudie is deel één van een serie. Wanneer u klaar bent, hebt u een stemtoepassing met een Java-webfront-end waarmee stemresultaten worden opgeslagen in een stateful back-endservice in Azure Service Fabric. Voor deze reeks zelfstudies moet u een werkende Mac OSX- of Linux-ontwikkelcomputer hebben. Als u de stemtoepassing niet handmatig wilt maken, kunt u de broncode voor de voltooide toepassing downloaden en verdergaan naar Walk through the voting sample application. Overweeg ook de quickstart voor betrouwbare Java-services te volgen.

Voorbeeld van Service Fabric-stem

In deze zelfstudiecursus leer je hoe je:

In deel 1 van de reeks leert u het volgende:

  • Een betrouwbare stateful Java-service maken
  • Een stateless Java-webtoepassingsservice maken
  • Service remoting gebruiken om te communiceren met de stateful service
  • Toepassing implementeren op een lokaal Service Fabric-cluster

Vereiste voorwaarden

Voor u met deze zelfstudie begint:

  • Als u nog geen Azure-abonnement hebt, maakt u een gratis account.
  • Stel uw ontwikkelomgeving in voor Mac of Linux. Volg de instructies voor het installeren van de Eclipse-invoegtoepassing, Gradle, de Service Fabric SDK en de Service Fabric CLI (sfctl).

De front-end stateless Java-service creëren

Maak eerst de webfront-end van de stemtoepassing. Een webgebruikersinterface mogelijk gemaakt door AngularJS verzendt aanvragen naar de stateless Java-service, die een lichtgewicht HTTP-server uitvoert. Deze service verwerkt elke aanvraag en verzendt een externe procedure-aanroep naar de stateful service om de stemmen op te slaan.

  1. Open Eclipse.

  2. Maak een project met Bestand>Nieuw>Overige>Service Fabric>Service Fabric-project.

    Nieuw Service Fabric-project in Eclipse

  3. Geef in het dialoogvenster ServiceFabric Project Wizard het project de naam Voting en selecteer Volgende.

    Kiezen van de stateless Java-service in het nieuwe service venster

  4. Selecteer op de pagina Service toevoegende optie Stateless Service en geef uw service votingWeb een naam. Selecteer Voltooien om het project te maken.

    Een stateless service maken voor uw Service Fabric-project

    Eclipse maakt een toepassing en een serviceproject en geeft deze weer in Package Explorer.

    Eclipse Package Explorer na het maken van de toepassing

De tabel geeft een korte beschrijving van elk item in de pakketverkenner uit de vorige schermafbeelding.

Package Exploreritem Beschrijving
PublishProfiles Bevat JSON-bestanden met een beschrijving van profieldetails van lokale en Azure Service Fabric-clusters. De inhoud van deze bestanden wordt door de invoegtoepassing gebruikt bij het implementeren van de toepassing.
Scripten Bevat helperscripts die vanaf de opdrachtregel kunnen worden gebruikt om uw toepassing snel te beheren met een cluster.
StemApplicatie Bevat de Service Fabric-toepassing die naar het Service Fabric-cluster wordt gepusht.
VotingWeb Bevat de bronbestanden voor stateless services van de front-end, samen met het bijbehorende gradle-buildbestand.
build.gradle Gradle-bestand dat wordt gebruikt om het project te beheren.
settings.gradle Bevat namen van Gradle-projecten in deze map.

HTML en JavaScript toevoegen aan de VotingWeb-service

Als u een gebruikersinterface wilt toevoegen die kan worden weergegeven door de stateless service, voegt u een HTML-bestand toe. Dit HTML-bestand wordt vervolgens weergegeven door de lichtgewicht HTTP-server die is ingesloten in de stateless Java-service.

  1. Vouw de map VotingApplication uit om de map VotingApplication/VotingWebPkg/Code te bereiken.

  2. Klik met de rechtermuisknop op de Code directory en selecteer Nieuwe>Map.

  3. Geef de map de naam wwwroot en selecteer Voltooien.

    De map Wwwroot maken in Eclipse

  4. Voeg een bestand toe aan de wwwroot met de naam index.html en plak de volgende inhoud in dit bestand.

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

Het VotingWeb.java-bestand bijwerken

Open in het subproject VotingWeb/src/statelessservice/VotingWeb.java het bestand VotingWeb/src/statelessservice. De VotingWeb-service is de gateway in de staatloze service en is verantwoordelijk voor het instellen van de communicatielistener voor de front-end-API.

Vervang de bestaande methode createServiceInstanceListeners in het bestand door het volgende en sla uw wijzigingen op.

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

Het HTTPCommunicationListener.java-bestand toevoegen

De HTTP-communicatielistener fungeert als een controller waarmee de HTTP-server wordt ingesteld en de API's beschikbaar worden gesteld die stemacties definiëren. Klik met de rechtermuisknop op het statelessservice-pakket in de map VotingWeb/src/statelessservice en selecteer Nieuw>Bestand. Geef het bestand een naam HttpCommunicationListener.java en selecteer Voltooien.

Vervang de bestandsinhoud door het volgende en sla de wijzigingen op. Later, in de sectie "Bijwerken van het HttpCommunicationListener.java-bestand", wordt dit bestand aangepast om stemgegevens van de back-endservice weer te geven, te lezen en te schrijven. Voorlopig retourneert de listener gewoon de statische HTML voor de stem-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();
    }
}

De luisterpoort configureren

Wanneer de front-endservice van de VotingWeb-service wordt gemaakt, selecteert Service Fabric een poort voor de service om op te luisteren. De VotingWeb-service fungeert als de front-end voor deze toepassing en accepteert extern verkeer. Laten we die service dus binden aan een vaste en bekende poort. Open VotingApplication/VotingWebPkg/ServiceManifest.xmlin Package Explorer. Zoek de eindpuntresource in de sectie Resources en wijzig de poortwaarde in 8080 (we blijven deze poort in de hele zelfstudie gebruiken). Als u de toepassing lokaal wilt implementeren en uitvoeren, moet de luisterende poort van de toepassing zijn geopend en beschikbaar zijn op uw computer. Plak het volgende codefragment in het Element ServiceManifest (bijvoorbeeld net onder het <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>

Een stateful back-end service toevoegen aan uw applicatie

Nu het skelet van de Java Web API-service is voltooid, gaan we verder met het voltooien van de stateful back-endservice.

Met Service Fabric kunt u uw gegevens consistent en betrouwbaar opslaan in uw service met behulp van betrouwbare verzamelingen. Betrouwbare verzamelingen zijn een set maximaal beschikbare en betrouwbare verzamelingsklassen. Het gebruik van deze klassen is bekend voor iedereen die Java-verzamelingen heeft gebruikt.

  1. Klik in Package Explorer met de rechtermuisknop op Voting in het toepassingsproject en selecteer Service Fabric>Add Service Fabric Service.

  2. Selecteer stateful service in het dialoogvenster Service toevoegen en geef de service de naam VotingDataService en selecteer Service toevoegen.

    Zodra uw serviceproject is gemaakt, hebt u twee services in uw toepassing. Wanneer u uw toepassing blijft bouwen, kunt u meer services op dezelfde manier toevoegen. Elk onderdeel kan onafhankelijk worden voorzien van een nieuwe versie en bijgewerkt.

  3. Eclipse maakt een serviceproject en geeft het weer in Package Explorer.

    Eclipse Project Explorer

Het VotingDataService.java-bestand toevoegen

Het bestand VotingDataService.java bevat de methoden die logica bevatten voor het ophalen, toevoegen en verwijderen van stemmen uit de betrouwbare verzamelingen. Voeg de volgende VotingDataService-klassemethoden toe aan het bestand 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()));
    }

}

Het skelet voor de stateless front-endservice en de back-endservice wordt nu gemaakt.

De communicatie-interface voor uw toepassing maken

De volgende stap is het verbinden van de stateless frontendservice en de backendservice. Beide services maken gebruik van een interface met de naam VotingRPC die de bewerkingen van de stemtoepassing definieert. Deze interface wordt geïmplementeerd door zowel de front-end- als back-endservices om externe procedureaanroepen (RPC) tussen de twee services in te schakelen. Helaas biedt Eclipse geen ondersteuning voor het toevoegen van Gradle-subprojecten, dus het pakket dat deze interface bevat, moet handmatig worden toegevoegd.

  1. Klik met de rechtermuisknop op het stemproject in Package Explorer en selecteer Nieuwe>map. Geef de map VotingRPC/src/rpcmethods een naam.

    VotingRPC-pakket maken in Eclipse Package Explorer

  2. Maak een bestand onder Voting/VotingRPC/src/rpcmethods met de naam VotingRPC.java en plak het volgende in het VotingRPC.java bestand.

    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. Maak een leeg bestand met de naam build.gradle in de map Voting/VotingRPC en plak het volgende erin. Dit gradle-bestand wordt gebruikt om het JAR-bestand te bouwen en te maken dat door de andere services wordt geïmporteerd.

    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. Voeg in het bestand Voting/settings.gradle een regel toe om het zojuist gemaakte project in de build op te nemen.

    include ':VotingRPC'
    
  5. Vervang in het bestand Voting/VotingWeb/src/statelessservice/HttpCommunicationListener.java het opmerkingenblok door het volgende.

    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. Voeg de juiste importinstructie toe bovenaan het bestand Voting/VotingWeb/src/statelessservice/HttpCommunicationListener.java .

    import rpcmethods.VotingRPC; 
    

In deze fase zijn de functionaliteit voor de front-end-, back-end- en RPC-interfaces voltooid. In de volgende fase configureert u de Gradle-scripts op de juiste wijze voordat u implementeert in een Service Fabric-cluster.

De voorbeeldtoepassing voor stemmen doorlopen

De stemtoepassing bestaat uit twee services:

  • Web-front-endservice (VotingWeb):een Java-webfront-endservice die de webpagina dient en API's beschikbaar maakt om te communiceren met de back-endservice.
  • Back-endservice (VotingDataService) - Een Java-webservice, die methoden definieert die worden aangeroepen via Remote Procedure Calls (RPC) om stemmen te behouden.

Voorbeeld van een stemformulierdiagram

Wanneer u een actie uitvoert in de toepassing (item toevoegen, stemmen, item verwijderen) vinden de volgende gebeurtenissen plaats:

  1. Een JavaScript verzendt de juiste aanvraag naar de web-API in de webfront-endservice als een HTTP-aanvraag.

  2. De webfront-endservice maakt gebruik van de ingebouwde Service Remoting-functionaliteit van Service Fabric om de aanvraag te lokaliseren en door te sturen naar de back-endservice.

  3. De back-endservice definieert methoden waarmee het resultaat wordt bijgewerkt in een betrouwbare woordenlijst. De inhoud van deze betrouwbare woordenlijst wordt gerepliceerd naar meerdere knooppunten binnen het cluster en persistent op schijf. Alle gegevens van de toepassing worden opgeslagen in het cluster.

Gradle-scripts configureren

In deze sectie worden de Gradle-scripts voor het project geconfigureerd.

  1. Vervang de inhoud van het bestand Voting/build.gradle door het volgende.

    apply plugin: 'java'
    apply plugin: 'eclipse'
    
    subprojects {
        apply plugin: 'java'
    }
    
    defaultTasks 'clean', 'jar', 'copyDeps'
    
  2. Vervang de inhoud van het bestand Voting/VotingWeb/build.gradle door het volgende.

    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. Vervang de inhoud van het bestand 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'
    

Toepassing implementeren in lokaal cluster

Op dit moment is de toepassing klaar om te worden geïmplementeerd in een lokaal Service Fabric-cluster.

  1. Klik met de rechtermuisknop op het stemproject in Package Explorer en selecteer Service Fabric>Build Application om uw toepassing te bouwen.

  2. Voer uw lokale Service Fabric-cluster uit. Deze stap is afhankelijk van uw ontwikkelomgeving (Mac of Linux).

    Als u een Mac gebruikt, voert u het lokale cluster uit met de volgende opdracht: Vervang de opdracht die is doorgegeven aan de parameter -v door het pad naar uw eigen werkruimte.

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

    Zie meer gedetailleerde instructies in de os X-installatiehandleiding.

    Als u op een Linux-computer werkt, start u het lokale cluster met de volgende opdracht:

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

    Zie meer gedetailleerde instructies in de Installatiehandleiding voor Linux.

  3. Klik in Package Explorer voor Eclipse met de rechtermuisknop op het stemproject en selecteer Service Fabric> PublishApplication

  4. Selecteer in het venster Toepassing publicerenLocal.json in de vervolgkeuzelijst en selecteer Publiceren.

  5. Ga naar uw webbrowser en bezoek http://localhost:8080 om uw draaiende toepassing weer te geven op het lokale Service Fabric-cluster.

Volgende stappen

In dit deel van de zelfstudie hebt u geleerd hoe u het volgende kunt doen:

  • Een Java-service maken als een betrouwbare stateful service.
  • Een Java-service maken als een staatloze webservice
  • Een Java-interface toevoegen om de externe procedure-aanroepen (RPC) tussen uw services af te handelen
  • Uw Gradle-scripts configureren
  • Uw toepassing bouwen en implementeren in een lokaal Service Fabric-cluster

Ga verder naar de volgende handleiding: