Bagikan melalui


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

Tutorial ini adalah bagian pertama dari seri. Setelah selesai, Anda memiliki aplikasi Voting dengan front end web Java yang menyimpan hasil pemungutan suara dalam 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 pemungutan suara secara manual, Anda dapat mengunduh kode sumber untuk aplikasi yang telah selesai dan melompat ke depan untuk Berjalan melalui aplikasi sampel pemungutan suara. Selain itu, pertimbangkan untuk mengikuti Panduan Cepat untuk layanan Java yang andal.

Sampel pemungutan suara Service Fabric

Dalam tutorial ini, Anda akan belajar cara:

Di bagian salah satu seri, Anda belajar cara:

  • Membuat layanan Java stateful yang andal
  • Membuat layanan aplikasi web Java stateless
  • Gunakan remoting layanan untuk berkomunikasi dengan layanan stateful
  • Menyebarkan aplikasi pada kluster Service Fabric lokal

Prasyarat

Sebelum Anda memulai tutorial ini:

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

Membuat layanan stateless front-end Java

Pertama, buat tampilan depan web dari aplikasi Pemungutan Suara. 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 stateful untuk menyimpan suara.

  1. Buka Eclipse.

  2. Buat proyek dengan File>New>Lainnya>Service Fabric>Service Fabric Project.

    Proyek Service Fabric baru di Eclipse

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

    Memilih layanan stateless Java 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 Package Explorer.

    Eclipse Package Explorer setelah pembuatan aplikasi

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

Item Paket Eksplorasi Deskripsi
PublishProfiles Berisi file JSON yang menjelaskan detail profil kluster lokal dan Azure Service Fabric. Konten 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.
Aplikasi Pemungutan Suara Berisi aplikasi Service Fabric yang dideploy ke kluster Service Fabric.
VotingWeb Berisi file sumber dari layanan front-end stateless beserta file build gradle terkait.
build.gradle File Gradle yang digunakan untuk mengelola proyek.
settings.gradle Berisi nama proyek Gradle di folder ini.

Menambahkan HTML dan JavaScript ke layanan VotingWeb

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

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

  2. Klik kanan pada direktori Kode dan pilihFolderBaru>.

  3. Beri nama folder wwwroot dan pilih Selesai.

    Eclipse membuat folder wwwroot

  4. Tambahkan file ke wwwroot yang disebut index.html dan tempelkan isi 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 gerbang menuju layanan stateless dan bertanggung jawab untuk menyiapkan pengendali 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 mengekspos API yang menentukan tindakan pemungutan suara. Klik kanan pada paket statelessservice di folder VotingWeb/src/statelessservice, lalu pilihFileBaru>. 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 merender, membaca, dan menulis data pemungutan suara 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

Saat layanan front-end layanan VotingWeb dibuat, Service Fabric memilih port untuk didengarkan oleh layanan. Layanan VotingWeb bertindak sebagai ujung depan untuk aplikasi ini dan menerima lalu lintas eksternal, jadi mari kita ikat layanan tersebut ke port tetap dan terkenal. Di Package 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>

Tambahkan layanan back-end stateful ke aplikasi Anda

Sekarang, setelah kerangka layanan Java Web API selesai, mari kita lanjutkan untuk menyelesaikan layanan back-end yang stateful.

Service Fabric memungkinkan Anda untuk menyimpan data Anda secara konsisten dan andal tepat di dalam layanan Anda dengan menggunakan koleksi yang andal. Koleksi andal adalah sekumpulan kelas koleksi yang sangat tersedia dan andal. Penggunaan kelas-kelas ini akrab bagi siapa saja yang telah menggunakan koleksi Java.

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

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

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

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

    Eclipse Project Explorer

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 front-end stateless dan layanan backend sekarang telah dibuat.

Membuat antarmuka komunikasi ke aplikasi Anda

Langkah selanjutnya adalah menghubungkan layanan stateless front-end dan layanan backend. 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) 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 Voting di Package Explorer dan pilihFolderBaru>. Beri nama folder VotingRPC/src/rpcmethods.

    Membuat paket VotingRPC di Eclipse Package Explorer

  2. Buat file di bawah Voting/VotingRPC/src/rpcmethods bernama VotingRPC.java dan tempelkan yang berikut ini di dalam file 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. Buat file kosong bernama build.gradle di direktori Voting/VotingRPC dan tempelkan yang berikut ini di dalamnya. File gradle ini digunakan untuk membangun dan membuat file jar untuk 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. Dalam file Voting/settings.gradle , tambahkan baris untuk menyertakan proyek 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, backend, dan RPC selesai. Tahap selanjutnya adalah mengonfigurasi skrip Gradle dengan tepat sebelum menyebarkan ke kluster Service Fabric.

Jelajahi aplikasi sampel pemungutan suara

Aplikasi pemungutan suara terdiri dari dua layanan:

  • Layanan front-end web (VotingWeb)- Layanan front-end web Java yang melayani halaman web dan mengekspos API untuk berkomunikasi dengan layanan backend.
  • Layanan back-end (VotingDataService) - Layanan web Java, yang mendefinisikan metode yang dipanggil melalui Panggilan Prosedur Jarak Jauh (RPC) untuk menyimpan suara.

Diagram sampel pemungutan suara

Saat Anda melakukan tindakan di aplikasi (tambahkan item, pilih, hapus 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 Service Remoting bawaan Service Fabric untuk menemukan dan meneruskan permintaan ke layanan back-end.

  3. Layanan back-end mendefinisikan metode yang memperbarui hasilnya dalam kamus yang andal. Isi kamus yang andal ini direplikasi ke beberapa simpul 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 isi dari 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'
    

Menyebarkan aplikasi ke kluster lokal

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

  1. Klik kanan pada proyek Voting di Package Explorer dan pilih Service Fabric>Build Application 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 rinci dalam panduan penyiapan OS X.

    Jika Anda berjalan di komputer Linux, Anda memulai kluster lokal dengan perintah berikut:

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

    Lihat instruksi lebih rinci dalam panduan penyiapan Linux.

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

  4. Di jendela Terbitkan Aplikasi , pilih Local.json dari menu 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

Di bagian tutorial ini, Anda belajar cara:

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

Melanjutkan ke tutorial berikutnya: