Boas-vindas ao terceiro curso de Spring Boot da Alura! Meu nome é Rodrigo Ferreira e continuarei acompanhando vocês neste curso, durante o qual exploraremos mais recursos do Spring Boot.
#acessibilidade Rodrigo Ferreira é uma pessoa de pele clara, com olhos castanhos e cabelos castanhos e curtos. Veste camiseta cinza e está sentado em uma cadeira de encosto preto. Ao fundo, há uma parede lisa com iluminação azul.
A ideia é continuarmos o mesmo projeto que desenvolvemos nos cursos anteriores, mas aprender a usar novos recursos e funcionalidades.
Nos cursos anteriores, aprendemos a:
Um dos objetivos deste curso é implementarmos a funcionalidade de agendamento de consultas. Anteriormente, desenvolvemos o CRUD de médicos e o de pacientes. Faltou implementar a funcionalidade mais importante do projeto, o agendamento de consultas.
Também aprenderemos a documentar a nossa API para facilitar a vida de quem precisa consumi-la, ou seja, a equipe que desenvolverá o aplicativo mobile ou a aplicação Front-End da nossa API.
Essas pessoas precisam saber quais as URLs da nossa API, os métodos HTTP suportados, os parâmetros, o formato, o que é devolvido pela API e assim por diante. Geraremos uma documentação automática usando uma biblioteca que se integra com o Spring Boot.
Também aprenderemos a fazer testes automatizados em um projeto com Spring Boot. Já que implementamos repository e controllers, precisaremos fazer testes automatizados destes componentes.
Mas como escrever um teste com a biblioteca JUnit padrão do Java para testar essas classes e componentes que dependem de informações e recursos do Spring? Aprenderemos a fazer isso neste curso.
Por fim, aprenderemos sobre o build do projeto. Imagine que terminamos o nosso projeto e queremos fazer o seu deploy em algum servidor, seja um servidor Cloud ou interno da empresa.
Como geramos o pacote do projeto e fazemos o build? Como funciona o build de uma aplicação que utiliza o Spring Boot? Como executamos o projeto dentro de um servidor independentemente da plataforma utilizada? Também falaremos de tudo isso neste curso.
Além disso, continuaremos trabalhando no projeto da clínica médica, com mesmo quadro Trello com o detalhamento da funcionalidade e aprenderemos a usar esses recursos ao longo do curso.
Achou interessante? Eu também! Te espero na primeira aula!
Vamos começar o projeto! Estou com o Trello dele aberto no navegador. Na coluna "DONE", podemos observar que já implementamos as funcionalidades do CRUD do médico e de pacientes. À esquerda, na coluna "TO DO", faltaram as funcionalidades de agendamento e cancelamento de consultas.
Começaremos implementando o agendamento. Por isso, arrastaremos o cartão "Agendamento de Consultas" para a coluna "DOING". Com um clique sobre o cartão, abriremos as informações dele e, como de costume, leremos a descrição da funcionalidade para entender o que precisamos fazer.
A descrição diz o seguinte:
"O sistema deve possuir uma funcionalidade que permita o agendamento de consultas, na qual as seguintes informações deverão ser preenchidas: Paciente, Médico, Data/Hora da consulta".
"As seguintes regras de negócio devem ser validadas pelo sistema: (segue lista com regras de negócio)".
Essa é uma funcionalidade similar às que já implementamos. A nossa API receberá uma requisição com o ID de paciente ou de médico e a data em que a consulta será realizada.
Com essas informações, faremos as validações e as salvaremos no banco de dados. A diferença é que agora temos regras de negócio mais complexas do que as que vimos anteriormente para médico e paciente.
Abrirei o nosso layout do Figma com um modelo de aplicativo mobile para o nosso projeto.
No aplicativo mobile, a pessoa terá uma tela para agendar uma nova consulta, que contém o nome do ou da paciente e do médico ou médica. Essas informações serão puxadas do nosso banco de dados. Além disso, haverá dois campos para escolha do dia e do horário da consulta, bem como um botão para confirmar o agendamento.
Com isso, o aplicativo mobile precisa fazer validações dos campos obrigatórios, sendo opcional apenas o campo "Nome do médico".
Quando a pessoa preencher todos os campos obrigatórios e clicar em "Agendar consulta", uma requisição será disparada para a nossa API. Precisaremos receber esses dados e fazer o tratamento deles conforme já fizemos nas outras funcionalidades.
Voltando ao cartão no Trello, a única diferença são algumas das validações, que exigirão a escrita de um algortimo, consulta no banco de dados etc.
Assim, estas não são só validações de formulário como aquelas com as quais trabalhamos anteriormente com BIN validation (campo obrigatório, campo número ou texto, tamanho mínimo e máximo etc.). Agora, teremos que aprender a trabalhar com validações um pouco mais complexas.
Se pararmos para pensar, boa parte do código que usaremos para implementar essa funcionalidade será reaproveitado dos passos que fizemos anteriormente.
Assim, para implementar esta ou quaisquer outras funcionalidades, seguimos uma espécie de passo-a-passo. Precisamos criar sempre os seguintes tipos de códigos:
Estes são os cinco tipos de código que sempre desenvolveremos para uma nova funcionalidade. Isso também se aplica ao agendamento das consultas, incluindo um sexto item à lista, as regras de negócio. Nesta aula, entenderemos como implementar as regras de negócio com algoritmos mais complexos.
Agora que já entendemos o que precisa ser feito, desenvolveremos a funcionalidade por partes. Começaremos pelos cinco primeiros itens da lista, que são mais básicos. Em seguida, abordaremos a parte de regras de negócio.
Abrirei o IntelliJ com o projeto importado na IDE. É o mesmo projeto do curso anterior, que finalizamos com a parte de segurança e tratamento de erros.
Como a primeira parte seguirá um mesmo padrão, deixei essas classes prontas no código. Criei um novo ConsultaController
no pacote "src > main > java > med.voll.api > controller".
Já que não estaremos mais cadastrando pacientes, mas consultas, a ideia é ter um Controller para receber essas requisições relacionadas a agendamento de consultas.
Ela é uma classe Controller, com as anotações do Spring: @RestController
e @RequestMapping("consultas")
. Ele mapeia requisições que chegam com a URI "/consultas", sabendo que deve chamar este controller e não os outros.
Em seguida, temos um método anotado com @PostMapping
. Então, a requisição para agendar uma consulta será do tipo Post, assim como observamos nas outras funcionalidades.
Ele recebe um DTO com os dados do agendamento (DadosAgendamentoConsulta
) e, no momento, a única coisa que fiz de diferente foi dar um System.out
nos dados, para garantir que eles cheguem corretamente. Por fim, o método devolve um código 200 com o DTO de resposta.
Perceba que temos um DTO que representa os dados que chegam da API, ou seja, o nome da pessoa paciente, do médico ou médica e a data e hora da consulta (DadosAgendamentoConsulta
).
// Trecho de código suprimido
@RestController
@RequestMapping("consultas")
public class ConsultaController {
@PostMapping
@Transactional
public ResponseEntity agendar(@RequestBody @Valid DadosAgendamentoConsulta dados) {
System.out.println(dados);
return ResponseEntity.ok(new DadosDetalhamentoConsulta(null, null, null, null));
}
}
Ao abrirmos o DadosAgendamentoConsulta
, perceberemos que se trata de um record
conforme os outros que vimos anteriormente. Ele tem os campos que chegam da API (Long idMedico
, Long idPaciente
e LocalDateTime data
) e as anotações do BIN validation @NotNull
para o ID da pessoa paciente e para a data, além de a data ter que ser no futuro (@Future
), ou seja, não podemos agendar uma consulta para dias que já passaram.
// Trecho de código suprimido
public record DadosAgendamentoConsulta(
Long idMedico,
@NotNull
Long idPaciente,
@NotNull
@Future
LocalDateTime data,
Especialidade especialidade) {
}
Voltando ao Controller, o nosso outro DTO é o de resposta, chamado DadosDetalhamentoConsulta
. Ele devolve o ID da consulta criada, do médico, da pessoa paciente e a data da consulta cadastrada no sistema.
Além disso, no pacote "src > main > java > med.voll.api > domain", criamos o subpacote "consulta", que abrange as classes relacionadas ao domínio de consulta.
Dentre elas, temos a entidade JPA "Consulta.java", que contém as anotações da JPA e do Lombok, além das informações da consulta: médico, paciente e data.
// Trecho de código suprimido
@Table(name = "consultas")
@Entity(name = "Consulta")
@Getter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(of = "id")
public class Consulta {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "medico_id")
private Medico medico;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "paciente_id")
private Paciente paciente;
private LocalDateTime data;
}
Neste caso, medico
e paciente
são relacionamentos com as outras entidades Medico
e Paciente
.
Também criamos o "ConsultaRepository.java", vazio por enquanto.
package med.voll.api.domain.consulta;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ConsultaRepository extends JpaRepository<Consulta, Long> {
}
Por fim, temos a migration em "src > main > java > med.voll.api > resources", na pasta "db.migration". Tínhamos cinco migrations, mas agora criei a migration de número 6 ("V6"), que cria a tabela de consultas:
create table consultas(
id bigint not null auto_increment,
medico_id bigint not null,
paciente_id bigint not null,
data datetime not null,
primary key(id),
constraint fk_consultas_medico_id foreign key(medico_id) references medicos(id),
constraint fk_consultas_paciente_id foreign key(paciente_id) references pacientes(id)
);
Nela, temos o ID da consulta, o ID de paciente, ID de médico e a data, sendo que medico_id
e paciente_id
são chaves estrangeiras que apontam para os IDs das tabelas de médico e paciente.
Estes são os códigos padrão para qualquer funcionalidade, com as suas respectivas mudanças de acordo com o projeto. Cada uma criará um controller ou uma entidade distinta, mas o funcionamento é o mesmo.
Eu já rodei o projeto e ele gerou um log na aba "Run", localizada no menu inferior do IntelliJ. Ele detectou a versão 6 da migration, executou esta última e criou a tabela de consultas.
O projeto foi inicializado sem nenhum erro. Já podemos tentar disparar uma requisição para esse endereço "/consultas" e verificar se ele cai no "ConsultaController.java" e nos dá o System.out
exibindo os dados que chegaram no JSON da requisição.
Para disparar a requisição, continuaremos usando o Insomnia. Aberta a sua interface, clicaremos no botão com o símbolo "+", no painel à esquerda, escolheremos a opção "HTTP Request". Dando um duplo clique sobre a request, vamos renomea-la para "Agendar Consulta".
No painel central, trocaremos a requisição de GET para POST e o endereço será http://localhost:8080/consultas. Logo abaixo, na aba "Body", clicaremos na seta para baixo e escolheremos a opção "JSON".
Se observarmos o "ConsultaController" no IntelliJ, o método agendar
recebe como parâmetro o DTO DadosAgendamentoConsulta
, com os seguintes campos: ID do médico, ID de paciente e data. Precisamos mandar no JSON os campos que têm exatamente estes nomes.
Voltando ao Insomnia, escreveremos:
{
"idPaciente": 1,
"idMedico": 1,
"data":
}
Perceba que o atributo data
é representado pelo LocalDateTime
, a classe de datas que entrou no Java 8. Como fazemos para enviar uma data na requisição do JSON de maneira que o Spring consiga criar o objeto LocalDateTime
corretamente? Que máscara usamos para a data?
É preciso que a data fique entre aspas, como se fosse uma string
e tenha o formato de data americano: AAAA-MM-DD. Para separar a data da hora, escrevemos a letra "T" maiúscula e a hora no formato HH:MM:SS. Os segundos são opcionais.
Assim, a requisição ficará da seguinte forma:
{
"idPaciente": 1,
"idMedico": 1,
"data": "2023-10-10T10:00"
}
Clicaremos no botão "Send" para disparar a requisição. Recebemos uma mensagem de erro "403 Forbidden". Isso aconteceu porque estamos usando o Spring Security e não levei o cabeçalho com o token.
Então, clicaremos em "Efetuar login" no menu lateral esquerdo e faremos o login primeiro. Dispararemos a requisição de login com o usuário que temos no nosso banco de dados, a Ana Souza.
Feito isso, recebemos um token de volta na aba Preview, no canto direito da tela. Vamos copiá-lo e voltar à requisição Agendar Consulta". Na aba "Auth" da janela central, clicaremos no ícone de seta para baixo e escolheremos a opção "Bearer Token". No campo "Token", colaremos o token copiado e clicaremos em "Send".
Recebemos o código "200 OK". Ele nos devolveu o DTO de resposta, mas todos os campos estão null
, pois não salvamos nada no banco de dados.
Voltaremos ao IntelliJ e abriremos a aba "Run" no canto inferior da tela. Veremos que ele nos deu o System.out
com as informações que inserimos no Insomnia:
DadosAgendamentoConsulta[idMedico=1, idPaciente=1, data=2023-10-10T10:00]
Conseguimos disparar a requisição para a nossa API e recebemos os dados do agendamento de consulta. A primeira parte foi concluída: já temos todo o código necessário para receber a requisição, ter a entidade, ter o repository e ter a tabela no banco de dados.
O nosso "ConsultaController.java", por enquanto, apenas dá um System.out
. Podemos até apagá-lo porque agora começaremos a fazer as validações das regras de negócio. Mostrarei como implementá-las da maneira mais apropriada no próximo vídeo.
Já implementamos o esqueleto da funcionalidade. Agora, precisamos implementar as regras de negócio.
Antes, abriremos novamente o cartão do Trello que descreve a funcionalidade. Precisamos implementar agora uma série de validações com objetivos distintos.
O nosso trabalho será um pouco diferente do que já fizemos com a validação de campos de formulário via BIN validation. Agora, as validações são mais complexas. Mas como fazemos para implementá-las?
Voltando ao IntelliJ e observando o "ConsultaController.java", poderíamos fazer todas as validações no método agendar()
, antes do retorno. No entanto, essa não é uma boa prática.
A classe controller não deve trazer as regras de negócio da aplicação.
Ela é apenas uma classe que controla o fluxo de execução: ao chegar uma requisição, ela chama a classe X, devolve a resposta Y. Se a condição for Z, ela devolve outra resposta e assim por diante. Ou seja, ela só controla o fluxo de execução e, por isso, não deveria ter regras de negócio.
Assim, isolaremos as regras de negócio, os algoritmos, os cálculos e as validações em outra classe que será chamada pelo Controller.
Expandiremos o menu "Project" na lateral esquerda da interface, selecionaremos o pacote "consulta" e usaremos o atalho "ALT + Insert", escolhendo em seguida a opção "Java Class". Com isso, criaremos uma classe para conter as regras de agendamento de consultas. Vamos chamá-la de "AgendaDeConsultas".
O nome é bem autoexplicativo: essa classe conterá a agenda de consultas. Podemos ter nesta classe outras funcionalidades ainda relacionadas ao agendamento de consultas.
Como acabamos de criar a classe e ela ainda não tem nenhuma anotação, o Spring Boot não consegue carregá-la automaticamente. Por isso, precisamos inserir alguma anotação primeiro.
No entanto, esta não é uma classe Controller tampouco uma classe de configurações. Esta classe representa um serviço da aplicação, o de agendamento de consultas. Por isso, será uma classe de serviços (Service) e levará a anotação @Service
. O objetivo desta anotação é declarar o componente de serviço ao Spring Boot.
Dentro desta classe, criaremos um método public void agendar()
, que recebe como parâmetro o DTO DadosAgendamentoConsulta
.
package med.voll.api.domain.consulta;
import org.springframework.stereotype.Service;
@Service
public class AgendaDeConsultas {
public void agendar(DadosAgendamentoConsulta dados) {
}
}
A classe Service executa as regras de negócio e as validações da aplicação.
Precisaremos usar esta classe em "ConsultaController.java". Precisamos declarar um atributo do tipo AgendaDeConsultas
, chamando-o de agenda
. Para pedir ao Spring instanciar este objeto, usaremos o @Autowired
acima do atributo.
// Trecho de código suprimido
@Autowired
private AgendaDeConsultas agenda;
// Trecho de código suprimido
Com isso, injetamos a classe AgendaDeConsultas
no Controller. Já no método agendar
do Controller, pegaremos o objeto agenda
e chamaremos o método agendar()
, passando como parâmetro os dados que chegam ao Controller. Tudo isso antes do retorno.
@PostMapping
@Transactional
public ResponseEntity agendar(@RequestBody @Valid DadosAgendamentoConsulta dados) {
agenda.agendar(dados);
return ResponseEntity.ok(new DadosDetalhamentoConsulta(null, null, null, null));
}
O Controller recebe as informações, faz apenas a validação do BIN validation e chama a classe Service AgendaDeConsultas
, que executará as regras de negócio. Esta é a forma correta de lidar com as regras de negócio.
Agora, abriremos a classe AgendaDeConsultas
e escreveremos todas as validações que constam no cartão do Trello dentro do método agendar()
.
No fim das contas, o nosso objetivo é salvar o agendamento no banco de dados: recebemos a requisição com os dados de agendamento e precisamos salvá-los na tabela de consultas.
Por isso, precisamos acessar o banco de dados e a tabela de consultas nesta classe. Assim, declararemos um atributo ConsultaRepository
, chamando-o de consultaRepository
.
Logo acima do atributo, usaremos a anotação @Autowired
para que o Spring Boot injete este repository na nossa classe Service.
No fim do método agendar()
, inseriremos consultaRepository.save()
e passaremos um objeto do tipo consulta
, a entidade JPA. Obviamente, só podemos chamar este método se todas as validações tiverem sido executadas conforme as regras de negócio.
@Service
public class AgendaDeConsultas {
@Autowired
private ConsultaRepository consultaRepository;
public void agendar(DadosAgendamentoConsulta dados) {
consultaRepository.save(consulta);
}
}
Já chamamos o save
, mas ele está dando um erro de compilação, porque a variável consulta
ainda não foi criada. Por isso, precisaremos executar essa ação.
@Service
public class AgendaDeConsultas {
@Autowired
private ConsultaRepository consultaRepository;
public void agendar(DadosAgendamentoConsulta dados) {
var consulta = new Consulta();
consultaRepository.save(consulta);
}
}
A entidade Consulta
está anotada com @AllArgsConstructor
, do Lombok, que gera um construtor com todos os atributos. Podemos usar esse mesmo construtor no "AgendamentoDeConsultas". O primeiro parâmetro é o ID null
, pois é o banco de dados que passará o ID. Já o segundo é medico
, paciente
e data
. Esta última virá no DTO, por meio do parâmetro dados
.
Acontece que medico
e paciente
não chegam na requisição, mas sim o ID do médico e o ID do paciente. Por isso, precisamos setar o objeto inteiro na entidade, e não apenas o ID.
Por isso, precisaremos carregar medico
e paciente
do banco de dados. Precisaremos injetar, então, mais dois Repositories na nossa Service: MedicoRepository
e PacienteRepository
.
No método agendar()
, precisamos criar um objeto paciente
também. Usaremos o pacienteRepository.findById()
para buscar o objeto pelo ID, que está dentro do DTO dados
.
Na requisição só vem o ID, mas precisamos carregar o objeto inteiro. Assim, usamos o Repository para carregar pelo ID do banco de dados. O médico seguirá a mesma dinâmica.
@Service
public class AgendaDeConsultas {
@Autowired
private ConsultaRepository consultaRepository;
@Autowired
private MedicoRepository medicoRepository;
@Autowired
private PacienteRepository pacienteRepository;
public void agendar(DadosAgendamentoConsulta dados) {
var paciente = pacienteRepository.findById(dados.idPaciente());
var medico = medicoRepository.findById(dados.idMedico());
var consulta = new Consulta(null, medico, paciente, dados.data());
consultaRepository.save(consulta);
}
}
Aparecerá um erro de compilação porque o método findById()
não devolve a entidade, mas um Optional
. Assim, no fim da linha, antes do ponto e vírgula, precisamos escrever .get()
ao lado de findById()
. Isso faz com que ele pegue a entidade carregada.
// Trecho de código suprimido
var paciente = pacienteRepository.findById(dados.idPaciente()).get();
var medico = medicoRepository.findById(dados.idMedico()).get();
var consulta = new Consulta(null, medico, paciente, dados.data());
consultaRepository.save(consulta);
O nosso método agendar()
na classe Service fará o seguinte: pegamos o ID e carregamos o paciente e o médico do banco de dados; criamos uma entidade consulta
passando o médico, o paciente e a data que vem no DTO; e salvamos isso no banco de dados.
Porém, antes disso, precisamos escrever o código para fazer todas as validações que fazem parte das regras de negócio.
O código das regras de negócio aparecerá antes do último trecho que implementamos neste vídeo. Na sequência, abordaremos como fazer as validações da melhor maneira possível.
O curso Spring Boot 3: documente, teste e prepare uma API para o deploy possui 210 minutos de vídeos, em um total de 46 atividades. Gostou? Conheça nossos outros cursos de Java em Programação, ou leia nossos artigos de Programação.
Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:
Impulsione a sua carreira com os melhores cursos e faça parte da maior comunidade tech.
1 ano de Alura
Assine o PLUS e garanta:
Formações com mais de 1500 cursos atualizados e novos lançamentos semanais, em Programação, Inteligência Artificial, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.
A cada curso ou formação concluído, um novo certificado para turbinar seu currículo e LinkedIn.
No Discord, você tem acesso a eventos exclusivos, grupos de estudos e mentorias com especialistas de diferentes áreas.
Faça parte da maior comunidade Dev do país e crie conexões com mais de 120 mil pessoas no Discord.
Acesso ilimitado ao catálogo de Imersões da Alura para praticar conhecimentos em diferentes áreas.
Explore um universo de possibilidades na palma da sua mão. Baixe as aulas para assistir offline, onde e quando quiser.
Acelere o seu aprendizado com a IA da Alura e prepare-se para o mercado internacional.
1 ano de Alura
Todos os benefícios do PLUS e mais vantagens exclusivas:
Luri é nossa inteligência artificial que tira dúvidas, dá exemplos práticos, corrige exercícios e ajuda a mergulhar ainda mais durante as aulas. Você pode conversar com a Luri até 100 mensagens por semana.
Aprenda um novo idioma e expanda seus horizontes profissionais. Cursos de Inglês, Espanhol e Inglês para Devs, 100% focado em tecnologia.
Transforme a sua jornada com benefícios exclusivos e evolua ainda mais na sua carreira.
1 ano de Alura
Todos os benefícios do PRO e mais vantagens exclusivas:
Mensagens ilimitadas para estudar com a Luri, a IA da Alura, disponível 24hs para tirar suas dúvidas, dar exemplos práticos, corrigir exercícios e impulsionar seus estudos.
Envie imagens para a Luri e ela te ajuda a solucionar problemas, identificar erros, esclarecer gráficos, analisar design e muito mais.
Escolha os ebooks da Casa do Código, a editora da Alura, que apoiarão a sua jornada de aprendizado para sempre.