JavaRush /Blogue Java /Random-PT /API REST e outra tarefa de teste.
Денис
Nível 37
Киев

API REST e outra tarefa de teste.

Publicado no grupo Random-PT
Parte I: Começando Por onde começar? Curiosamente, mas pelas especificações técnicas. É extremamente importante certificar-se de que, após ler os TOR enviados, você entenda perfeitamente o que está escrito nele e o que o cliente espera. Em primeiro lugar, isto é importante para uma implementação futura e, em segundo lugar, se não implementar o que se espera de si, isso não será vantajoso para si. Para evitar desperdício de ar, vamos esboçar uma especificação técnica simples. Então, quero um serviço para o qual eu possa enviar dados, eles serão armazenados no serviço e me serão devolvidos à vontade. Também preciso atualizar e excluir esses dados, se necessário . Algumas frases não parecem claras, certo? Como quero enviar dados para lá? Quais tecnologias usar? Em que formato esses dados estarão? Também não há exemplos de dados recebidos e enviados. Conclusão – a especificação técnica já é ruim . Vamos tentar reformular: precisamos de um serviço que possa processar solicitações HTTP e trabalhar com dados transferidos. Este será o banco de dados de registros pessoais. Teremos funcionários, eles estão divididos por departamentos e especialidades, os funcionários poderão ter tarefas atribuídas a eles. Nossa tarefa é automatizar o processo de contabilização de funcionários admitidos, demitidos, transferidos, bem como o processo de atribuição e cancelamento de tarefas utilizando a API REST. Na Fase 1, atualmente trabalhamos apenas com funcionários. O serviço deve ter vários endpoints para funcionar: - POST /employee - Solicitação POST, que deve aceitar um objeto JSON com dados do funcionário. Este objeto deve ser salvo no banco de dados; caso tal objeto já exista no banco de dados, as informações dos campos deverão ser atualizadas com novos dados. - GET /employee - Solicitação GET que retorna toda a lista de funcionários salva no banco de dados - DELETE - DELETE /employee para excluir um funcionário específico Modelo de dados do funcionário:
{
  "firstName": String,
  "lastName": String,
  "department": String,
  "salary": String
  "hired": String //"yyyy-mm-dd"
  "tasks": [
  	//List of tasks, not needed for Phase 1
  ]
}
Parte II: Ferramentas para o trabalho Então, o escopo do trabalho é mais ou menos claro, mas como vamos fazê-lo? Obviamente, tais tarefas no teste são dadas com alguns objetivos de aplicação, para ver como você codifica, para forçá-lo a usar o Spring e a trabalhar um pouco com o banco de dados. Bem, vamos fazer isso. Precisamos de um projeto SpringBoot com suporte a API REST e um banco de dados. No site https://start.spring.io/ você encontra tudo o que precisa. API REST ou outra tarefa de teste.  - 1 Você pode selecionar o sistema de compilação, idioma, versão do SpringBoot, definir configurações de artefato, versão Java e dependências. Clicar no botão Adicionar Dependências abrirá um menu de características com uma barra de pesquisa. Os primeiros candidatos para as palavras rest e data são Spring Web e Spring Data - iremos adicioná-los. Lombok é uma biblioteca conveniente que permite usar anotações para se livrar de quilômetros de código com métodos getter e setter. Ao clicar no botão Gerar receberemos um arquivo com o projeto que já pode ser descompactado e aberto em nosso IDE favorito. Por padrão, receberemos um projeto vazio, com um arquivo de configuração para o sistema de compilação (no meu caso será gradle, mas com o Maven as coisas não fazem diferença fundamental, e um arquivo de inicialização do spring). Pessoas atentas poderiam prestar atenção em duas API REST ou outra tarefa de teste.  - 2 coisas . Primeiro, tenho dois arquivos de configurações, application.properties e application.yml. Por padrão, você obterá exatamente propriedades - um arquivo vazio no qual você pode armazenar configurações, mas para mim o formato yml parece um pouco mais legível, agora vou mostrar uma comparação: Apesar da imagem API REST ou outra tarefa de teste.  -3 à esquerda parecer mais compacta , é fácil ver uma grande quantidade de duplicação no caminho das propriedades. A imagem à direita é um arquivo yml normal com uma estrutura em árvore bastante fácil de ler. Usarei esse arquivo posteriormente no projeto. A segunda coisa que quem está atento pode notar é que meu projeto já possui vários pacotes. Ainda não existe um código sensato, mas vale a pena examiná-los. Como um aplicativo é escrito? Tendo uma tarefa específica, devemos decompô-la - dividi-la em pequenas subtarefas e iniciar sua implementação sequencial. O que é exigido de nós? Precisamos fornecer uma API que o cliente possa usar; o conteúdo do pacote do controlador será responsável por esta parte da funcionalidade. A segunda parte da aplicação é o banco de dados – o pacote de persistência. Nele armazenaremos coisas como Entidades de Banco de Dados (Entidades), bem como Repositórios - interfaces especiais de primavera que permitem interagir com o banco de dados. O pacote de serviços conterá classes de serviço. Falaremos sobre o que é o serviço do tipo Spring a seguir. E por último mas não menos importante, o pacote utils. Ali serão armazenadas classes utilitárias com todos os tipos de métodos auxiliares, por exemplo, classes para trabalhar com data e hora, ou classes para trabalhar com strings, e sabe-se lá o que mais. Vamos começar a implementar a primeira parte da funcionalidade. Parte III: Controlador
@RestController
@RequestMapping("${application.endpoint.root}")
@RequiredArgsConstructor
public class EmployeeController {

    private final EmployeeService employeeService;

    @GetMapping("${application.endpoint.employee}")
    public ResponseEntity<List<Employee>> getEmployees() {
        return ResponseEntity.ok().body(employeeService.getAllEmployees());
    }
}
Agora nossa classe EmployeeController se parece com isto. Há uma série de coisas importantes às quais vale a pena prestar atenção aqui. 1. Anotações acima da classe, o primeiro @RestController informa ao nosso aplicativo que esta classe será um endpoint. 2. @RequestMapping, embora não seja obrigatório, é uma anotação útil, pois permite definir um caminho específico para o endpoint. Aqueles. para bater nele, você precisará enviar solicitações não para localhost:port/employee, mas neste caso para localhost:8086/api/v1/employee Na verdade, de onde vieram essas api/v1 e funcionário? Do nosso application.yml Se você olhar com atenção, poderá encontrar as seguintes linhas:
application:
  endpoint:
    root: api/v1
    employee: employee
    task: task
Como você pode ver, temos variáveis ​​​​como application.endpoint.root e application.endpoint.employee, são exatamente o que escrevi nas anotações, recomendo lembrar este método - economizará muito tempo na expansão ou reescrita do funcionalidade - é sempre mais conveniente ter tudo em config e não codificar todo o projeto. 3. @RequiredArgsConstructor é uma anotação Lombok, uma biblioteca conveniente que permite evitar escrever coisas desnecessárias. Neste caso, a anotação equivale ao fato de que a classe terá um construtor público com todos os campos marcados como finais
public EmployeeController(EmployeeService employeeService) {
    this.employeeService=employeeService;
}
Mas por que deveríamos escrever tal coisa se uma anotação é suficiente? :) Aliás, parabéns, esse campo final tão particular nada mais é do que a notória Injeção de Dependência. Vamos em frente, na verdade, que tipo de campo é EmployeeService? Este será um dos serviços do nosso projeto que processará solicitações para este endpoint. A ideia aqui é muito simples. Cada turma deve ter sua própria tarefa e não deve ser sobrecarregada com ações desnecessárias. Se for um controlador, deixe que ele se encarregue de receber solicitações e enviar respostas, mas preferimos confiar o processamento a um serviço adicional. A última coisa que resta nesta classe é o único método que retorna uma lista de todos os funcionários de nossa empresa que utilizam o serviço mencionado acima. A própria lista está envolvida em uma entidade chamada ResponseEntity. Faço isso para que no futuro, se necessário, eu possa retornar facilmente o código de resposta e a mensagem que preciso, que o sistema automatizado possa entender. Então, por exemplo, ResponseEntity.ok() retornará o 200º código, que dirá que está tudo bem, mas se eu retornar, por exemplo
return ResponseEntity.badRequest().body(Collections.emptyList());
então o cliente receberá o código 400 - reuqest incorreto e uma lista vazia na resposta. Normalmente, esse código é retornado se a solicitação estiver incorreta. Mas um controlador não será suficiente para iniciarmos o aplicativo. Nossas dependências não nos permitirão fazer isso, pois ainda precisamos ter uma base :) Bom, vamos para a próxima parte. Parte IV: persistência simples Como nossa principal tarefa é iniciar o aplicativo, nos limitaremos a alguns esboços por enquanto. Você já viu na classe Controller que retornamos uma lista de objetos do tipo Employee, esta será a nossa entidade para o banco de dados. Vamos criá-lo no pacote demo.persistence.entity.No futuro, o pacote de entidades poderá ser complementado com outras entidades do banco de dados.
@Entity
@Data
@Accessors(chain = true)
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long id;
}
Esta é uma classe tão simples quanto uma porta, cujas anotações dizem exatamente o seguinte: esta é uma entidade de banco de dados @Entity, esta é uma classe com dados @Data - Lombok. O útil Lombok criará para nós todos os getters, setters e construtores necessários - recheio completo. Bem, uma pequena cereja no bolo é @Accessors(chain = true) Na verdade, esta é uma implementação oculta do padrão Builder. Suponha que você tenha uma classe com vários campos que deseja atribuir não por meio de um construtor, mas por métodos. Em ordem diferente, talvez não todos ao mesmo tempo. Você nunca sabe que tipo de lógica estará em seu aplicativo. Esta anotação é a chave para esta tarefa. Vamos olhar:
public Employee createEmployee() {
    return new Employee().setName("Peter")
        				.setAge("28")
        				.setDepartment("IT");
}
Vamos supor que temos todos esses campos em nossa classe😄Você pode atribuí-los, não pode atribuí-los, pode misturá-los em alguns lugares. No caso de apenas 3 imóveis, isso não parece algo excepcional. Mas existem classes com um número muito maior de propriedades, por exemplo 50. E escreva algo como
public Employee createEmployee() {
    return new Employee("Peter", "28", "IT", "single", "loyal", List.of(new Task("do Something 1"), new Task ("do Something 2")));
}
Não parece muito bonito, não é? Também precisamos seguir rigorosamente a ordem de adição de variáveis ​​de acordo com o construtor. No entanto, estou divagando, vamos voltar ao assunto. Agora temos um campo (obrigatório) nele - um identificador exclusivo. Neste caso, trata-se de um número do tipo Long, que é gerado automaticamente quando salvo no banco de dados. Assim, a anotação @Id nos indica claramente que este é um identificador único; @GeneratedValue é responsável por sua geração única. Vale ressaltar que @Id pode ser adicionado a campos que não são gerados automaticamente, mas aí a questão da exclusividade precisará ser tratada manualmente. O que poderia ser um identificador exclusivo de funcionário? Bem, por exemplo, nome completo + departamento... porém, uma pessoa tem homônimos completos, e há uma chance de trabalhar no mesmo departamento, pequena, mas existe - isso significa que a decisão é ruim. Seria possível adicionar vários outros campos, como data de contratação, cidade, mas tudo isso, me parece, complica demais a lógica. Você pode se perguntar: como é possível que vários campos sejam únicos ao mesmo tempo? Eu respondo - talvez. Se você estiver curioso, pode pesquisar no Google algo como @Embeddable e @Embedded Bem, terminamos com a essência. Agora precisamos de um repositório simples. Isso parecerá assim:
public interface EmployeeRepository extends JpaRepository<Employee, Long> {

}
Sim, isso é tudo. Apenas uma interface, chamamos de EmployeeRepository, ela estende JpaRepository que possui dois parâmetros digitados, o primeiro é responsável pelo tipo de dado com o qual trabalha, o segundo pelo tipo de chave. No nosso caso, são Employee e Long. Isso é o suficiente por enquanto. O toque final antes de lançar o aplicativo será o nosso serviço:
@Service
@RequiredArgsConstructor
public class EmployeeService {

    private final EmployeeRepository employeeRepository;

    public List<Employee> getAllEmployees() {
        return List.of(new Employee().setId(123L));
    }
}
Existe o já familiar RequiredArgsConstructor e a nova anotação @Service - isso é o que geralmente denota a camada de lógica de negócios. Ao executar um contexto Spring, as classes marcadas com esta anotação serão criadas como Beans. Quando na classe EmployeeController criamos a propriedade final EmployeeService e anexamos RequiredArgsConstructor (ou criamos um construtor manualmente) Spring, ao inicializar o aplicativo, ele encontrará este local e nos colocará um objeto de classe nesta variável. O padrão aqui é Singleton - ou seja, haverá um objeto para todos esses links; isso é importante levar em consideração ao projetar o aplicativo. Na verdade, isso é tudo, o aplicativo pode ser iniciado. Não se esqueça de inserir as configurações necessárias no config. API REST ou outra tarefa de teste.  - 4 Não descreverei como instalar um banco de dados, criar um usuário e o próprio banco de dados, mas apenas observarei que na URL utilizo dois parâmetros adicionais - useUnicore=true e characterEncoding=UTF-8. Isso foi feito para que o texto fosse exibido de forma mais ou menos igual em qualquer sistema. No entanto, se você estiver com preguiça de mexer no banco de dados e realmente quiser fuçar no código de trabalho, há uma solução rápida: 1. Adicione a seguinte dependência ao build.gradle:
implementation 'com.h2database:h2:2.1.214'
2. Em application.yml você precisa editar várias propriedades, darei um exemplo completo da seção spring para simplificar:
spring:
  application:
    name: "employee-management-service"
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: update
    database-platform: org.hibernate.dialect.H2Dialect
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:file:./mydb
    username: sa
    password:
O banco de dados será armazenado na pasta do projeto, em um arquivo chamado mydb . Mas eu recomendaria instalar um banco de dados completo 😉 Artigo útil sobre o tema: Spring Boot com banco de dados H2 Por precaução, fornecerei uma versão completa do meu build.gradle para eliminar discrepâncias nas dependências:
plugins {
	id 'org.springframework.boot' version '2.7.2'
	id 'io.spring.dependency-management' version '1.0.12.RELEASE'
	id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'mysql:mysql-connector-java:8.0.30'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
	useJUnitPlatform()
}
O sistema está pronto para ser iniciado: API REST ou outra tarefa de teste.  - 5 você pode verificar enviando uma solicitação GET de qualquer programa adequado para nosso endpoint. Neste caso específico, um navegador normal servirá, mas no futuro precisaremos do Postman. API REST ou outra tarefa de teste.  - 6 Sim, de facto ainda não implementámos nenhum dos requisitos de negócio, mas já temos uma aplicação que inicia e pode ser expandida até às funcionalidades necessárias. Continuação: API REST e validação de dados
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION