共用方式為


教學課程:在 Azure Service Fabric 上使用 Java API 前端服務和具狀態後端服務建立應用程式

本教學課程是一個系列的第一部分。 完成時,您會有一個具有 Java Web 前端的投票應用程式,可將投票結果儲存在 Azure Service Fabric 上的具狀態後端服務中。 本教學課程系列要求您擁有可運作的 Mac OSX 或 Linux 開發人員電腦。 如果您不想手動建立投票應用程式,您可以 下載已完成應用程式的原始程式碼 ,然後跳至 逐步解說投票範例應用程式。 此外,請考慮遵循 Java 可靠服務快速入門

Service Fabric 投票範例

在本教學課程系列中,您將瞭解如何:

在系列的第一部分中,您將了解如何:

  • 建立具狀態的 Java 可靠服務程式
  • 建立 Java 無狀態 Web 應用程式服務
  • 使用服務遠端處理來與具狀態服務進行通訊
  • 在本機 Service Fabric 叢集上部署應用程式

先決條件

開始進行本教學課程之前:

  • 如果您沒有 Azure 訂閱,請建立免費帳戶
  • 設定 MacLinux 的開發環境。 請遵循指示來安裝 Eclipse 外掛程式、Gradle、Service Fabric SDK 和 Service Fabric CLI (sfctl)。

建立前端 Java 無狀態服務

首先,建立投票應用程式的 Web 前端。 由 AngularJS 提供支援的 Web UI 會將請求傳送至 Java 無狀態服務,該服務會執行輕量級 HTTP 伺服器。 此服務會處理每個要求,並將遠端程序呼叫傳送給具狀態服務以儲存選票。

  1. 開啟 Eclipse。

  2. 透過 [檔案]>[新增]>[其他]>[Service Fabric]>[Service Fabric 專案] 來建立專案。

    Eclipse 中的新 Service Fabric 專案

  3. [ServiceFabric 專案精靈] 對話方塊中,將 [專案投票] 命名為 [下一步]。

    在新服務對話框中選擇 Java 無狀態服務

  4. 在 [ 新增服務 ] 頁面上,選取 [無狀態服務],並將您的服務命名為 VotingWeb。 選取 完成 以建立專案。

    為您的 Service Fabric 專案建立無狀態服務

    Eclipse 會建立應用程式及服務專案,並在「套件瀏覽器」中顯示它們。

    應用程式建立後的 Eclipse 套件瀏覽器

下表提供上一個螢幕擷圖中套件瀏覽器中每個項目的簡短描述。

套件總管項目 說明
PublishProfiles 包含描述本機和 Azure Service Fabric 叢集設定檔詳細資料的 JSON 檔案。 這些檔案的內容會由外掛程式在部署應用程式時使用。
劇本 包含可從命令列使用的協助程式指令碼,以快速管理叢集的應用程式。
投票應用 包含已推送至 Service Fabric 叢集的 Service Fabric 應用程式。
投票網 包含前端無狀態服務來源檔案以及相關的 gradle 建構檔案。
build.gradle Gradle 檔案,用於管理專案。
settings.gradle 包含此資料夾中的 Gradle 專案名稱。

將 HTML 和 JavaScript 新增至 VotingWeb 服務

若要新增可由無狀態服務轉譯的 UI,請新增 HTML 檔案。 然後,此 HTML 檔案會由內嵌在無狀態 Java 服務中的輕量型 HTTP 伺服器轉譯。

  1. 展開 VotingApplication 目錄以到達 VotingApplication/VotingWebPkg/Code 目錄。

  2. 右鍵單擊 Code 目錄並選擇 >資料夾

  3. 將資料夾命名為 wwwroot,然後選取 [完成]。

    Eclipse 建立 wwwroot 資料夾

  4. 將一個名為 index.html 的檔案新增至 wwwroot,並將以下內容貼到此檔案中。

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

更新VotingWeb.java檔案

VotingWeb 子專案中,開啟 VotingWeb/src/statelessservice/VotingWeb.java 檔案。 VotingWeb 服務是進入無狀態服務的閘道,負責設定前端 API 的通訊接聽程式。

將檔案中現有的 createServiceInstanceListeners 方法取代為下列方法,並儲存您的變更。

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

新增HTTPCommunicationListener.java檔案

HTTP 通訊接聽器可作為控制器,設定 HTTP 伺服器並公開定義投票動作的 API。 以滑鼠右鍵按一下 VotingWeb/src/statelessservice 資料夾中的 statelessservice 套件,然後選取 [新增>檔案]。 將檔案命名為HttpCommunicationListener.java,然後選取 [完成]。

將檔案內容取代為下列內容,然後儲存您的變更。 稍後,在更新HttpCommunicationListener.java檔案中,會修改此檔案,以從後端服務轉譯、讀取及寫入投票資料。 目前,監聽器只會傳回投票應用程式的靜態 HTML。

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

設定接聽埠

建立 VotingWeb 前端服務時,Service Fabric 會選擇一個埠以供服務監聽。 VotingWeb 服務充當此應用程式的前端並接受外部流量,因此讓我們將該服務綁定到固定且眾所周知的連接埠。 在 [套件總管] 中,開啟 VotingApplication/VotingWebPkg/ServiceManifest.xml。 在 [資源] 區段中尋找端點資源,並將 [埠] 值變更為 8080 (我們將在整個教學課程中繼續使用此埠)。 若要在本機部署和執行應用程式,應用程式接聽埠必須在您的電腦上開啟且可用。 將下列程式碼片段貼到 ServiceManifest 元素中 (也就是元素正下方 <DataPackage> )。

<Resources>
    <Endpoints>
        <!-- This endpoint is used by the communication listener to obtain the port on which to
            listen. Please note that if your service is partitioned, this port is shared with
            replicas of different partitions that are placed in your code. -->
        <Endpoint Name="WebEndpoint" Protocol="http" Port="8080" />
    </Endpoints>
  </Resources>

在您的應用程式中新增狀態後端服務

現在 Java Web API 服務的框架已經完成,我們繼續完成有狀態的後端服務吧。

Service Fabric 可讓您使用可靠的集合,在服務內一致且可靠地儲存資料。 可靠集合是一組高度可用且可靠的集合類別。 這些類別的用法對於任何使用過 Java 集合的人來說都很熟悉。

  1. 在 [套件總管] 中,以滑鼠右鍵按一下應用程式專案內的 [投票],然後選取 [Service Fabric]> [新增 Service Fabric 服務]。

  2. [ 新增服務 ] 對話方塊中,選取 [ 可設定狀態的服務 ] ,並將服務命名為 [ VotingDataService ] ,然後選取 [ 新增服務]。

    建立服務專案之後,您的應用程式中就會有兩個服務。 當您繼續建置應用程式時,您可以以相同的方式新增更多服務。 每個版本都可以獨立版本化和升級。

  3. Eclipse 會建立服務專案,並在「套件總管」中顯示它。

    Eclipse 專案瀏覽器

新增VotingDataService.java檔案

VotingDataService.java檔案包含包含邏輯的方法,可從可靠集合擷取、新增及移除投票。 將下列 VotingDataService 類別方法新增至 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()));
    }

}

現在已建立前端無狀態服務和後端服務的骨架。

建立應用程式的通訊介面

下一步是連接前端無狀態服務和後端服務。 這兩項服務都使用稱為 VotingRPC 的介面來定義 Voting 應用程式的作業。 此介面是由前端和後端服務實作,以啟用兩個服務之間的遠端程式呼叫 (RPC)。 不幸的是,Eclipse 不支援新增 Gradle 子專案,因此必須手動新增包含此介面的套件。

  1. 在套件總管 中的投票專案 上按一下滑鼠右鍵,然後選取 [新增>資料夾]。 將資料夾命名為 VotingRPC/src/rpcmethods

    在 Eclipse 套件總管中建立 VotingRPC 套件

  2. Voting/VotingRPC/src/rpcmethods 下建立名為 VotingRPC.java 的檔案,並將下列內容貼到 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. Voting/VotingRPC 目錄中建立一個名為 build.gradle 的空檔案,並將以下內容貼到其中。 這個 gradle 檔案可用來建構及建立其他服務匯入的 jar 檔案。

    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. Voting/settings.gradle 檔案中,新增一行,以將新建立的專案包含在建構中。

    include ':VotingRPC'
    
  5. Voting/VotingWeb/src/statelessservice/HttpCommunicationListener.java 檔案中,將註解區塊取代為下列內容。

    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. Voting/VotingWeb/src/statelessservice/HttpCommunicationListener.java 檔案頂端新增適當的匯入陳述式。

    import rpcmethods.VotingRPC; 
    

在此階段,前端、後端和 RPC 介面的功能已完成。 下一個階段是在部署至 Service Fabric 叢集之前,適當地設定 Gradle 腳本。

帶您瀏覽投票範例應用程式

投票應用程式包含兩項服務:

  • Web前端服務(VotingWeb)- 一種Java Web前端服務,它服務於網頁並公開API以與後端服務進行通訊。
  • 後端服務 (VotingDataService) - Java Web 服務,它定義透過遠端程序呼叫 (RPC) 呼叫的方法,以保存投票。

投票範例圖

當您在應用程式中執行動作 (新增項目、投票、移除項目) 時,會發生下列事件:

  1. JavaScript 會將適當的要求傳送至 Web 前端服務中的 Web API 作為 HTTP 要求。

  2. Web 前端服務會使用 Service Fabric 的內建服務遠端功能來尋找要求並將其轉送至後端服務。

  3. 後端服務會定義在可靠字典中更新結果的方法。 此可靠字典的內容會複寫到叢集內的多個節點,並保存在磁碟上。 應用程式的所有資料都儲存在叢集中。

設定 Gradle 指令碼

本節會設定專案的 Gradle 指令碼。

  1. Voting/build.gradle 檔案的內容取代為下列內容。

    apply plugin: 'java'
    apply plugin: 'eclipse'
    
    subprojects {
        apply plugin: 'java'
    }
    
    defaultTasks 'clean', 'jar', 'copyDeps'
    
  2. Voting/VotingWeb/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/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. 取代 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'
    

將應用程式部署至本機叢集

此時,應用程式已準備好部署至本機 Service Fabric 叢集。

  1. 以滑鼠右鍵按一下 [套件總管] 中的 [投票 ] 專案,然後選取 [ Service Fabric>建置應用程式 ] 以建置您的應用程式。

  2. 執行本機「Service Fabric」叢集。 此步驟取決於您的開發環境(Mac 或 Linux)。

    使用 Mac 的話,請使用下列命令來啟動本機叢集:將傳遞到 -v 參數的命令替換為您自己的工作區路徑。

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

    請參閱 OS X 設定指南中的更詳細指示。

    如果您在 Linux 機器上執行,請使用下列命令啟動本機叢集:

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

    請參閱 Linux 安裝指南中的更詳細說明。

  3. 在 Eclipse 的套件總管中,以滑鼠右鍵按一下 [投票] 專案,然後選取 [Service Fabric>發佈應用程式]

  4. 「發佈應用程式」視窗中,從下拉式清單中選取 Local.json,然後選取「發佈」。

  5. 移至您的網頁瀏覽器並存取 http://localhost:8080 ,以檢視本機 Service Fabric 叢集上執行中的應用程式。

後續步驟

在本教學課程的這一部分中,您已瞭解如何:

  • 將 Java 服務建立為有狀態的可靠服務
  • 將 Java 服務建立為無狀態 Web 服務
  • 新增 Java 介面,以處理服務之間的遠端程序呼叫 (RPC)
  • 設定 Gradle 指令碼
  • 建置應用程式並將其部署至本機 Service Fabric 叢集

前進到下一個教學課程: