البرنامج التعليمي: إنشاء تطبيق مع خدمة واجهة برمجة التطبيقات Java الأمامية وخدمة خلفية ذات حالة على Azure Service Fabric

هذا البحث هو الجزء الأول من سلسلة. عند الانتهاء، يكون لديك تطبيق Voting مع واجهة ويب Java التي تحفظ نتائج التصويت في خدمة خلفية ذات حالة على Azure Service Fabric. تتطلب هذه السلسلة التعليمية أن يكون لديك نظام التشغيل Mac OSX أو Linux developer machine. إذا كنت لا تريد إنشاء تطبيق التصويت يدوياً، يمكنك تنزيل الشفرة المصدرية للتطبيق المكتمل وتخطي التقدم إلى Walk خلال تطبيق نموذج التصويت. أيضاً، ضع في اعتبارك اتباع البداية السريعة لخدمات Java الموثوقة..

نموذج تصويت Service Fabric

ستتعلم في سلسلة البرامج التعليمية هذه كيفية:

في الجزء الأول من السلسلة، ستتعلم كيفية:

  • قم بإنشاء خدمة Java موثوق بها بدون حالة
  • قم بإنشاء خدمة تطبيق ويب Java دون حالة
  • استخدام الاتصال عن بعد بالخدمة للاتصال بخدمة ذات حالة
  • نشر التطبيق على مجموعة Service Fabric المحلية

المتطلبات الأساسية

قبل أن تبدأ هذا البرنامج التعليمي:

  • إذا لم يكن لديك اشتراك في Azure، فأنشئ حساباً مجانياً.
  • إعداد بيئة التطوير الخاصة بك لنظام التشغيل Mac أو Linux. اتبع الإرشادات لتثبيت البرنامج المساعد Eclipse، Gradle، Service Fabric SDK وService Fabric CLI (sfctl).

إنشاء خدمة Java دون حالة ذات واجهة أمامية

أولاً، إنشاء الواجهة الأمامية على الويب لتطبيق التصويت. ترسل واجهة مستخدم الويب المدعومة من AngularJS طلبات إلى خدمة Java دون حالة، والتي تشغل خادم HTTP خفيفاً. تعالج هذه الخدمة كل طلب وترسل طلب إجراء عن بعد إلى الخدمة ذات الحالة لتخزين الأصوات.

  1. قم بفتح Eclipse.

  2. أنشئ مشروعًا باستخدام ملف>ملف>جديد لـ>Service Fabric>مشروع Service Fabric .

    مشروع Service Fabric الجديد في Eclipse

  3. في مربع ServiceFabric Project Wizard قم بتسمية المشروع Voting وحددالتالي.

    اختيار خدمة Java عديمة الحالة في مربع الخدمة الجديدة

  4. في صفحة "Add Service" ، حدد "Stateless Service" ، وقم بتسمية الخدمة الخاصة بك VotingWeb. حدد "Finish" لإنشاء المشروع.

    إنشاء خدمة بدون حالة لمشروع

    يقوم Eclipse بإنشاء تطبيق ومشروع خدمة وعرضهما في Package Explorer.

    Eclipse Package Explorer بعد إنشاء التطبيق

يقدم الجدول وصفاً مختصراً لكل عنصر في Package Explorer من لقطة الشاشة السابقة.

Package Explorer Item الوصف
PublishProfiles يحتوي على ملفات JSON التي تصف تفاصيل ملف التعريف للمجموعات المحلية ومجموعاتAzure Service Fabric. يتم استخدام محتويات هذه الملفات من قبل البرنامج المساعد عند نشر التطبيق.
البرامج النصية يحتوي على نصوص برمجية مساعدة يمكن استخدامها من سطر الأوامر لإدارة تطبيقك بسرعة باستخدام مجموعة ما.
VotingApplication يحتوي على تطبيق "Service Fabric" الذي يتم دفعه إلى مجموعة "Service Fabric".
VotingWeb يحتوي على ملفات مصدر الخدمة عديم الحالة الأمامية مع إنشاء ملف gradle ذي الصلة.
إنشاء gradle. يستخدم ملف Gradle لإدارة المشروع.
إعدادات gradle. يحتوي على أسماء مشاريع Gradle في هذا المجلد.

أضف HTML وJavaScript إلى خدمة VotingWeb

لإضافة واجهة مستخدم يمكن تقديمها بواسطة خدمة عديمة الحالة، أضف ملف HTML. ثم يتم تقديم ملف HTML هذا بواسطة خادم HTTP الخفيف المضمن في خدمة Java عديمة الحالة.

  1. قم بتوسيع دليل VotingApplication للوصول إلى دليل VotingApplication/VotingWebPkg/Code.

  2. انقر بزر الماوس الأيمن فوق الدليل التعليمات البرمجية وحدد New>Folder.

  3. قم بتسمية المجلد wwwroot وحدد "Finish" .

    يقوم Eclipse بإنشاء ملف wwwroot

  4. قم بإضافة ملف إلى wwwroot يسمى index.html ولصق المحتويات التالية في هذا الملف.

<!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 هي بوابة الخدمة عديمة الحالة وهي مسؤولة عن إعداد وحدة الإصغاء للاتصالات لواجهة برمجة التطبيقات الأمامية.

استبدال أسلوب 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 وتعرض واجهات برمجة التطبيقات التي تحدد إجراءات التصويت. انقر بزر الماوس الأيمن فوق حزمة statelessservice في المجلد VotingWeb/src/statelessservice، ثم حدد "New">"File" . قم بتسمية الملف HttpCommunicationListener.java وحدد "Finish" .

استبدل محتويات الملف بما يلي، ثم احفظ تغييراتك. لاحقاً، في تحديث الملف 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 كواجهة أمامية لهذا التطبيق وتقبل حركة البيانات الخارجية، لذلك دعنا نربط هذه الخدمة بمنفذ ثابت ومعروف. في Package Explorer، افتح VotingApplication/VotingWebPkg/ServiceManifest.xml. ابحث عن مورد Endpoint في قسم الموارد وغير قيمة المنفذ إلى 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. في Package Explorer، انقر بزر الماوس الأيمن فوق التصويت ضمن مشروع التطبيق وحدد Service Fabric>وأضف خدمة Service Fabric.

  2. في مربع "Add Service" حدد "Stateful Service" وقم بتسمية الخدمة VotingDataService وحدد "Add Service" .

    بمجرد إنشاء مشروع الخدمة الخاص بك، يكون لديك خدمتان في تطبيقك. أثناء متابعة إنشاء التطبيق الخاص بك، يمكنك إضافة المزيد من الخدمات بنفس الطريقة. يمكن إصدار وتحديث كل منها بشكل مستقل.

  3. يقوم Eclipse بإنشاء مشروع خدمة ويعرضه في Package Explorer.

    Eclipse Project Explorer

إضافة ملف 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 تحدد عمليات تطبيق التصويت. يتم تطبيق هذه الواجهة من قِبَل كل من خدمات الواجهة الأمامية والجهة الخلفية لتمكين طلبات الإجراءات البعيدة (RPC) بين الخدمتين. لسوء الحظ، لا يدعم Eclipse إضافة المشاريع الفرعية Gradle، لذا يجب إضافة الحزمة التي تحتوي على هذه الواجهة بشكل يدوي.

  1. انقر بزر الماوس الأيمن فوق مشروع التصويت في Package Explorer وحدد New>Folder. قم بتسمية المجلد VotingRPC/src/rpcmethods.

    قم بإنشاء حزمة VotingRPC فيEclipse Package Explorer

  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. قم بإنشاء ملف فارغ اسمه build.gradle في الدليل Voting/VotingRPC ولصق ما يلي داخله. يتم استخدام ملف 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. المرحلة التالية هي تكوين البرامج النصية Gradle بشكل مناسب قبل نشرها إلى مجموعةService Fabric.

تصفح تطبيق نموذج التصويت

يتكون تطبيق التصويت من خدمتين:

  • خدمة الواجهة الأمامية للويب (VotingWeb) - خدمة واجهة ويب Java الأمامية التي تخدم صفحة الويب وتكشف واجهات برمجة التطبيقات للتواصل مع خدمة الواجهة الخلفية.
  • خدمة الواجهة الخلفية (VotingDataService) - خدمة ويب Java، والتي تعرف الطرق التي يتم طلبها عبر استدعاء الإجراءات عن بعد (RPC) لاستمرار التصويت.

رسم تخطيطي لعينة التصويت

عند تنفيذ إجراء في التطبيق (قم بإضافة وإزالة عنصر التصويت) تقع الأحداث التالية:

  1. يرسل JavaScript الطلب المناسب إلى واجهة برمجة تطبيقات الويب في خدمة واجهة ويب الأمامية كذلك طلب HTTP.

  2. تستخدم خدمة الشبكة الأمامية دالة "Service Remoting" المضمنة في 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. انقر بزر الماوس الأيمن فوق مشروع التصويت في Package Explorer وحدد Service Fabric>Build Application لإنشاء تطبيقك.

  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. في Package Explorer for Eclipse، انقر بزر الماوس الأيمن فوق مشروع Voting وحدد Service Fabric>Publish Application

  4. في نافذة Publish Application، حدد Local.json من القائمة المنسدلة، وحدد "Publish" .

  5. انتقل إلى متصفح الويب الخاص بك وقم بالوصول إلى http://localhost:8080 لعرض التطبيق قيد التشغيل على مجموعة Service Fabric المحلية.

الخطوات التالية

في هذا الجزء من البرنامج التعليمي، تعلمت كيفية:

  • إنشاء خدمة Java كخدمة موثوقة ذات حالة
  • إنشاء خدمة Java كخدمة ويب بدون حالة
  • إضافة واجهة Java لمعالجة استدعاءات الإجراءات عن بعد (RPC) بين خدماتك
  • كوّن البرامج النصية Gradle
  • إنشاء التطبيق ونشره إلى مجموعة "Service Fabric" محلية

تقدم إلى البرنامج التعليمي التالي: