Tutorial: Membuat aplikasi dengan layanan front-end Java API dan layanan back-end yang stateful pada Azure Service Fabric

Tutorial ini adalah bagian pertama dari sebuah rangkaian. Setelah selesai, Anda memiliki aplikasi Voting dengan front-end web Java yang menyimpan hasil voting di layanan back-end stateful di Azure Service Fabric. Seri tutorial ini mengharuskan Anda memiliki mesin pengembang Mac OSX atau Linux yang berfungsi. Jika Anda tidak ingin membuat aplikasi voting secara manual, Anda dapat mengunduh kode sumber untuk aplikasi yang sudah selesai dan melompat ke depan untuk melalui aplikasi sampel voting. Juga, pertimbangkan untuk mengikuti mulai cepat untuk layanan yang dapat diandalkan Java..

Sampel voting Service Fabric

Dalam tutorial ini, Anda akan belajar cara:

Di bagian salah satu seri, Anda belajar cara:

  • Membuat layanan yang dapat diandalkan Java stateful
  • Membuat layanan aplikasi web tanpa status Java
  • Menggunakan remoting layanan untuk berkomunikasi dengan layanan yang stateful
  • Menyebarkan aplikasi pada kluster Service Fabric lokal

Prasyarat

Sebelum Anda memulai tutorial ini:

  • Jika Anda tidak memiliki langganan Azure, buat akun gratis sebelum memulai.
  • Siapkan lingkungan pengembangan Anda untuk Mac atau Linux. Ikuti petunjuk untuk memasang plug-in Eclipse, Gradle, Service Fabric SDK, dan Service Fabric CLI (sfctl).

Membuat layanan stateless Java front-end

Pertama, buat front end web aplikasi Voting. UI web yang didukung oleh AngularJS mengirimkan permintaan ke layanan stateless Java, yang menjalankan server HTTP ringan. Layanan ini memproses setiap permintaan dan mengirim panggilan prosedur jarak jauh ke layanan yang dinyatakan untuk menyimpan suara.

  1. Buka Eclipse.

  2. Buat proyek dengan File>Baru>LainnyaService >FabricService Fabric >Proyek.

    Proyek Service Fabric Baru di Eclipse

  3. Dalam dialog Wizard Proyek ServiceFabric, beri nama Proyek Voting dan pilih Berikutnya.

    Memilih layanan Java stateless dalam dialog layanan baru

  4. Pada halaman Tambahkan Layanan, pilih Layanan stateless, dan beri nama layanan Anda VotingWeb. Pilih Selesai untuk membuat proyek.

    Membuat layanan stateless untuk proyek Service Fabric Anda

    Eclipse membuat aplikasi dan proyek layanan dan menampilkannya di Explorer Paket.

    Explorer Paket Eclipse setelah pembuatan aplikasi

Tabel memberikan deskripsi singkat tentang setiap item di explorer paket dari cuplikan layar sebelumnya.

Item Explorer Paket Deskripsi
PublishProfiles Berisi file JSON yang menjelaskan detail profil kluster Lokal dan Azure Service Fabric. Isi file-file ini digunakan oleh plugin saat menyebarkan aplikasi.
Skrip Berisi skrip pembantu yang dapat digunakan dari baris perintah untuk mengelola aplikasi Anda dengan cepat dengan kluster.
VotingApplication Berisi aplikasi Service Fabric yang didorong ke kluster Service Fabric.
VotingWeb Berisi file sumber layanan stateless front-end bersama dengan file build gradle terkait.
build.gradle File Gradle digunakan untuk mengelola proyek.
settings.gradle Berisi nama proyek Gradle di folder ini.

Tambahkan HTML dan Javascript ke layanan VotingWeb

Untuk menambahkan UI yang bisa dirender oleh layanan stateless, tambahkan file HTML. File HTML ini kemudian dirender oleh server HTTP ringan yang tertanam ke dalam layanan Java stateless.

  1. Perluas direktori VotingApplication untuk mencapai direktori VotingApplication/VotingWebPkg/Code.

  2. Klik kanan pada direktori Kode dan pilih Folder>Baru.

  3. Beri nama folder wwwroot dan pilih Selesai.

    Eclipse membuat folder wwwroot

  4. Tambahkan file ke wwwroot yang disebut index.html dan tempelkan konten berikut ke dalam file ini.

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

Memperbarui file VotingWeb.java

Dalam subproyek VotingWeb, buka file votingWeb/src/statelessservice/VotingWeb.java. Layanan VotingWeb adalah gateway ke layanan stateless dan bertanggung jawab untuk mengatur pendengar komunikasi untuk API front-end.

Ganti metode createServiceInstanceListeners yang ada dalam file dengan yang berikut ini dan simpan perubahan Anda.

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

Menambahkan file HTTPCommunicationListener.java

Pendengar komunikasi HTTP bertindak sebagai pengontrol yang menyiapkan server HTTP dan memaparkan API yang mendefinisikan tindakan voting. Klik kanan pada paket statelessservice di folder VotingWeb/src/statelessservice, lalu pilih File>Baru. Beri nama file HttpCommunicationListener.java dan pilih Selesai.

Ganti konten file dengan yang berikut ini, lalu simpan perubahan Anda. Kemudian, dalam Memperbarui file HttpCommunicationListener.java, file ini dimodifikasi untuk menyajikan, membaca, dan menulis data voting dari layanan back-end. Untuk saat ini, pendengar hanya mengembalikan HTML statis untuk aplikasi Voting.

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

Mengonfigurasi port mendengarkan

Ketika layanan VotingWeb front-end service dibuat, Service Fabric memilih port untuk didengarkan layanan. Layanan VotingWeb bertindak sebagai ujung depan untuk aplikasi ini dan menerima lalu lintas eksternal, jadi mari kita ikat layanan itu ke port tetap dan terkenal. Di Paket Explorer, buka VotingApplication/VotingWebPkg/ServiceManifest.xml. Temukan sumber daya Titik akhir di bagian Sumber Daya dan ubah nilai Port menjadi 8080 (kami akan terus menggunakan port ini di seluruh tutorial). Untuk menyebarkan dan menjalankan aplikasi secara lokal, port mendengarkan aplikasi harus terbuka dan tersedia di komputer Anda. Tempelkan cuplikan kode berikut dalam elemen ServiceManifest (yaitu tepat di bawah <DataPackage> elemen).

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

Menambahkan layanan back-end yang dinyatakan ke aplikasi Anda

Sekarang kerangka layanan Java Web API selesai, mari kita lanjutkan dan selesaikan layanan back-end yang stateful.

Service Fabric memungkinkan Anda untuk menyimpan data secara konsisten dan andal tepat di dalam layanan Anda dengan menggunakan koleksi yang andal. Koleksi yang andal adalah satu set kelas koleksi yang sangat tersedia dan dapat diandalkan. Penggunaan kelas-kelas ini sudah tidak asing lagi bagi siapa saja yang telah menggunakan koleksi Java.

  1. Di Explorer Paket, klik kanan Voting dalam proyek aplikasi dan pilih Service Fabric>Tambahkan Fabric Service.

  2. Dalam dialog Tambahkan Layanan, pilih Layanan Stateful dan beri nama layanan VotingDataService dan pilih Tambahkan Layanan.

    Setelah proyek layanan Anda dibuat, Anda memiliki dua layanan di aplikasi Anda. Saat terus membangun aplikasi, Anda dapat menambahkan lebih banyak layanan dengan cara yang sama. Masing-masing dapat di versi independen dan ditingkatkan.

  3. Eclipse membuat proyek layanan dan menampilkannya di Explorer Paket.

    Explorer Proyek Eclipse

Menambahkan file VotingDataService.java

File VotingDataService.java berisi metode yang berisi logika untuk mengambil, menambahkan, dan menghapus suara dari koleksi yang dapat diandalkan. Tambahkan metode kelas VotingDataService berikut ke file 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()));
    }

}

Kerangka untuk layanan stateless front-end dan layanan back-end sekarang dibuat.

Membuat antarmuka komunikasi ke aplikasi Anda

Langkah selanjutnya adalah menghubungkan layanan stateless front-end dan layanan back-end. Kedua layanan menggunakan antarmuka yang disebut VotingRPC yang mendefinisikan operasi aplikasi Voting. Antarmuka ini diimplementasikan oleh layanan front-end dan back-end untuk mengaktifkan panggilan prosedur jarak jauh (RPC) di antara kedua layanan. Sayangnya, Eclipse tidak mendukung penambahan subproyek Gradle, sehingga paket yang berisi antarmuka ini harus ditambahkan secara manual.

  1. Klik kanan pada proyek Votingdi Explorer Paket dan pilihFolder>Baru. Beri nama folder VotingRPC/src/rpcmethods.

    Buat paket VotingRPC di Explorer Paket Eclipse

  2. Buat file di bawah Voting/VotingRPC/src/rpcmethods bernamaVotingRPC.java dan tempelkan hal berikut di dalam file .java VotingRPC.

    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. Buat file kosong bernama build.gradle di direktori Voting/VotingRPC dan tempelkan yang berikut ini di dalamnya. File gradle ini digunakan untuk membuat dan membuat file jar yang diimpor oleh layanan lain.

    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. Di file Voting/settings.gradle, tambahkan baris untuk menyertakan project yang baru dibuat dalam build.

    include ':VotingRPC'
    
  5. Dalam file Voting/VotingWeb/src/statelessservice/HttpCommunicationListener.java, ganti blok komentar dengan yang berikut ini.

    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. Tambahkan pernyataan impor yang sesuai di bagian atas file Voting/VotingWeb/src/statelessservice/HttpCommunicationListener.java.

    import rpcmethods.VotingRPC; 
    

Pada tahap ini, fungsionalitas untuk antarmuka front-end, back-end, dan RPC selesai. Tahap berikutnya adalah mengonfigurasi skrip Gradle dengan tepat sebelum menyebar ke kluster Service Fabric.

Mempelajari aplikasi contoh voting

Aplikasi voting terdiri atas dua layanan:

  • Layanan ujung depan web (VotingWeb) – Layanan front-end Java yang melayani halaman web dan memaparkan API web untuk berkomunikasi dengan layanan back-end.
  • Layanan back-end (VotingDataService) - Layanan web Java, yang menentukan metode yang dipanggil melalui Panggilan Prosedur Jarak Jauh (RPC) untuk bertahan memilih.

Diagram sampel voting

Saat Anda melakukan tindakan dalam aplikasi (menambahkan item, memilih, menghapus item) peristiwa berikut terjadi:

  1. JavaScript mengirimkan permintaan yang sesuai ke API web di layanan front-end web sebagai permintaan HTTP.

  2. Layanan front-end web menggunakan fungsionalitas Remoting Layanan bawaan Service Fabric untuk menemukan dan meneruskan permintaan ke layanan back-end.

  3. Layanan back-end menentukan metode yang memperbarui hasilnya dalam kamus yang dapat diandalkan. Isi kamus yang dapat diandalkan ini direplikasi ke beberapa node dalam kluster dan bertahan pada disk. Semua data aplikasi disimpan dalam kluster.

Mengonfigurasi skrip Gradle

Di bagian ini, skrip Gradle untuk proyek dikonfigurasi.

  1. Ganti konten file Voting/build.gradle dengan yang berikut ini.

    apply plugin: 'java'
    apply plugin: 'eclipse'
    
    subprojects {
        apply plugin: 'java'
    }
    
    defaultTasks 'clean', 'jar', 'copyDeps'
    
  2. Ganti konten file Voting/VotingWeb/build.gradle dengan yang berikut ini.

    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. Ganti konten file 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'
    

Terapkan aplikasi ke kluster lokal

Pada titik ini, aplikasi siap untuk disebarkan ke kluster Service Fabric lokal.

  1. Klik kanan pada proyek Voting di Explorer Paket dan pilih Service Fabric>Bangun Aplikasi untuk membangun aplikasi Anda.

  2. Jalankan kluster Service Fabric lokal Anda. Langkah ini tergantung pada lingkungan pengembangan Anda (Mac atau Linux).

    Jika Anda menggunakan Mac, Anda menjalankan kluster lokal dengan perintah berikut: Ganti perintah yang diteruskan ke parameter -v dengan jalur ke ruang kerja Anda sendiri.

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

    Lihat instruksi lebih terperinci dalam panduan penyiapan OS X.

    Jika Anda berjalan pada mesin Linux, Anda memulai kluster lokal dengan perintah berikut:

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

    Lihat instruksi lebih terperinci dalam panduan penyiapan Linux.

  3. Di Explorer Paket untuk Eclipse, klik kanan pada proyek Voting dan pilih Service Fabric>Terbitkan Aplikasi

  4. Di jendela Terbitkan Aplikasi, pilih Local.json dari dropdown, dan pilih Terbitkan.

  5. Buka browser web Anda dan akses http://localhost:8080 untuk melihat aplikasi Anda yang sedang berjalan di kluster Service Fabric lokal.

Langkah berikutnya

Dalam bagian tutorial ini, Anda belajar cara:

  • Membuat layanan Java sebagai layanan stateful yang dapat diandalkan
  • Membuat layanan Java sebagai layanan web stateless
  • Menambahkan antarmuka Java untuk menangani Panggilan Prosedur Jarak Jauh (RPC) di antara layanan Anda
  • Mengonfigurasi skrip Gradle Anda
  • Bangun dan sebarkan aplikasi Anda ke kluster Service Fabric lokal

Lanjutkan ke tutorial berikutnya: