diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..490f145 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,4 @@ +FROM public.ecr.aws/amazoncorretto/amazoncorretto:11 +EXPOSE 81 +ADD target/dorotech_teste-1.0-SNAPSHOT.jar dorotech_teste-1.0-SNAPSHOT.jar +ENTRYPOINT ["java","-jar","/dorotech_teste-1.0-SNAPSHOT.jar"] \ No newline at end of file diff --git a/README.md b/README.md index d171857..c0c6ec2 100644 --- a/README.md +++ b/README.md @@ -1,76 +1,61 @@ # Desafio Back End Java na DoroTech -Somos uma empresa com clientes que atuam em vários segmentos do mercado, com diferentes tecnologias, culturas e desafios. +### Documentação +``` -Gostamos de compor nossos times com profissionais multidisciplinares, que tenham alta capacidade de aprendizado, sejam detalhistas, resilientes, questionadores e curiosos. Você, como Java Developer, será o responsável por implementar, dar manutenção, aplicar correções e propor soluções em projetos de software. +1. Desenvolvido aplicação CRUD para cadastro, recuperação, update e delete de produtos. -## Requisitos do desafio: -``` -1. Criar um código que execute um CRUD(Create, Read, Update, Delete) em uma tabela para gerenciar produtos eletrônicos. -2. Use um banco NoSQL(DynamoDB é um diferencial). -3. Utilizar Spring como framework(Quarkus é um diferencial). -4. Dados da tabela a ser criada no banco: - - Products: - name, - description, - price, - amount. +2. Foi utilizado o Framework SpringBoot para desenvolvimento, java 11, Maven, Swagger e Lombok. +3. Os dados estão sendo persistidos no Banco de dados NOSQl DynamoDB, tambem estou utilizando Docker, ECR, ECS, fargate, CodeBuild e CodePipeline para deploy da aplicação. -Seja criativo! fazer o melhor não é ser complexo. -``` +4. Utilizei processamento assincrono para criação de um novo produto, utilizando o SQS da AWS -## Dicas e Informações Valiosas -``` -O que gostaríamos de ver em seu teste: +5. As variaveis de ambiente com as credenciais da AWS para testar a aplicação serão enviadas ao avaliador + +6. A aplicação foi configurada para rodar na porta 8081, assim como a porta do container que esta sendo exposta é a porta 81 + +7. É possivil testar e verificar parte da documentação gerada pelo swagger no link gerado pelo mesmo quando rodando local: http://localhost:8081/swagger-ui.html#/ + +8. Como rodar manualmente: + - va ao diretorio raiz do projeto e acesse a pasta target + - em seguida abra no terminal a localização e entre com o seguinte comando + - mvn spring-boot:run + Obs: atente-se as variaveis de ambiente - Convenção de nome em classes, objetos, variáveis, métodos e etc. - Faça commits regulares. Eles são melhores do que um commit gigantesco. Gostaríamos de ver commits organizados e padronizados, então capriche neles! - Bônus 1 Quarkus & AWS, implementação de uma lambda AWS utilizando framework Quarkus - Bônus 2 Testes automatizados - Observação: Nenhum dos itens acima é obrigatório. - -O que o seu Teste não deve ter: - Saber que não foi você quem implementou o projeto. - Várias bibliotecas instaladas sem uso. - Falta de organização de código. - Falta de documentação. - Nome de variáveis sem sentido ou sem padrão de nomes. - Histórico de commits desorganizado e despadronizado. - -Boa Sorte!! -``` +9. Acesso através da API publica : http://54.237.216.243:8081/ + rotas + Acesso atraǘes da API publica + Swagger : http://54.237.216.243:8081/swagger-ui.html#/ + +9. Caso deseje rodar via IDE recomendo fortemente o uso do Intellij, porem caso use outra IDE não deve encontrar grandes problemas pois todo o gerenciamento +de dependencias esta sendo feito pelo maven, basta apenas ter atenção ao detalhe do anotation processor do lombok e tambem adicionar as variaveis de ambiente + -## Itens obrigatórios -``` -1. Possibilitar a criação de um novo produto -2. Possibilitar consulta de todos os produtos no banco de dados. -3. Possibilitar consultar um produto específico pelo id. -4. Permitir a exclusão de um produto. -5. Persistir os dados na base. ``` -## Itens desejáveis +### Métodos HTTP ``` -1. Criação de Testes unitários. -2. Utilização de alguma ferramenta AWS(API Gateway, Lambda, SQS, SNS, EC2,..). -3. Docker. -4. Utilização de algum padrão de projeto. +--->>>Local + - Busca todos os produtos - GET : http://localhost:8081/products/getAllProducts + - Cria produto - POST : http://localhost:8081/products/createProduct + - Encontra produto por ID - GET : GET http://localhost:8081/products/getProductById/{{id}} + - Deleta produto por ID - DELETE : http://localhost:8081/products/deleteProduct/{{id}} + - Atualiza produto por ID - UPDATE : http://localhost:8081/products/updateProduct/{{id}} + ``` -### Instruções para entrega +### Funcionamento do programa ``` -1. Fazer um fork desse repositório - -2. Criar um branch com o seu primeiro e último nome -git checkout -b joao-silva -3. Escreva a documentação da sua aplicação -Você deve, substituir o conteúdo do arquivo README.md e escrever a documentação da sua aplicação, com os seguintes tópicos: - - Projeto: Descreva o projeto e como você o executou. Seja objetivo. - - Tecnologias: Descreva quais tecnologias foram utilizadas, enumerando versões (se necessário) e os links para suas documentações, quais bibliotecas instalou e porque. -Como compilar e rodar: Descreva como compilar e rodar sua aplicação. + OBS: Recomendo a utilização do Swagger para fins de testes manuais. Link Segue Acima -4. Faça uma Pull Request -Após implementada a solução, crie uma pull request com o seu projeto para esse repositório, avise o recrutador. + - Ao iniciar o programa localmente o mesmo ira rodar na porta local 8081 e necessitará das variaveis de ambiente para se conectar a AWS + e por fim se conectar ao DynamoDB e ao sistema de filas SQS + - Ja existe uma carga de dados no Banco então se ao iniciar o software e acessar o metodo GET /getAllProducts esse por sua vez deverá retornar os produtos do banco + - Para criar um produto isso deverá ser feito através do metodo POST /createProduct, a criação de produtos ocorrerá em background com o DTO do produto sendo enviado + para uma fila SQS, e só após a fila ser consumida esse novo produto será persistido no banco. + - Para fazer um update, primeiro deve-se saber o ID do produto em seguida atualizar as informação e então enviar a requisição. + - Para se fazer um delete também é necessário saber o ID do produto que se deseja excluir. + ``` + + diff --git a/buildspec.yml b/buildspec.yml new file mode 100644 index 0000000..19cc8a5 --- /dev/null +++ b/buildspec.yml @@ -0,0 +1,31 @@ +version: 0.2 + + +phases: + pre_build: + commands: + - mvn clean install + - echo Logging in to Amazon ECR... + - aws --version + - aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 237601488461.dkr.ecr.us-east-1.amazonaws.com + - REPOSITORY_URI=237601488461.dkr.ecr.us-east-1.amazonaws.com/myrepository + - COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7) + - IMAGE_TAG=build-$(echo $CODEBUILD_BUILD_ID | awk -F":" '{print $2}') + build: + commands: + - echo Build started on `date` + - echo Building the Docker image... + - docker build -t myrepository . + - docker tag myrepository:latest 237601488461.dkr.ecr.us-east-1.amazonaws.com/myrepository:latest + post_build: + commands: + - echo Build completed on `date` + - echo Pushing the Docker images... + - docker push 237601488461.dkr.ecr.us-east-1.amazonaws.com/myrepository:latest + - echo Writing image definitions file... + - printf '[{"name":"container","imageUri":"%s"}]' $REPOSITORY_URI:$IMAGE_TAG > imagedefinitions.json + - cat imagedefinitions.json +artifacts: + files: + - imagedefinitions.json + - target/dorotech_teste-1.0-SNAPSHOT.jar \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..fa9deb9 --- /dev/null +++ b/pom.xml @@ -0,0 +1,107 @@ + + + 4.0.0 + + groupId + dorotech_teste + 1.0-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-parent + 2.7.4 + + + + + 11 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.projectlombok + lombok + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + io.springfox + springfox-swagger-ui + 2.9.2 + + + + io.springfox + springfox-swagger2 + 2.9.2 + + + + com.amazonaws + aws-java-sdk-dynamodb + 1.12.198 + + + + com.github.derjust + spring-data-dynamodb + 5.1.0 + + + + com.amazonaws + amazon-sqs-java-messaging-lib + 1.0.8 + + + + org.springframework + spring-jms + + + + commons-io + commons-io + 2.4 + + + + com.amazonaws + aws-java-sdk-sqs + 1.12.198 + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/br/dorotech/app/DorotechApp.java b/src/main/java/com/br/dorotech/app/DorotechApp.java new file mode 100644 index 0000000..c125db7 --- /dev/null +++ b/src/main/java/com/br/dorotech/app/DorotechApp.java @@ -0,0 +1,15 @@ +package com.br.dorotech.app; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +@SpringBootApplication +@EnableSwagger2 +public class DorotechApp { + + public static void main(String[] args) { + SpringApplication.run(DorotechApp.class, args); + } + +} diff --git a/src/main/java/com/br/dorotech/app/configs/DynamoDbConfig.java b/src/main/java/com/br/dorotech/app/configs/DynamoDbConfig.java new file mode 100644 index 0000000..7b45529 --- /dev/null +++ b/src/main/java/com/br/dorotech/app/configs/DynamoDbConfig.java @@ -0,0 +1,44 @@ +package com.br.dorotech.app.configs; + +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; +import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient; +import org.socialsignin.spring.data.dynamodb.repository.config.EnableDynamoDBRepositories; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.StringUtils; + + +@Configuration +@EnableDynamoDBRepositories(basePackages = "com.br.dorotech.app.repositories") +public class DynamoDbConfig { + + @Value("${amazon.dynamodb.endpoint}") + private String amazonDynamoDBEndpoint; + + @Value("${amazon.aws.accesskey}") + private String amazonAWSAccessKey; + + @Value("${amazon.aws.secretkey}") + private String amazonAWSSecretKey; + + @Bean + public AmazonDynamoDB amazonDynamoDB() { + AmazonDynamoDB amazonDynamoDB + = new AmazonDynamoDBClient(amazonAWSCredentials()); + + if (!StringUtils.isEmpty(amazonDynamoDBEndpoint)) { + amazonDynamoDB.setEndpoint(amazonDynamoDBEndpoint); + } + + return amazonDynamoDB; + } + + @Bean + public AWSCredentials amazonAWSCredentials() { + return new BasicAWSCredentials( + amazonAWSAccessKey, amazonAWSSecretKey); + } +} diff --git a/src/main/java/com/br/dorotech/app/configs/SQSJmsConsumerConfiguration.java b/src/main/java/com/br/dorotech/app/configs/SQSJmsConsumerConfiguration.java new file mode 100644 index 0000000..399072b --- /dev/null +++ b/src/main/java/com/br/dorotech/app/configs/SQSJmsConsumerConfiguration.java @@ -0,0 +1,62 @@ +package com.br.dorotech.app.configs; + +import com.amazon.sqs.javamessaging.ProviderConfiguration; +import com.amazon.sqs.javamessaging.SQSConnectionFactory; +import com.amazonaws.services.sqs.AmazonSQS; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.DependsOn; +import org.springframework.jms.annotation.EnableJms; +import org.springframework.jms.config.DefaultJmsListenerContainerFactory; +import org.springframework.jms.core.JmsTemplate; +import org.springframework.util.ErrorHandler; + +import javax.annotation.PostConstruct; + +/** + * Configuration class for connection with SQS queues (consumer) + */ +@Configuration +@EnableJms +@DependsOn("SQSProviderConfiguration") +@Slf4j +public class SQSJmsConsumerConfiguration implements ErrorHandler { + + private SQSConnectionFactory connectionFactory; + + private AmazonSQS amazonSQS; + + public SQSJmsConsumerConfiguration(AmazonSQS amazonSQS) { + this.amazonSQS = amazonSQS; + } + + @PostConstruct + public void init() { + connectionFactory = createSQSConnectionFactory(); + } + + private SQSConnectionFactory createSQSConnectionFactory() { + return new SQSConnectionFactory(new ProviderConfiguration(), amazonSQS); + } + + @Bean + public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() { + final DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); + factory.setConnectionFactory(connectionFactory); + factory.setErrorHandler(this); + return factory; + } + + @Bean + public JmsTemplate defaultJmsTemplate() { + return new JmsTemplate(connectionFactory); + } + + @Override + public void handleError(Throwable t) { + log.error("Listener SQS error has been thrown"); + log.error("SQS_ERROR_LOCAL_MESSAGE: {}", t.getLocalizedMessage()); + log.error("SQS_ERROR_MESSAGE: {}", t.getMessage()); + } +} \ No newline at end of file diff --git a/src/main/java/com/br/dorotech/app/configs/SQSProviderConfiguration.java b/src/main/java/com/br/dorotech/app/configs/SQSProviderConfiguration.java new file mode 100644 index 0000000..3002a64 --- /dev/null +++ b/src/main/java/com/br/dorotech/app/configs/SQSProviderConfiguration.java @@ -0,0 +1,33 @@ +package com.br.dorotech.app.configs; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.sqs.AmazonSQS; +import com.amazonaws.services.sqs.AmazonSQSClientBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SQSProviderConfiguration { + + @Value("${amazon.region}") + private String region; + + @Value("${amazon.aws.accesskey}") + private String accessKey; + + @Value("${amazon.aws.secretkey}") + private String secretKey; + + @Bean + public AmazonSQS createSQSClient() { + return AmazonSQSClientBuilder + .standard() + .withCredentials(new AWSStaticCredentialsProvider( + new BasicAWSCredentials(accessKey,secretKey) + )) + .withRegion(region) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/br/dorotech/app/configs/SwaggerConfig.java b/src/main/java/com/br/dorotech/app/configs/SwaggerConfig.java new file mode 100644 index 0000000..f2d0a9d --- /dev/null +++ b/src/main/java/com/br/dorotech/app/configs/SwaggerConfig.java @@ -0,0 +1,47 @@ +package com.br.dorotech.app.configs; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +@Configuration +@EnableSwagger2 +public class SwaggerConfig extends WebMvcConfigurationSupport { + + @Bean + public Docket DorotechApi() { + return new Docket(DocumentationType.SWAGGER_2) + .select() + .apis(RequestHandlerSelectors.any()) + .paths(PathSelectors.any()) + .build() + .apiInfo(metaData()); + } + + private ApiInfo metaData() { + return new ApiInfoBuilder() + .title("Spring Boot REST API - Dorotech") + .description("\"Spring Boot REST API Dorotech App\"") + .version("1.0.0") + .license("Apache License Version 2.0") + .licenseUrl("https://www.apache.org/licenses/LICENSE-2.0\"") + .build(); + } + + @Override + protected void addResourceHandlers(ResourceHandlerRegistry registry) { + registry.addResourceHandler("swagger-ui.html") + .addResourceLocations("classpath:/META-INF/resources/"); + + registry.addResourceHandler("/webjars/**") + .addResourceLocations("classpath:/META-INF/resources/webjars/"); + } +} \ No newline at end of file diff --git a/src/main/java/com/br/dorotech/app/controllers/ProductController.java b/src/main/java/com/br/dorotech/app/controllers/ProductController.java new file mode 100644 index 0000000..aed6514 --- /dev/null +++ b/src/main/java/com/br/dorotech/app/controllers/ProductController.java @@ -0,0 +1,58 @@ +package com.br.dorotech.app.controllers; + +import com.br.dorotech.app.models.dtos.ProductDTO; +import com.br.dorotech.app.models.entities.Product; +import com.br.dorotech.app.services.ProductService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequestMapping(value = "/products") +@RequiredArgsConstructor +public class ProductController { + + private final ProductService productService; + + @GetMapping(value = "/getAllProducts") + public ResponseEntity> getAllProducts() { + final List product = productService.findAllProducts(); + return new ResponseEntity<>(product, HttpStatus.OK); + } + + @PostMapping(value = "/createProduct") + public ResponseEntity createProduct(@RequestBody ProductDTO productDTO) { + final ProductDTO createProductDTO = productService.startProductCreation(productDTO); + return new ResponseEntity<>(createProductDTO, HttpStatus.CREATED); + } + + @GetMapping(value = "/getProductById/{id}") + public ResponseEntity getProductById(@PathVariable String id){ + final ProductDTO productDTO = productService.findProductById(id); + return new ResponseEntity<>(productDTO, HttpStatus.OK); + } + + @DeleteMapping(value = "/deleteProduct/{id}") + public ResponseEntity deleteProduct(@PathVariable String id) { + final String msg = "Product Id " + id + " deleted!"; + productService.deleteProduct(id); + return new ResponseEntity<>(msg, HttpStatus.OK); + } + + @PutMapping(value = "/updateProduct/{id}") + public ResponseEntity updateProduct(@PathVariable String id, @RequestBody ProductDTO productDTO) { + final ProductDTO productUpdateDTO = productService.updateProduct(id, productDTO); + return new ResponseEntity<>(productUpdateDTO, HttpStatus.OK); + } + +} diff --git a/src/main/java/com/br/dorotech/app/exceptions/ResourceNotFoundException.java b/src/main/java/com/br/dorotech/app/exceptions/ResourceNotFoundException.java new file mode 100644 index 0000000..ac9faa0 --- /dev/null +++ b/src/main/java/com/br/dorotech/app/exceptions/ResourceNotFoundException.java @@ -0,0 +1,11 @@ +package com.br.dorotech.app.exceptions; + +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ResponseStatusException; + +public class ResourceNotFoundException extends ResponseStatusException { + + public ResourceNotFoundException(HttpStatus status, String reason) { + super(status, reason); + } +} diff --git a/src/main/java/com/br/dorotech/app/exceptions/RestBusinessException.java b/src/main/java/com/br/dorotech/app/exceptions/RestBusinessException.java new file mode 100644 index 0000000..0ace4b4 --- /dev/null +++ b/src/main/java/com/br/dorotech/app/exceptions/RestBusinessException.java @@ -0,0 +1,11 @@ +package com.br.dorotech.app.exceptions; + +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ResponseStatusException; + +public class RestBusinessException extends ResponseStatusException { + + public RestBusinessException(HttpStatus status, String reason) { + super(status, reason); + } +} diff --git a/src/main/java/com/br/dorotech/app/helper/ProductHelper.java b/src/main/java/com/br/dorotech/app/helper/ProductHelper.java new file mode 100644 index 0000000..082f3a2 --- /dev/null +++ b/src/main/java/com/br/dorotech/app/helper/ProductHelper.java @@ -0,0 +1,36 @@ +package com.br.dorotech.app.helper; + +import com.br.dorotech.app.models.dtos.ProductDTO; +import com.br.dorotech.app.models.entities.Product; + +public class ProductHelper { + + public static ProductDTO productsDTOBuilder(Product product){ + return ProductDTO.builder() + .amount(product.getAmount()) + .price(product.getPrice()) + .name(product.getName()) + .description(product.getDescription()) + .build(); + } + + public static Product productsBuilder(ProductDTO productDTO){ + return Product.builder() + .amount(productDTO.getAmount()) + .price(productDTO.getPrice()) + .name(productDTO.getName()) + .description(productDTO.getDescription()) + .build(); + } + + public static Product productsUpdateBuilder(Product product, ProductDTO productDTO){ + return Product.builder() + .id(product.getId()) + .amount(productDTO.getAmount()) + .price(productDTO.getPrice()) + .name(productDTO.getName()) + .description(productDTO.getDescription()) + .build(); + } + +} diff --git a/src/main/java/com/br/dorotech/app/models/dtos/ProductDTO.java b/src/main/java/com/br/dorotech/app/models/dtos/ProductDTO.java new file mode 100644 index 0000000..3a5c8b8 --- /dev/null +++ b/src/main/java/com/br/dorotech/app/models/dtos/ProductDTO.java @@ -0,0 +1,19 @@ +package com.br.dorotech.app.models.dtos; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ProductDTO { + + private String name; + private String description; + private Double price; + private Double amount; + +} diff --git a/src/main/java/com/br/dorotech/app/models/entities/Product.java b/src/main/java/com/br/dorotech/app/models/entities/Product.java new file mode 100644 index 0000000..fd738d7 --- /dev/null +++ b/src/main/java/com/br/dorotech/app/models/entities/Product.java @@ -0,0 +1,39 @@ +package com.br.dorotech.app.models.entities; + +import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute; +import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAutoGeneratedKey; +import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey; +import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +@DynamoDBTable(tableName = "products") +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class Product implements Serializable { + + private static final long serialVersionUID = 1L; + + @DynamoDBHashKey(attributeName = "productId") + @DynamoDBAutoGeneratedKey + private String id; + + @DynamoDBAttribute + private String name; + + @DynamoDBAttribute + private String description; + + @DynamoDBAttribute + private Double price; + + @DynamoDBAttribute + private Double amount; + +} diff --git a/src/main/java/com/br/dorotech/app/repositories/ProductRepository.java b/src/main/java/com/br/dorotech/app/repositories/ProductRepository.java new file mode 100644 index 0000000..c6dbf1e --- /dev/null +++ b/src/main/java/com/br/dorotech/app/repositories/ProductRepository.java @@ -0,0 +1,13 @@ +package com.br.dorotech.app.repositories; + +import com.br.dorotech.app.models.entities.Product; +import org.socialsignin.spring.data.dynamodb.repository.DynamoDBCrudRepository; +import org.socialsignin.spring.data.dynamodb.repository.EnableScan; +import org.springframework.stereotype.Repository; + +@Repository +@EnableScan +public interface ProductRepository extends DynamoDBCrudRepository { + + +} diff --git a/src/main/java/com/br/dorotech/app/services/ProductService.java b/src/main/java/com/br/dorotech/app/services/ProductService.java new file mode 100644 index 0000000..9e58c6d --- /dev/null +++ b/src/main/java/com/br/dorotech/app/services/ProductService.java @@ -0,0 +1,22 @@ +package com.br.dorotech.app.services; + +import com.br.dorotech.app.models.dtos.ProductDTO; +import com.br.dorotech.app.models.entities.Product; + +import java.util.List; + +public interface ProductService { + + ProductDTO startProductCreation(ProductDTO productDTO); + + void finishProductCreation(ProductDTO productDTO); + + List findAllProducts(); + + ProductDTO findProductById(String id); + + void deleteProduct(String id); + + ProductDTO updateProduct(String id, ProductDTO productDTO); + +} diff --git a/src/main/java/com/br/dorotech/app/services/impl/ProductServiceImpl.java b/src/main/java/com/br/dorotech/app/services/impl/ProductServiceImpl.java new file mode 100644 index 0000000..932473f --- /dev/null +++ b/src/main/java/com/br/dorotech/app/services/impl/ProductServiceImpl.java @@ -0,0 +1,72 @@ +package com.br.dorotech.app.services.impl; + +import com.br.dorotech.app.exceptions.ResourceNotFoundException; +import com.br.dorotech.app.models.dtos.ProductDTO; +import com.br.dorotech.app.models.entities.Product; +import com.br.dorotech.app.repositories.ProductRepository; +import com.br.dorotech.app.services.ProductService; +import com.br.dorotech.app.sqs.ProductMessageProducer; +import com.br.dorotech.app.util.JsonUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +import java.util.List; + +import static com.br.dorotech.app.helper.ProductHelper.productsBuilder; +import static com.br.dorotech.app.helper.ProductHelper.productsDTOBuilder; +import static com.br.dorotech.app.helper.ProductHelper.productsUpdateBuilder; + +@Service +@RequiredArgsConstructor +@Slf4j +public class ProductServiceImpl implements ProductService { + + private final ProductRepository productRepository; + + private final ProductMessageProducer productMessageProducer; + + @Value("${amazon.queue.product-creation}") + private String queueName; + + @Override + public ProductDTO startProductCreation(ProductDTO productDTO) { + productMessageProducer.sentToQueue(queueName, JsonUtil.writeValueAsString(productDTO)); + log.info("***** PRODUCT CREATION DATA SENT TO QUEUE: " + queueName + ", PRODUCT NAME: " + productDTO.getName() + + ", PRODUCT DESCRIPTION: " + productDTO.getDescription()); + return productDTO; + } + + @Override + public void finishProductCreation(ProductDTO productDTO){ + productRepository.save(productsBuilder(productDTO)); + } + + @Override + public List findAllProducts() { + return (List) productRepository.findAll(); + } + + @Override + public ProductDTO findProductById(String id) { + Product product = productRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException(HttpStatus.NOT_FOUND, "Id not found!")); + return productsDTOBuilder(product); + } + + @Override + public void deleteProduct(String id) { + Product product = productRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException(HttpStatus.NOT_FOUND, "Id not found!")); + productRepository.delete(product); + } + + @Override + public ProductDTO updateProduct(String id, ProductDTO productDTO) { + Product product = productRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException(HttpStatus.NOT_FOUND, "Id not found!")); + Product productUpdated = productsUpdateBuilder(product, productDTO); + productRepository.save(productUpdated); + return productDTO; + } + +} diff --git a/src/main/java/com/br/dorotech/app/sqs/ProductMessageConsumer.java b/src/main/java/com/br/dorotech/app/sqs/ProductMessageConsumer.java new file mode 100644 index 0000000..1f3bc89 --- /dev/null +++ b/src/main/java/com/br/dorotech/app/sqs/ProductMessageConsumer.java @@ -0,0 +1,33 @@ +package com.br.dorotech.app.sqs; + +import com.br.dorotech.app.models.dtos.ProductDTO; +import com.br.dorotech.app.services.ProductService; +import com.br.dorotech.app.util.JsonUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.jms.annotation.JmsListener; +import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +@RequiredArgsConstructor +public class ProductMessageConsumer { + + private final ProductService productService; + + @Value("${amazon.queue.product-creation}") + private String queueName; + + @JmsListener(destination = "${amazon.queue.product-creation}") + public void messageConsumer(@Payload String message) { + ProductDTO productDTO = JsonUtil.readValue(message, ProductDTO.class); + + log.info("***** PRODUCT CREATED: " + queueName + ", PRODUCT NAME: " + productDTO.getName() + + ", PRODUCT DESCRIPTION: " + productDTO.getDescription()); + + productService.finishProductCreation(productDTO); + } + +} \ No newline at end of file diff --git a/src/main/java/com/br/dorotech/app/sqs/ProductMessageProducer.java b/src/main/java/com/br/dorotech/app/sqs/ProductMessageProducer.java new file mode 100644 index 0000000..a7e2bf0 --- /dev/null +++ b/src/main/java/com/br/dorotech/app/sqs/ProductMessageProducer.java @@ -0,0 +1,22 @@ +package com.br.dorotech.app.sqs; + +import com.amazonaws.services.sqs.AmazonSQS; +import com.amazonaws.services.sqs.model.SendMessageRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ProductMessageProducer { + + private final AmazonSQS amazonSQS; + + public void sentToQueue(final String queueName, final String message) { + amazonSQS.sendMessage(new SendMessageRequest() + .withQueueUrl(queueName) + .withMessageBody(message) + ); + } + +} + diff --git a/src/main/java/com/br/dorotech/app/util/JsonUtil.java b/src/main/java/com/br/dorotech/app/util/JsonUtil.java new file mode 100644 index 0000000..ea6102a --- /dev/null +++ b/src/main/java/com/br/dorotech/app/util/JsonUtil.java @@ -0,0 +1,54 @@ +package com.br.dorotech.app.util; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class JsonUtil { + private static final Logger LOG = LoggerFactory.getLogger(JsonUtil.class); + + private static final ObjectMapper OBJECT_MAPPER = createObjectMapper(); + + private static final ObjectMapper createObjectMapper() { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true); + objectMapper.registerModule(new JavaTimeModule()); + objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + return objectMapper; + } + + + public static String writeValueAsString(Object value) { + try { + String json = OBJECT_MAPPER.writeValueAsString(value); + json = (json.equals("\"\"")) ? "{}" : json; + + return json; + } catch (JsonProcessingException e) { + LOG.error("Erro ao gerar json : " + value, e); + } + + return ""; + } + + public static T readValue(String json, Class valueType) { + try { + if (JsonNode.class.isAssignableFrom(valueType)) { + return (T) (OBJECT_MAPPER.readTree(json)); + } else { + return OBJECT_MAPPER.readValue(json, valueType); + } + } catch (Exception e) { + LOG.error("Erro ao gerar objeto a partir do JSON : " + json, e); + } + + return null; + } + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..572f4a9 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,20 @@ +server: + port: 8081 + error: + include-message: always + include-binding-errors: always + +amazon: + aws: + accesskey: ${access.key} + secretkey: ${secret.key} + dynamodb: + endpoint: http://dynamodb.us-east-1.amazonaws.com + + queue: + product-creation: product-creation + + region: us-east-1 + + +