Hoje iremos construir uma aplicação completa utilizando Kotlin, Spring, Docker e com deploy no Azure, esta aplicação será responsável por realizar o cálculo do IMC.
Os Requisitos
A aplicação deve realizar o cálculo do IMC (Índice de Massa Corporal), portanto teremos uma Api que receberá os seguintes parâmetros:
- Altura (Double);
- Peso (Double).
O retorno será um JSON contendo uma mensagem sobre em qual faixa a pessoa se encontra.
Criação da Aplicação
A aplicação será construída utilizando as seguintes tecnologias:
- Kotlin;
- OpenJDK (Versão 11 ou superior);
- Apache Maven (Opcional, caso não esteja instalado utilize o .mvnw ou invés de mvn);
- Spring Boot;
- IntelliJ IDEA (Em caso de utilizar o Eclipse, lembre-se de instalar o plugin para Kotlin.)
Estrutura
No site do Spring Initializr vamos utilizar o Generate a Project, que é um wizard que já cria o projeto e coloca todas as dependências necessárias.
Criando a Aplicação no Spring Initializr. |
Após o download do projeto, descompacte e o importe na IDE, o resultado será como o abaixo:
Projeto dentro do IntelliJ IDEA. |
Arquitetura
Como se trata de uma aplicação pequena não precisamos de uma arquitetura muito elaborada, e sim uma que atenda a alguns requisitos básicos:
- Não tenha estado entre as chamadas;
- Possa ser utilizada por qualquer cliente, ou seja, qualquer aplicação independente da tecnologia pode se comunicar com a API;
- As regras de cálculo deve ser simples e com possibilidade de extensão.
Com base nos requisitos, iremos seguir o modelo de Clean Architecture proposta por Uncle Bob, assim temos cada parte da aplicação isolada, facilitando uma evolução ou alteração futura.
Arquitetura da Aplicação. |
Acima definimos a arquitetura, onde teremos os seguintes packages:
- application
- Responsável por armazenar conteúdo de borda da aplicação, no nosso caso, somente teremos o entrypoints, onde teremos a API e classe de Request;
- core
- Responsável por armazenar o conteúdo de domain e os usecases da aplicação;
- config
- Contém todos os recursos de configuração da aplicação, tais como: Classes de tratamento de erros e Bundles de mensagens;
Desenvolvimento da Aplicação
Agora que já temos a base desenvolvida, vamos passar pelos principais pontos do código, analisando como o fluxo da tarefa acontece.
Aplicação Desenvolvida. |
Resources
O arquivo de configuração de aplicações Spring Boot ficam armazenados em src/main/resources, eles podem ser Properties ou Yaml, dentro desse arquivo adicionamos diversas configurações, tais como, portas, profiles, databases, etc.
Em nossa aplicação teremos 2 ambientes, sendo o primeiro de desenvolvimento local (dev), onde o serviço irá utilizar a porta 8080, e em produção(prod) usando a porta 80, para termos a distinção entre os ambientes usaremos os profiles do Spring Boot, onde podemos ter configuração especificas para cada ambiente de execução.
Arquivos para os profiles da aplicação. |
Perceba que temos arquivo properties delimitados por nome, dessa forma conseguimos estabelecer configurações diferentes para cada ambiente.
Entrypoints
O porta de entrada da aplicação onde temos a API e a classe de Request, aqui iremos receber os seguintes dados do usuário:
- height: Altura;
- weight: Peso.
Como sempre, temos que pensar na validação dos dados de entrada, por isso usaremos o BeanValidation para conseguir validar e devolver uma mensagem amigável ao usuário.
@RestController class ImcApi(val imc:ImcUsecase) { @PostMapping("/imc") fun imc(@Valid @RequestBody imcRequest: ImcRequest): ResponseEntity<ImcResponse> { return ResponseEntity.ok(imc.calcule(imcRequest.height!!, imcRequest.weight!!)) } }
API
class ImcRequest { @NotNull(message="{message.height.notnull}") var height: Double? = null @NotNull(message="{message.weight.notnull}") var weight:Double? = null }
Classe de Request
Esta camada é bem simples, e com Kotlin poderíamos deixar-lá ainda menor, omitindo valores de retorno e colocando os recursos no mesmo arquivo.
Config
A camada de config é onde colocamos as classes de configuração da aplicação, sendo elas detalhes de framework, tratamento de erros, entre outras configurações.
O tratamento de erros é um exemplo de configuração customizada, por padrão, o Spring nos fornece um JSON com informações referente a erros de validação que aconteceram em uma chamada, mas este não tão amigável para quem vai consumir, portanto criamos um @RestControllerAdvice para fornecer uma resposta mais simples.
open class ErrorMessage(open val message:String) data class FieldErrorMessage(override val message:String, val field:String) : ErrorMessage(message) @RestControllerAdvice class ErrorController(val messageSource: MessageSource) { @ResponseStatus(code = HttpStatus.BAD_REQUEST) @ExceptionHandler(value = [MethodArgumentNotValidException::class]) fun methodArgumentNotValidException(ex: MethodArgumentNotValidException): List<FieldErrorMessage>? { return ex.bindingResult.fieldErrors.map { FieldErrorMessage(messageSource.getMessage(it, LocaleContextHolder.getLocale()), it.field) } } @ResponseStatus(code = HttpStatus.BAD_REQUEST) @ExceptionHandler(value = [HttpMessageNotReadableException::class]) fun methodArgumentNotValidException(ex: HttpMessageNotReadableException): ErrorMessage { return ErrorMessage(ex.localizedMessage) } }
Classe customizada para tratamento de erros.
Core
A camada core é onde temos as principais lógicas da aplicação, sendo elas domain (Camada que envolve todas as regras de negocio) e os usecases (Regras de aplicação, também conhecida como Application Services).
Em nosso domain, temos um arquivo chamado Classify, nele temos um enum que armazena as regras sobre as faixas do IMC, e uma função que determina a classificação com base no argumento result, portanto, em caso de alguma mudança de regra ou uma possível nova faixa, apenas este elemento sofreria uma modificação:
fun defineClassification(result:Double):Classification{ var classify:Classification? = null for(currentClassify in Classification.values()){ if(currentClassify.predicate(result)){ classify = currentClassify break } } return classify!! } enum class Classification(val category: String, val table: String, val predicate:(Double) -> Boolean){ THINNESS("MAGREZA", "MENOR QUE 18,5", {result -> result <= 18.4}), NORMAL("NORMAL", "ENTRE 18,5 E 24,9", {result -> result in 18.5..24.9 }), OVERWEIGHT("SOBREPESO", "ENTRE 25,0 E 29,9", {result -> result in 25.0..29.9 }), OBESITY("OBESIDADE", "ENTRE 30,0 E 39,9", {result -> result in 30.0..39.9 }), SERIOUS_OBESITY("OBESIDADE GRAVE", "MAIOR QUE 40,0", {result -> result >= 40.0}) }
Regras de Classificação.
No usecase temos os elementos que vão orquestrar o processo de cálculo do IMC, aqui recebemos os dados de entrada, processamos, e os encaminhamos ao domain para determinar qual será a classificação de usuário:
@Component class ImcUsecaseLogic : ImcUsecase { override fun calcule(height: Double, weight: Double): ImcResponse { val resultValue = weight / (height * height) val classifyResult = defineClassification(resultValue) return with(classifyResult){ val resultBigDecimal = BigDecimal.valueOf(resultValue).setScale(2, RoundingMode.HALF_EVEN) ImcResponse(resultBigDecimal, category, table) } } }
Regras de Aplicação do Cálculo.
Um outro ponto importante são os testes unitários, eles garantem que a lógicas de negocio da aplicação estão de acordo com os requisitos, aqui criamos alguns casos de testes para a classe ImcUsecaseLogic.
@Test fun `Calcula o IMC com resultado = THINNESS`(){ val height = 1.90 val weight = 60.0 imcUsecase.calcule(height, weight).run { assertEquals(BigDecimal.valueOf(16.62), result) assertEquals(category, THINNESS.category) assertEquals(message, THINNESS.table) } }
Caso de teste avaliando o resultado THINNESS.
Após a aplicação estar concluída, vamos testar usando a interface do Swagger, para isso vamos adicionar a seguinte dependência no pom.xml:
<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>3.0.0</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-boot-starter</artifactId> <version>3.0.0</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>3.0.0</version> </dependency>
Com tudo pronto podemos rodar aplicação através da IDE ou utilizando Maven como abaixo:
mvn spring-boot:run
ou
./mvnw spring-boot:run
Após basta acessar a url http://localhost:8080/swagger-ui/index.html, e teremos o seguinte resultado:
Tela do Swagger. |
Adicionando Docker ao Projeto
Atualmente o uso de containers vem ganhando popularidade, isso se deve principalmente a facilidade de implantação, agrupamento de dependências, entre outros requisitos, pois tudo isso esta encapsulado dentro da imagem.
Criando a imagem
Existem várias maneiras de se criar uma imagem para uma aplicação JVM, principalmente usando Spring Boot, vamos listar algumas delas:
- Dockerfile com Multi-Stage Builds
- O build da aplicação acontece em uma imagem própria para build, onde temos o compilador e o gerenciador de dependência (Maven ou Gradle), após utilizamos uma imagem apenas para a execução, dessa forma temos uma imagem de Build e outra de Execução;
- Framework
- Buildpack: No caso do Spring Boot, a partir da versão 2.3.0, temos a feature chamada Buildpack que permite a criação de imagens sem a necessidade do uso do Dockerfile, isso através do comando spring-boot:build-image;
- Layered jar: Por padrão o Spring Boot cria um fat-jar contendo todas as dependências da aplicação, isso não é muito bom para cenários com containers, pois tudo ficaria em uma unica layer, mas agora podemos quebrar o artefato em diferentes layers, e usando isso dentro da imagem docker.
Em nosso projeto vamos utilizar a primeira alternativa utilizando Dockerfile, dessa forma podemos analisar a estrutura de um arquivo Dockerfile.
O primeiro passo é estar dentro do diretório raiz da aplicação (onde estão o pom.xml e o Dockerfile) e criar o build da aplicação através do Maven, usando o comando:
mvn clean package
ou
./mvnw clean package
Após o build, vamos construir a imagem docker com o comando:
docker build -f app-imc:1.0.0 .
docker images
O Resultado será:
Imagem criada com base no nome e versão presentes no pom.xml. |
Subindo para o Docker Hub
A próxima etapa é subir a imagem criada para um registry, este é um repositório utilizado para armazenar imagens docker, aqui iremos utilizar o Docker Hub.
Conta Docker Hub
Para utilizar o Docker Hub precisamos criar uma conta, esta permite que você possa armazenar imagens públicas e também privados (sendo um grátis e os demais pagos).
Criando a Tag
O próximo passo é criar uma tag da imagem e enviá-la ao registry, aqui devemos utilizar a seguinte sintaxe: <login-registry>/<image-name>:<version>
docker tag app-imc:1.0.0 aqui-o-seu-login/app-imc:1.0.0
Faça novamente um docker images e veremos a tag pronta para ser utilizada.
Login Docker Hub
Para realizar push para o Docker Hub é necessário efetuar o processo de autenticação, para isso devemos executar o seguinte comando:
docker login
O console irá pedir o Login e Senha, após o processo você deverá ver uma mensagem de Login Succeeded.
Realizando o Push
A ultima etapa é realizar o push para o Docker Hub, esta operação é a responsável por encaminhar a tag gerada para o servidor remoto.
docker push aqui-o-seu-login/app-imc:1.0.0
Após o push efetue o login na plataforma do Docker Hub, e vá em repositórios, o resultado será o seguinte:
Imagem armazenada no Docker Hub. |
Após todos os passos temos a aplicação pronta para rodar em qualquer ambiente que tenha um container runtime.
Subindo a Aplicação no Azure
Agora que temos a aplicação pronta e já hospedada no Docker Hub, vamos subir no Azure para realmente termos um ciclo de desenvolvimento de ponta a ponta.
O Serviço que vamos utilizar no Azure é o Azure App Services, ele permite subir aplicações em uma infraestrutura gerenciada, ou seja, não temos que nos preocupar com detalhes de hardware ou coisas do tipo.
Abrindo a Conta no Azure
Antes de tudo precisamos ter uma conta no Azure, similar a outros cloud providers, o Azure nos fornece uma conta com vários serviços gratuitos por um período de 12 meses, inclusive existem alguns serviços que são sempre gratuitos.
Um outro fator interessante é que além dos serviços gratuitos, também teremos um crédito em $$$ para usar os serviços que não estão na tabela de serviços gratuitos.
A pagina inicial do Azure é como a abaixo:
Tela inicial do Portal Azure. |
Realizando a implantação no Azure App Services
A implantação da aplicação vai acontecer utilizando o Azure App Services portanto, na tela inicial vá na opção App Services e teremos a tela abaixo:
Tela principal do Azure App Services. |
Agora para criar uma nova aplicação, clique na opção New e teremos a seguinte tela:
Criação de um App Services. |
Aqui iremos preencher os dados referentes a aplicação, que são os seguintes:
- Subscription: Avaliação Gratuita
- Resource Group: Vamos criar um novo chamado (app-imc-resource);
- Name: Aqui o nome deverá ser único, ele será utilizado para o acesso a aplicação, por exemplo: <name>.azurewebsites.net
- Publish: Aqui é onde especificamos a tecnologia, no nosso caso, selecionaremos Docker Container;
- Region: Região onde a aplicação estará alocada, podemos manter East US;
- App Service Plan: Como nossa aplicação é de testes, aqui manteremos as configuração do plano free (Free F1);
A próxima etapa de configuração é referente ao Docker, aqui teremos que adicionar as seguintes informações:
- Options: Selecione Single Container;
- Image Source: Aqui iremos selecionar o Docker Hub, que foi onde enviamos a nossa imagem;
- Access Type: Selecione Public, por ser um repositório público;
- Image and Tag: Aqui devemos adicionar o nome e tag da imagem no formato repositorio/nome_imagem:tag, exemplo: repositorio/app-imc:1.0.0
Na etapa de Monitoring, não iremos utilizar nenhum recurso, portanto, marque a opção Enable Application Insights como No.
Na etapa de Tags também não são necessários preenchimentos, portanto basta seguir para a ultima etapa de Review.
Review
Nesta etapa é onde visualizamos um resumo da configuração, é importante sempre revisar as informações principalmente as relacionadas ao custo, para aplicações de testes o item SKU deve ser Free:
Tela de Revisão. |
Após a revisão devemos ir na opção Create, dessa forma o ambiente será criado conforme todas as configurações realizadas nas etapas anteriores.
Deployment em progresso. |
Deployment
Após o processo de criação temos a tela de Deployment, aqui são exibidas informações que podem ser realizadas na aplicação, além de fornecer gráficos contendo métricas de uso e consumo.
Tela de Deployment |
Para acessar a aplicação basta acessar a seguinte url:
<app-name>.azurewebsites.net/swagger-ui.index.html
Tela final do serviço executando no Azure App Services. |
Comentários
Postar um comentário