Skip to content

14. [TD]: نمایانگی وب لایه [metier]

کلیدواژه‌ها: معماری چندلایه، Spring، تزریق وابستگی، سرویس وب / jSON، کلاینت/سرور.

بیایید به معماری فعلی برنامه TD بازگردیم:

ما قصد داریم این معماری را به شکل زیر تکامل دهیم:

به منظور در معرض قرار دادن رابط [IMetier] لایه کسب‌وکار در وب. برای این کار، از روش‌شناسی توصیف‌شده در بخش 13.5 پیروی خواهیم کرد.

14.1. Support

  

پروژه‌های این فصل را می‌توان در پوشه [support / chap-14] یافت.

14.2. پروژهٔ Eclipse برای لایهٔ [métier]

  

14.2.1. پیکربندی Maven

پروژه لایه [métier] یک پروژه Maven است که توسط فایل زیر [pom.xml] پیکربندی شده است:


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <modelVersion>4.0.0</modelVersion>
    <groupId>istia.st.elections</groupId>
    <artifactId>elections-metier-dao-spring-data</artifactId>
    <version>0.1.0</version>

    <!-- وابستگی‌ها -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.7.RELEASE</version>
    </parent>
    <dependencies>
        <!--لایه [DAO] -->
        <dependency>
            <groupId>istia.st.elections</groupId>
            <artifactId>elections-dao-spring-data-01</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <!-- اسپرینگ بوت -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- آزمایش Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <properties>
        <!-- برای همه چیز از UTF-8 استفاده کنید -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>
</project>
  • خطوط ۱۸–۲۲: وابستگی به لایه [DAO] که در بخش ۱۲ ساخته شده است؛
  • خطوط ۲۳–۳۴: وابستگی‌های مورد نیاز برای تست؛

14.2.2. پیکربندی Spring

  

پروژه لایه [métier] یک پروژه Spring است که توسط فایل زیر [MetierConfig] پیکربندی شده است:


package elections.metier.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;

import elections.dao.config.DaoConfig;

@Import({ DaoConfig.class })
@ComponentScan({ "elections.metier.service" })
public class MetierConfig {
}
  • ما در اینجا از نشانه [@Configuration] که کلاس را به عنوان یک کلاس پیکربندی Spring مشخص می‌کند، استفاده نمی‌کنیم. حضور anotationهای [@Import, @ComponentScan] به طور خودکار آن را به یک کلاس پیکربندی تبدیل می‌کند؛
  • خط ۸: ما فایل پیکربندی لایه [DAO] را وارد می‌کنیم. سپس به همهٔ بیین‌های تعریف‌شده در این فایل دسترسی داریم؛
  • خط ۹: بیین‌های بیشتری از Spring در پوشه [elections.metier.service] یافت می‌شوند؛

14.2.3. پیاده‌سازی لایه [métier]

  

پیاده‌سازی لایه [métier] همان‌طور که در بخش 8.5 تعریف شده است.

14.2.4. آزمایش لایه [métier]

  

کلاس تست همان است که در بخش 8.6 توصیف شده است.


وظیفه: پروژه لایه [métier] را پیاده‌سازی کرده و تست واحد آن را با موفقیت پشت سر بگذارید. آرشیو لایه را در مخزن محلی Maven ایجاد کنید (Run As / Maven / Install).


14.3. پروژهٔ Eclipse برای لایهٔ [web]

لایه وب یک لایه Spring است MVC:

پروژه اکلیپس ساختار زیر را دارد:

  • [Boot.java] کلاسی است که سرویس وب را راه‌اندازی می‌کند؛
  • [WebConfig.java] کلاس پیکربندی سرویس وب است؛
  • [Response.java] پاسخی است که توسط نمونه‌های مختلف URL از سرویس وب تولید می‌شود؛
  • [ElectionsController] کلاس پیاده‌سازی سرویس وب است؛

14.4. پیکربندی Maven

این پروژه یک پروژه Maven است که توسط فایل زیر [pom.xml] پیکربندی شده است:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>istia.st.elections</groupId>
    <artifactId>elections-webjson-metier-dao-spring-data</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <name>elections-webjson-metier-dao-spring-data</name>
    <description>couche métier exposée comme un service web / jSON</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.7.RELEASE</version>
    </parent>

    <dependencies>
        <!--لایه کسب‌وکار -->
        <dependency>
            <groupId>istia.st.elections</groupId>
            <artifactId>elections-metier-dao-spring-data</artifactId>
            <version>0.1.0</version>
        </dependency>
        <!-- لایه MVC -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>

</project>
  • خطوط ۱۹–۲۳: وابستگی به آرشیو لایه [métier]. این همان آرشیویی است که در پاراگراف ۱۴ ایجاد کردیم؛
  • خطوط ۲۵–۲۸: وابستگی برای برنامه Spring MVC;

14.5. پیکربندی Spring

 

کلاس [WebConfig] سرویس وب را پیکربندی می‌کند:


package elections.webjson.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Scope;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

import com.fasterxml.jackson.databind.ObjectMapper;

import elections.metier.config.MetierConfig;

@EnableWebMvc
@Import({ MetierConfig.class })
@ComponentScan({ "elections.webjson.service" })
public class WebConfig {
    // -------------------------------- [web] پیکربندی لایه
    @Autowired
    private ApplicationContext context;

    @Bean
    public DispatcherServlet dispatcherServlet() {
        DispatcherServlet servlet = new DispatcherServlet((WebApplicationContext) context);
        return servlet;
    }

    @Bean
    public ServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {
        return new ServletRegistrationBean(dispatcherServlet, "/*");
    }

    @Bean
    public EmbeddedServletContainerFactory embeddedServletContainerFactory() {
        return new TomcatEmbeddedServletContainerFactory("", 8080);
    }
    // jSON نگاشت‌کننده
    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public ObjectMapper jsonMapper() {
        return new ObjectMapper();
    }

}
  • هدف این پیکربندی در بخش 13.5.3.1 توضیح داده شد. ما فقط ویژگی‌های جدید را توضیح خواهیم داد:
  • خط ۲۲: فایل پیکربندی لایه [métier] برای استفاده از تمام بیین‌های آن وارد می‌شود؛
  • خط ۲۳: مشخص می‌کنیم که بیین‌های بیشتر در پوشه [elections.webjson.server.service] یافت خواهند شد؛

14.6. کلاس راه‌اندازی سرویس وب

 

کلاس [Boot] سرویس وب را به شرح زیر راه‌اندازی می‌کند:


package elections.webjson.boot;

import org.springframework.boot.SpringApplication;

import elections.webjson.config.WebConfig;

public class Boot {

    public static void main(String[] args) {
        SpringApplication.run(WebConfig.class, args);
    }
}
  • خط ۱۰: متد استاتیک [SpringApplication.run] از فایل پیکربندی [WebConfig] استفاده خواهد کرد. به دلیل انوتیشن [@EnableAutoConfigurationSpring Boot سرور Tomcat را راه‌اندازی کرده و سرویس وب را روی آن مستقر خواهد کرد؛

14.7. پاسخ از سرویس وب URL

 

تمام درخواست‌های URL به سرویس وب / jSON همان نوع پاسخ را بازمی‌گردانند:


package elections.webjson.service;

import java.util.List;

public class Response<T> {

    // ----------------- ویژگی‌ها
    //وضعیت عملیات
    private int status;
    // هرگونه پیام خطا
    private List<String> messages;
    // بدنه پاسخ
    private T body;

    // سازنده‌ها
    public Response() {

    }

    public Response(int status, List<String> messages, T body) {
        this.status = status;
        this.messages = messages;
        this.body = body;
    }

    // گیرنده و تنظیم‌کننده
...
}

این کلاس در بخش 13.5.5.3 معرفی و مورد بحث قرار گرفت.

14.8. پیاده‌سازی سرویس وب / jSON

 

سرویس وب / jSON توسط کلاس زیر [ElectionsController] پیاده‌سازی شده است:


package elections.webjson.service;

import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import elections.dao.entities.ElectionsConfig;
import elections.dao.entities.ElectionsException;
import elections.metier.service.IElectionsMetier;

@Controller
public class ElectionsController {

    //وابستگی‌های Spring
    @Autowired
    private ObjectMapper jsonMapper;

    @Autowired
    private IElectionsMetier metier;

    @RequestMapping(value = "/getElectionsConfig", method = RequestMethod.GET, produces = "application/json; charset=UTF-8")
    @ResponseBody
    public String getElectionsConfig() throws JsonProcessingException {
        // پاسخ
        Response<ElectionsConfig> response;
        try {
            response = new Response<>(0, null,
                    new ElectionsConfig(metier.getNbSiegesAPourvoir(), metier.getSeuilElectoral()));
        } catch (ElectionsException e1) {
            response = new Response<>(e1.getCode(), e1.getErreurs(), null);
        } catch (RuntimeException e2) {
            response = new Response<>(1000, getErreursForException(e2), null);
        }
        // پاسخ
        return jsonMapper.writeValueAsString(response);
    }

    @RequestMapping(value = "/getListesElectorales", method = RequestMethod.GET, produces = "application/json; charset=UTF-8")
    @ResponseBody
    public String getListesElectorales() throws JsonProcessingException {
        throw new UnsupportedOperationException("Not supported yet");
    }

    @RequestMapping(value = "/setListesElectorales", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8", produces = "application/json; charset=UTF-8")
    @ResponseBody
    public String setListesElectorales(HttpServletRequest request) throws JsonProcessingException {
        throw new UnsupportedOperationException("Not supported yet");
    }

    @RequestMapping(value = "/calculerSieges", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8", produces = "application/json; charset=UTF-8")
    @ResponseBody
    public String calculerSieges(HttpServletRequest request) throws JsonProcessingException {
        throw new UnsupportedOperationException("Not supported yet");
    }

    // متدهای خصوصی -----------------------------
    // فهرست پیام‌های خطا برای یک RuntimeException
    private List<String> getErreursForException(Exception e) {
        // فهرست پیام‌های خطا را برای استثنا بازیابی می‌کند
        Throwable cause = e;
        List<String> erreurs = new ArrayList<>();
        while (cause != null) {
            // پیام را تنها در صورتی بازیابی می‌کند که != null و خالی نباشد
            String message = cause.getMessage();
            if (message != null) {
                message = message.trim();
                if (message.length() != 0) {
                    erreurs.add(message);
                }
            }
            // علت بعدی
            cause = cause.getCause();
        }
        return erreurs;
    }

}

وظیفه: در ادامه آنچه در بخش 13.5.5 انجام شد، کد کلاس [ElectionsController] را تکمیل کنید.


یادداشت‌ها:

  • در اینجا هیچ فیلتری از نوع jSON وجود ندارد زیرا جداول [CONF] و [LISTES] توسط یک رابطه کلید خارجی به هم متصل نیستند، که این امر کد سرویس وب را به طور قابل توجهی ساده می‌کند؛
  • annotationهای مختلف مورد نیاز Spring را فراموش نکنید؛
  • متدهای URL بر اساس نام متدهای مرتبطشان نام‌گذاری خواهند شد؛
  • متد [setListeElectorales] با یک عملیات [POST] فراخوانی می‌شود. مقدار ارسال‌شده آرایه‌ای از لیست‌های رقیب (از نوع ListeElectorale[]) به همراه ویژگی‌های [sieges, voix, elimine] آن‌ها است که باید در پایگاه داده ذخیره شود. این متد در صورت عدم وقوع خطا، یک نوع [Response<Void>] با یک فیلد [status=0] را بازمی‌گرداند، در غیر این صورت چیزی دیگر را بازمی‌گرداند؛
  • متد [calculerSieges] با یک عملیات [POST] فراخوانی می‌شود. مقدار ارسال‌شده آرایه‌ای از فهرست‌های رقیب (از نوع ListeElectorale[]) به همراه ویژگی‌های [nom, voix] آن‌ها است. این متد یک نوع [Response<ListeElectorale[]>] را بازمی‌گرداند که بدنه آن را فهرست‌های انتخاباتی با فیلدهای [sieges, elimine] اولیه شده تشکیل می‌دهند؛

14.9. Tests

پس از راه‌اندازی سرویس وب، با استفاده از ابزار [Advanced Rest Client]، تست‌های زیر را انجام دهید تا اطمینان حاصل شود که سرویس وب به درستی کار می‌کند:

 

پاسخ jSON به درخواست قبلی به شرح زیر است: [1]:

1
2

در [2]، پاسخ را به کلیپ‌بورد کپی کنید، سپس آن را در هر ویرایشگر متنی [3] جای‌گذاری کنید:

مقدار فیلد [body] را جدا کنید و برای مثال، آراء را برای لیست‌ها تغییر دهید. در زیر، در [4]، آراء را برای همه لیست‌ها روی 100 تنظیم می‌کنیم:

اطمینان حاصل کنید که رشته شما jSON با [ et se termine par ] شروع می‌شود. این کاراکترها برای جدا کردن یک آرایه jSON استفاده می‌شوند. در [5]، رشته jSON را که در بالا نشان داده شده است، الصاق کنید. این مقدار برای URL بعدی ارسال خواهد شد. برای انجام این کار، باید متد HTTP [POST] [7] را انتخاب کنید.

  • در [6]، درخواست URL و [setListesElectorales]. این URL با استفاده از POST درخواست می‌شود. مقدار ارسال‌شده جدول jSON فهرست‌های رقابتی است که نتایج آن باید در پایگاه داده ثبت شود؛

نتیجه زیر به دست آمد:

 

میدان [status=0] نشان می‌دهد که هیچ خطایی رخ نداده است. برای تأیید این موضوع، فهرست مسابقات را مجدداً درخواست کنید و بررسی کنید که تغییراتی که در فهرست‌ها اعمال کرده‌اید، در نظر گرفته شده‌اند:

ما یک [POST] دیگر اجرا می‌کنیم تا کرسی‌های کسب‌شده توسط فهرست‌ها را محاسبه کنیم:

  • در [1]: URL از محاسبه تخصیص کرسی‌ها؛
  • به [2]: ما یک [POST] را اجرا می‌کنیم؛
  • در [3]: فهرست‌های رقیب. میدان [voix] با مقادیر TD تخصیص داده می‌شود؛ تمام میدان‌های [sieges] روی 0 تنظیم می‌شوند؛ تمام میدان‌های [elimine] روی false تنظیم می‌شوند؛

نتیجهٔ به‌دست‌آمده به شرح زیر است: