Skip to content

14. [TD]: Exposição na web da camada [metier]

Palavras-chave: arquitetura multicamadas, Spring, injeção de dependências, serviço web / jSON, cliente / servidor.

Voltemos à arquitetura atual da aplicação do TD:

Vamos evoluir essa arquitetura para a seguinte:

a fim de expor na web a interface [IMetier] da camada de negócios. Para isso, seguiremos a metodologia descrita no parágrafo 13.5.

14.1. Support

  

Os projetos deste capítulo estão localizados na pasta [support / chap-14].

14.2. O projeto Eclipse da camada [métier]

  

14.2.1. Configuração do Maven

O projeto da camada [métier] é um projeto Maven configurado pelo seguinte arquivo [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>

    <!-- dependências -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.7.RELEASE</version>
    </parent>
    <dependencies>
        <!-- camada [DAO] -->
        <dependency>
            <groupId>istia.st.elections</groupId>
            <artifactId>elections-dao-spring-data-01</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <!-- Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- Teste do Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <properties>
        <!-- usa UTF-8 para tudo -->
        <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>
  • linhas 18-22: a dependência da camada [DAO] criada no parágrafo 12;
  • linhas 23-34: as dependências necessárias para os testes;

14.2.2. Configuração do Spring

  

O projeto da camada [métier] é um projeto Spring configurado pelo seguinte arquivo [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 {
}
  • Aqui, não utilizamos a notação [@Configuration], que transforma a classe em uma classe de configuração do Spring. A presença das anotações [@Import, @ComponentScan] automaticamente a torna uma classe de configuração;
  • linha 8: importamos o arquivo de configuração da camada [DAO]. Assim, passamos a dispor de todos os beans definidos nesse arquivo;
  • linha 9: outros beans do Spring devem ser buscados na pasta [elections.metier.service];

14.2.3. Implementação da camada [métier]

  

A implementação da camada [métier] é a que foi definida no parágrafo 8.5.

14.2.4. Teste da camada [métier]

  

A classe de teste é a descrita no parágrafo 8.6.


Tarefa a ser realizada: implemente o projeto da camada [métier] e execute seu teste unitário. Gere o arquivo da camada no repositório Maven local (executar como / Maven / instalar).


14.3. O projeto Eclipse da camada [web]

A camada web é uma camada Spring MVC:

O projeto Eclipse possui a seguinte estrutura:

  • [Boot.java] é a classe que inicia o serviço web;
  • [WebConfig.java] é a classe de configuração do serviço web;
  • [Response.java] é a resposta gerada pelas diferentes instâncias de URL do serviço web;
  • [ElectionsController] é a classe de implementação do serviço web;

14.4. Configuração do Maven

O projeto é um projeto Maven configurado pelo seguinte arquivo [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>
        <!-- camada de negócios -->
        <dependency>
            <groupId>istia.st.elections</groupId>
            <artifactId>elections-metier-dao-spring-data</artifactId>
            <version>0.1.0</version>
        </dependency>
        <!-- camada 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>
  • linhas 19-23: a dependência do arquivo da camada [métier]. É a que criamos no parágrafo 14;
  • linhas 25-28: a dependência para ter uma aplicação Spring MVC;

14.5. Configuração do Spring

 

A classe [WebConfig] configura o serviço web:


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 {
    // -------------------------------- configuração da camada [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);
    }
    // mapeador jSON
    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public ObjectMapper jsonMapper() {
        return new ObjectMapper();
    }

}
  • O significado dessa configuração foi explicado no parágrafo 13.5.3.1. Explicaremos apenas as novidades:
  • linha 22: importa-se o arquivo de configuração da camada [métier] para utilizar todos os seus beans;
  • linha 23: indica-se que outros beans serão encontrados na pasta [elections.webjson.server.service];

14.6. A classe de inicialização do serviço web

 

A classe [Boot] inicia o serviço web da seguinte maneira:


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);
    }
}
  • linha 10: o método estático [SpringApplication.run] utilizará o arquivo de configuração [WebConfig]. Devido à anotação [@EnableAutoConfiguration], o Spring Boot iniciará o servidor Tomcat e implantará o serviço web nele;

14.7. A resposta do serviço web URL

 

Todas as solicitações URL do serviço web / jSON enviam o mesmo tipo de resposta:


package elections.webjson.service;

import java.util.List;

public class Response<T> {

    // ----------------- propriedades
    // status da operação
    private int status;
    // eventuais mensagens de erro
    private List<String> messages;
    // o corpo da resposta
    private T body;

    // construtores
    public Response() {

    }

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

    // getters e setters
...
}

Essa classe foi apresentada e estudada no parágrafo 13.5.5.3.

14.8. A implementação do serviço web / jSON

 

O serviço web / jSON é implementado pela seguinte classe [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 {

    // dependências do 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 {
        // resposta
        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);
        }
        // resposta
        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");
    }

    // métodos privados -----------------------------
    // lista de mensagens de erro de uma RuntimeException
    private List<String> getErreursForException(Exception e) {
        // recupera-se a lista de mensagens de erro da exceção
        Throwable cause = e;
        List<String> erreurs = new ArrayList<>();
        while (cause != null) {
            // recupera-se a mensagem somente se ela for !=null e não estiver vazia
            String message = cause.getMessage();
            if (message != null) {
                message = message.trim();
                if (message.length() != 0) {
                    erreurs.add(message);
                }
            }
            // causa seguinte
            cause = cause.getCause();
        }
        return erreurs;
    }

}

Tarefa a ser realizada: seguindo o que foi feito no parágrafo 13.5.5, complete o código da classe [ElectionsController].


Observações:

  • não há aqui filtros jSON, pois as tabelas [CONF] e [LISTES] não estão ligadas entre si por uma relação de chave estrangeira, o que simplifica consideravelmente o código do serviço web;
  • não se esqueça das diversas anotações do Spring necessárias;
  • atribuiremos aos URL os nomes dos métodos associados;
  • o método [setListeElectorales] é chamado com uma operação [POST]. O valor enviado é a matriz das listas em disputa (do tipo ListeElectorale[]) com seus atributos [sieges, voix, elimine], que devem ser gravados no banco de dados. Este método retorna um tipo [Response<Void>] com um campo [status=0] se não houver erros; caso contrário, retorna outro valor;
  • o método [calculerSieges] é chamado com uma operação [POST]. O valor enviado é a matriz das listas em disputa (do tipo ListeElectorale[]) com seus atributos [nom, voix]. Este método retorna um tipo [Response<ListeElectorale[]>] cujo corpo contém as listas eleitorais com seus campos [sieges, elimine] inicializados;

14.9. Tests

Após ter iniciado o serviço web, você deverá realizar os seguintes testes para garantir o bom funcionamento do serviço web com o utilitário [Advanced Rest Client]:

 

A resposta jSON à solicitação anterior é a seguinte: [1]:

1
2

Em [2], copie a resposta para a área de transferência e, em seguida, cole-a em qualquer editor de texto [3]:

Isole o valor do campo [body] e altere, por exemplo, os votos das listas. Abaixo, em [4], aumentamos para 100 os votos de todas as listas:

Verifique se sua sequência jSON começa com [ et se termine par ]. Esses caracteres servem para delimitar uma tabela jSON. Em [5], cole a sequência jSON acima. Esse será o valor lançado para o próximo URL. Para isso, é necessário selecionar o método HTTP [POST] [7].

  • no [6], solicite o URL [setListesElectorales]. Esse URL é solicitado com um POST. O valor lançado é a tabela jSON das listas em disputa, cujos resultados devem ser registrados no banco de dados;

Obtém-se o seguinte resultado:

 

O campo [status=0] indica que não houve erro. Para verificar isso, solicite novamente as listas em competição e verifique se as alterações que você fez nas listas foram consideradas:

Executamos novamente um [POST] para calcular os assentos obtidos pelas listas:

  • em [1]: o URL do cálculo dos assentos;
  • em [2]: executamos um [POST];
  • em [3]: as listas concorrentes. Atribuímos ao campo [voix] os valores do TD; todos os [sieges] ficam em 0; todos os campos [elimine] ficam em false;

O resultado obtido é o seguinte: