[{"content":"A forma como trabalhamos com desenvolvimento de software vem mudando com o tempo, a cada dia novas ferramentas aparecem e melhoraram e muito como entregamos código, mas isso também gera algumas preocupações, como a qualidade desse código gerado. Trouxe aqui algumas boas práticas para escrever testes unitários utilizando LLMs, garantindo que o código gerado seja de qualidade e siga as melhores práticas do mercado.\nUnit Tests (Testes unitários) Antes de tudo é importante entender o que são testes unitários, para então passar a como escrever bons testes.\nUnit tests são testes automatizados que validam cenários específicos de regras de negócio, validações lógicas e fluxos de código que garantem qualidade na entrega evitando bugs e falhas em produção. Eles são escritos para testar unidades isoladas de código, como métodos ou classes, garantindo que cada parte do sistema funcione corretamente.\nNos temos unitários temos alguns tipos de cobertura (coverage) como:\nCobertura de linha: indica a porcentagem de linhas de código que foram executadas durante a execução dos testes. Cobertura de método: indica a porcentagem de métodos que foram executados durante a execução dos testes. Cobertura de branch: indica a porcentagem de ramos de decisão (como if, else, switch) que foram executados durante a execução dos testes. Uma alta cobertura de branch indica que diferentes caminhos lógicos do código foram testados. A seguinte estrutura é sempre recomendada para escrever testes unitários:\nGiven: onde são definidos os dados de entrada e o cenário do teste. When: onde é executada a ação ou comportamento que está sendo testado. Then: onde são feitas as asserções para verificar se o resultado esperado foi alcançado. Segue um exemplo em java:\n@Test public void testCalculateTotalPrice() { // Given NinjaShoppingCart cart = new NinjaShoppingCart(); cart.addItem(new Item(\"Kunai\", 15.0)); cart.addItem(new Item(\"Bandana da Vila de Konoha\", 25.0)); // When double totalPrice = cart.calculateTotalPrice(); // Then assertEquals(40.0, totalPrice, 0.001); } Seguir essa estrutura garante que os testes sejam claros, organizados e fáceis de entender, facilitando a manutenção e a identificação de falhas no código.\nMocks, Stubs e Fakes Um outra boa prática é utilizar mocks, stubs e fakes para lidar com dependências externas, como bancos de dados, APIs ou serviços de terceiros, garantindo que os testes sejam independentes e possam ser executados isoladamente.\nMock: usado para verificar interações Stub: retorna valores pré-definidos Fake: implementação simplificada para teste Spy: mistura comportamento real e simulado Normalmente sua linguagem terá uma biblioteca específica para lidar com esses conceitos, como o Mockito para Java/Kotlin, o unittest.mock para Python, entre outros.\nNomemclatura e legibilidade Outra boa prática é utilizar uma nomenclatura clara e descritiva para os testes, garantindo que outros desenvolvedores possam entender facilmente o propósito do teste apenas lendo o nome do método de teste. Além disso, é importante manter os testes legíveis e organizados, utilizando boas práticas de formatação e estruturação do código.\n// Exemplo de nomemclatura ruim @Test public void test1() { // código do teste } // Exemplo de nomemclatura boa @Test public void shouldCalculateTotalPriceWhenItemHasChakra() { // código do teste } Cobertura não é qualidade Mesmo que o código tenha uma boa cobertura de testes, isso não significa necessariamente que o código seja de qualidade. A cobertura é apenas uma métrica que indica a quantidade de código que foi testada, mas não garante que os testes sejam eficazes ou que o código esteja livre de bugs.\nExemplo: Um teste que executa uma linha sem validar corretamente o comportamento ainda conta para a cobertura, mas não garante qualidade. Cobertura é um indicador, não um objetivo final.\n@Test void shouldExecuteMethod() { service.process(); assertTrue(true); } Lembre-se sempre: Testes devem cobrir cenários reais, ou seja, devem ser escritos pensando em situações que podem ocorrer na vida real, garantindo que o código seja testado de forma abrangente e eficaz. Testar por testar apenas poluí o ambiente de desenvolvimento de dificulta análises futuras. Pense no teste como uma documentação viva do código, ele deve ser escrito para ajudar outros desenvolvedores (e IAs) a entenderem o comportamento do código e a identificar possíveis falhas.\nAGENTS.md Outra dica que ajuda a manter o padrão de código e testes do time é utilizar arquivos de configuração, como o AGENTS.md, nele é definido como a AI deve lidar com as situações apresentadas, seguindo um guide de boas práticas, como por exemplo:\nSempre seguir a estrutura Given, When, Then para escrever testes unitários. Garantir que os testes sejam independentes e possam ser executados isoladamente. Manter os testes legíveis e fáceis de entender, utilizando nomes descritivos para os testes e as variáveis. Também podem ser definidas as principais ferramentas utilizadas para escrever testes unitários, como o JUnit para Kotlin/Java, o pytest para Python, entre outros, e as melhores práticas para cada uma dessas ferramentas.\nExemplo de AGENTS.md para um time quen utiliza Kotlin e JUnit no dia a dia:\n# AGENTS.md ## Setup commands - Install dependencies: `mvn clean install` - Configure proxy variables if necessary: ​​`export http_proxy=http://proxy.example.com:8080` and `export https_proxy=http://proxy.example.com:8080` - Configure the development environment: `export JAVA_HOME=/path/to/java` and `export PATH=$JAVA_HOME/bin:$PATH` ## Code style - Use camelCase for variable and method names, and PascalCase for class names. - Follow the standard Kotlin code conventions for formatting, including indentation, spacing, and line breaks. - Use meaningful and descriptive names for variables, methods, and classes to enhance readability and maintainability ## Testing instructions 1. Always follow the Given, When, Then structure to ensure clarity and organization in tests. 2. Ensure that tests are independent, avoiding dependencies between them to facilitate isolated execution. 3. Keep tests readable and easy to understand, using descriptive names for tests and variables, facilitating maintenance and identification of code flaws. ## PR instructions - Title format: [\u003cproject_name\u003e] \u003cTitle\u003e - Description: Provide a clear and concise description of the changes made, including the purpose and any relevant details. Para configurar o uso do AGENTS.md no IntelliJ IDEA, siga os passos abaixo (Esse processo pode variar dependendo da ferramenta que você utiliza, mas o importante é entender o método de configuração):\nSelecione na aba do copilot a opção “Configure Agents”: Nas configurações teremos a opção GitHub Copilot -\u003e Customizations -\u003e “Use AGENTS.md file”, selecione essa opção para habilitar o uso do AGENTS.md para personalizar as instruções de geração de código da IA. Com o arquivo criado na pasta raiz do projeto com o nome AGENTS.md, com isso o GitHub Copilot irá utilizar as instruções definidas no Markdown para gerar os testes unitários, e outras instruções, seguindo as boas práticas definidas no arquivo. Usando IAs para escrever testes unitários Agora que vimos o que são testes unitários e a importância de manter um padrão de código, vamos falar sobre como usar IAs para escrever testes unitários.\nAs ferramentas que irei utilizar aqui são o IntelliJ IDEA e o GitHub Copilot, mas o importante é entender o método simples de escrever testes unitários utilizando IAs, que pode ser aplicado em outras ferramentas.\nPasso 1: Detalhar o cenários de teste (criar o prompt) Essa é a etapa mais importante, definir quais são os requisitos da sua funcionalidade ou daquele pedaço de código que deseja testar.\nExemplo de prompt para um cenário de teste:\nVocê é um engenheiro de QA sênior especializado em testes unitários Java com expertise em sistemas distribuídos e Redis. Analise a classe fornecida e gere uma suíte de testes unitários completa que atinja 95% de cobertura de linhas, branches e métodos. Antes de executar a criação dos testes, forneça um plano detalhado de execução, incluindo: - Identificação dos cenários de teste mais críticos com base na complexidade do código e nas dependências. - Estratégia para lidar com dependências externas, como mocks ou stubs. Com um prompt detalhado, a IA tem mais informações para gerar um teste unitário de qualidade, seguindo as melhores práticas e garantindo uma boa cobertura de código.\nDica: Existem diversas ferramentas para auxiliar em escrever bons prompts, como por exemplo o Prompt Cowboy, sempre que estiver na dúvida utilize ele para melhorar a sua entrada para a IA\nPasso 2: Solicitar um plano de execução para a IA Nesta etapa iremos utilizar o prompt criado no passo anterior para solicitar a IA um plano de execução para escrever os testes unitários, com isso a IA irá analisar o código e criar um plano detalhado de execução, identificando os cenários de teste mais críticos e a estratégia para lidar com dependências externas.\nNo IntelliJ IDEA, temos a opção de “Plan”, com ela podemos solicitar para a IA um plano de execução detalhado, seguindo as melhores práticas definidas no AGENTS.md, para escrever os testes unitários.\nSegue um exemplo de plano de execução gerado pela IA, nele temos todos os principais cenários de teste identificados:\nApós validar o plano gerado podemos clicar na opção “Start Implementation” na própria IDE, lembrando que caso esteja em outra ferramenta basta responder a IA com um simples: “Pode implementar” ou caso tenha pontos de melhoria solicitar uma atualização do plano.\nClasse que foi utilizada para gerar o plano de execução: RedisDistributedTokenBucket.kt\nPasso 3: Solicitar a implementação do teste unitário Com o plano de execução validado, podemos solicitar para a IA a implementação do teste unitário, seguindo o plano de execução gerado no passo anterior e as boas práticas definidas no copilot-instructions.md (AGENTS.md).\nSegue o exemplo de teste gerado: RedisDistributedTokenBucketTest\n@ExtendWith(MockitoExtension::class) class RedisDistributedTokenBucketTest { @Mock private lateinit var redisTemplate: RedisTemplate\u003cString, Any\u003e @Mock private lateinit var valueOperations: ValueOperations\u003cString, Any\u003e private lateinit var tokenBucket: RedisDistributedTokenBucket @BeforeEach fun setUp() { // Criação correta dos mocks e configuração do comportamento esperado whenever(redisTemplate.opsForValue()).thenReturn(valueOperations) tokenBucket = RedisDistributedTokenBucket(redisTemplate) } @Test // Cenário criado com a estrutura Given, When, Then e seguindo as boas práticas definidas no AGENTS.md fun `Given no last refill key When tryConsume Then initialize bucket and consume token`() { // Given whenever(valueOperations.get(\"hero:orders:token_bucket:last_refill\")).thenReturn(null) whenever(valueOperations.get(\"hero:orders:token_bucket\")).thenReturn(10) whenever(valueOperations.decrement(\"hero:orders:token_bucket\", 1L)).thenReturn(9L) whenever(redisTemplate.expire(eq(\"hero:orders:token_bucket\"), any\u003cDuration\u003e())).thenReturn(true) whenever(redisTemplate.expire(eq(\"hero:orders:token_bucket:last_refill\"), any\u003cDuration\u003e())).thenReturn(true) // When val consumed = tokenBucket.tryConsume() // Then assertTrue(consumed) verify(valueOperations).set(\"hero:orders:token_bucket\", 10) verify(valueOperations).set(eq(\"hero:orders:token_bucket:last_refill\"), any\u003cLong\u003e()) verify(valueOperations).decrement(\"hero:orders:token_bucket\", 1L) verify(redisTemplate, times(2)).expire(eq(\"hero:orders:token_bucket\"), any\u003cDuration\u003e()) verify(redisTemplate).expire(eq(\"hero:orders:token_bucket:last_refill\"), any\u003cDuration\u003e()) } } Caso queira ver todo o Pull Request (fazer seu próprio code review) segue o link: Pull Request com teste unitário gerado pela IA\nPasso 4: Revisar o teste unitário gerado pela IA Com o código gerado pela IA, vamos para uma das principais etapas como desenvolvedor nos dia atuais: Fazer o Code Review (Revisar código).\nOs principais pontos que devem ser analisados são:\nCenários cobertos: Verificar se os principais cenários de teste foram cobertos, garantindo que o código esteja testado de forma abrangente e eficaz. Boas práticas: Analisar se o teste unitário segue as boas práticas definidas no AGENTS.md, como a estrutura Given, When, Then, a independência dos testes e a legibilidade do código. Cobertura de código: Verificar se a cobertura de código atingiu o percentual esperado, garantindo que o teste unitário esteja cobrindo as linhas, branches e métodos de forma adequada. Qualidade do código: Analisar a qualidade do código gerado pela IA, verificando se ele é legível, organizado e fácil de entender, garantindo que outros desenvolvedores possam manter e evoluir o código no futuro. Validação dos resultados: Verificar se as asserções feitas no teste unitário estão corretas e se os resultados esperados estão de acordo com o comportamento do código, garantindo que o teste esteja validando corretamente o código. (As vezes a IA alucina e faz o clássico assertEquals(true, true)) Conclusão Uma aplicação bem testada evita diversos problemas produtivos, como bugs, falhas e retrabalho, além de garantir a qualidade do código e evitar retrabalho no futuro. Com o aumento no uso de IAs para auxiliar no desenvolvimento de software, ou até realizar todo o processo de desenvolvimento, uma das principais preocupações dos desenvolvedores será a qualidade do código gerado por essas IAs, e a melhor forma de garantir isso é utilizando boas práticas na escrita de testes.\nEsse foi apenas o inicio, temos outros tipos de testes, como testes de integração, testes de aceitação, testes de performance que são importantes para garantir a qualidade do software e da entrega final.\nCom isso, o desenvolvedor deve conhecer cada vez mais os fundamentos para não cair na armadilha de confiar cegamente na IA, e sim utilizar a IA como uma ferramenta para auxiliar no processo de desenvolvimento, mas sempre mantendo o controle e a responsabilidade sobre o código gerado.\nReferências AGENTS.md Prompt Cowboy JUnit Mockito IntelliJ IDEA ","cover":"/posts/images/2026-04-09-boas-praticas-na-escrita-de-testes-com-IA/cover.png","date":"2026-04-09","description":"","permalink":"https://jjeanjacques10.github.io/posts/2026-04-09-boas-praticas-na-escrita-de-testes-com-ia/","tags":["Engenharia de Software","Testes","Kotlin","IA"],"title":"Boas práticas na escrita de testes unitários com IA"},{"content":"Resolvendo problemas de Rate Limiter com Token Bucket e Semáforos Como utilizar estratégias de rate limit para controlar o consumo de mensagens em arquiteturas distribuídas.\nPara quem trabalha com arquiteturas distribuídas e comunicações assíncronas, esse cenário é bem comum:\nPreciso controlar quantas mensagens devem ser processadas ao mesmo tempo pelos consumidores, para evitar o uso excessivo de recursos ou para seguir alguma regra de negócio específica.\nPara resolver esse tipo de problema, temos algumas estratégias interessantes que podemos utilizar. É exatamente isso que quero trazer neste artigo, além da aplicação de exemplo que criei para validar cada um dos cenários:\nhttps://github.com/jjeanjacques10/rate-limit-hero-orders O problema Inspirado no universo de My Hero Academia, onde super-heróis recebem solicitações de serviço através de uma fila SQS, precisamos de controle, coordenação e limites para não causar problemas de excesso de requisições.\nCenário:\nCentenas de solicitações de heróis chegam simultaneamente Vários “escritórios de heróis” (containers) processam mensagens em paralelo Cada solicitação exige processamento confiável (nenhuma pode ser perdida) O sistema não pode sobrecarregar recursos críticos Vamos agora explorar algumas estratégias para resolver esse problema…\n🏗️Estratégia 1: Controle na aplicação (local) Esta é a abordagem mais simples, onde o controle é feito dentro da própria aplicação/agência, limitando o que cada container individual pode fazer.\nComo funciona (controle local) Configuramos o listener do SQS para processar um número fixo de mensagens por vez. No Spring Cloud AWS, usamos as propriedades maxConcurrentMessages e maxMessagesPerPoll. Cada escritório de super-heróis faz seu próprio controle. No exemplo abaixo, são 3 solicitações por vez:\n@SqsListener( value = [\"event-hero-orders-queue\"], maxConcurrentMessages = \"3\", maxMessagesPerPoll = \"3\" ) fun consumeMessage(message: Message\u003cHeroOrderRequest\u003e, acknowledgement: Acknowledgement) { try { orderService.processOrder(message.payload) acknowledgement.acknowledge() // Confirmação manual } catch (e: Exception) { // Se falhar, volta para a fila automaticamente (validar maxReceiveCount) } } Em cenários de rate limit ou backpressure intencional (Token Bucket ou Semáforo), a mensagem pode retornar várias vezes para a fila sem representar erro. Caso a fila tenha DLQ configurada, é necessário ajustar o maxReceiveCount para evitar que mensagens válidas sejam enviadas indevidamente para a DLQ apenas por falta momentânea de capacidade.\nVantagem ✔ Simples de implementar ✔ Não depende de componentes externos Desvantagem ❌ Não escala bem ❌ Não existe uma visão global do limite Se você tiver 10 containers com limite de 3 mensagens, terá 30 mensagens sendo processadas simultaneamente, independentemente da capacidade real do sistema.\nCenário não recomendado (controle local) ❌ Quando o número de containers escala dinamicamente Execução (controle local) Exemplo de itens sendo processados, de 3 em 3, com cada solicitação durante 5 segundos:\n./mvnw spring-boot:run -Dspring.profiles.active=local-control\nAgora podemos entrar um pouco em estratégias que funcionam melhor em cenários distribuídos:\n🚥 Estratégia 2: Semáforos Distribuídos (Redis) Quando precisamos garantir que, independente do número de escritórios de heróis (containers), apenas um certo número de solicitações sejam processadas simultaneamente no cluster todo, podemos usar um Semáforo Distribuído.\nO Conceito Definimos o Redis como uma fonte da verdade. Antes de processar a mensagem, o consumidor tenta adquirir um “permissão” (permit).\nO consumidor busca a mensagem no SQS. Tenta adquirir o permit no Redis. Conseguiu? Processa e, ao final, libera o permit. Não conseguiu? A mensagem não é confirmada (no-ACK) e volta para a fila para ser tentada novamente por outro (ou pelo mesmo) container. @SqsListener( value = [\"event-hero-orders-queue\"], acknowledgementMode = \"MANUAL\", id = \"distributed-semaphores-order-consumer\" ) fun consumeMessage(message: Message\u003cHeroOrderRequest\u003e, acknowledgement: Acknowledgement) { var permitId: String? = null try { // Tenta adquirir uma permissão do semáforo distribuído permitId = distributedSemaphore.tryAcquire(message.payload.heroId) if (permitId == null) { // Não foi possível adquirir permissão, rejeita a mensagem para ser retentada depois log.warn(\"Could not acquire semaphore permit for order: ${message.payload.heroName}. Message will NOT be acknowledged (returning to queue).\") // NÃO faz o acknowledge - a mensagem retornará à fila return } // Processa o pedido com a permissão orderService.processOrder(message.payload) // Faz o acknowledge da mensagem apenas após o processamento bem-sucedido acknowledgement.acknowledge() log.info(\"Message acknowledged successfully for hero: ${message.payload.heroName}\") } catch (e: Exception) { // Se o processamento falhar, NÃO faz acknowledge - a mensagem retornará à fila log.error(\"Error processing message for hero: ${message.payload.heroName}. Message will NOT be acknowledged.\", e) // A mensagem retornará automaticamente à fila } finally { // Sempre libera a permissão se ela foi adquirida permitId?.let { distributedSemaphore.release(it) } } } Ponto Chave: O uso de TTL (Time-To-Live) nos permits do Redis garante que, se um container morrer, a permissão expire e não trave o sistema indefinidamente.\nCenário não recomendado (semáforo distribuído) ❌ Não usar para controle de taxa (ex: requisições por minuto) ❌ Não usar sem TTL (risco real de deadlock distribuído) Execução (semáforo distribuído) Exemplo de itens sendo processados conforme os recursos são liberados:\n./mvnw spring-boot:run -Dspring.profiles.active=distributed-semaphores\n🪣 Estratégia 3: Token Bucket Distribuído Se o seu problema não é apenas a concorrência (quantos agora), mas sim a taxa de transferência (quantos por segundo), o algoritmo Token Bucket é a escolha ideal. Esse é o que mais vejo ser implementado no dia a dia!\nComo funciona (token bucket) Imagine um balde que se enche de “tokens” a uma taxa constante (ex: 2 tokens por segundo). Para processar uma mensagem, o consumidor precisa retirar um token do balde, para um novo serviço ser processado é necessário que os heróis estejam disponíveis para isso.\nBurst: Se o balde estiver cheio (ex: 10 tokens), o sistema pode processar 10 mensagens. Refill: Após o pico, ele volta a processar apenas na velocidade em que os tokens são repostos (2/s). fun tryConsume(tokensNeeded: Int = 1): Boolean { refillTokens() // Reabastece tokens baseado no tempo val currentTokens = getCurrentTokens() if (currentTokens \u003e= tokensNeeded) { // Consome o token atomicamente redisTemplate.opsForValue().decrement(TOKEN_BUCKET_KEY, tokensNeeded.toLong()) return true } return false } Com isso o consumidor segue o seguinte formato:\n@SqsListener( value = [\"event-hero-orders-queue\"], acknowledgementMode = \"MANUAL\", id = \"distributed-token-bucket-order-consumer\" ) fun consumeMessage(message: Message\u003cHeroOrderRequest\u003e, acknowledgement: Acknowledgement) { try { // Tenta consumir um token do bucket if (!tokenBucket.tryConsume()) { // Nenhum token disponível, rejeita a mensagem para ser retentada depois log.warn(\"No token available for order: ${message.payload.heroName}. Message will NOT be acknowledged (returning to queue).\") // NÃO faz o acknowledge - a mensagem retornará à fila return } // Processa o pedido se o token foi consumido orderService.processOrder(message.payload) // Faz o acknowledge a mensagem apenas após o processamento bem-sucedido acknowledgement.acknowledge() log.info(\"Message acknowledged successfully for hero: ${message.payload.heroName}\") } catch (e: Exception) { // Se o processamento falhar, NÃO faz acknowledge - a mensagem retornará à fila log.error(\"Error processing message for hero: ${message.payload.heroName}. Message will NOT be acknowledged.\", e) // A mensagem retornará automaticamente à fila } } Cenário não recomendado (token bucket) ❌ Não usar quando o problema é concorrência simultânea ❌ Evitar usar sem que a operação seja atômica (como no exemplo acima). Execução (token bucket) Processamentos sendo feitos com o uso de token bucket, muitas transações retornam para fila aguardando a liberação de mais fichas no balde.\n🪣 Estratégia 4: Token Bucket Distribuído com Bucket4J As implementações acima foram feitas de forma manual, vamos explorar um pouco uma solução de mercado para isso. Uma das mais famosas é o Bucket4J, uma biblioteca Java para rate limit baseada no algoritmo de token-bucket. Mas antes quero falar um pouco dos motivos que resolvi trazer essa solução para esse artigo a “Atomicidade”:\nAtomicidade Atomicidade garante que a operação de verificar a quantidade de tokens disponível e consumi-los aconteça como uma única operação indivisível.\nOu tudo acontece, ou nada acontece.\nSem atomicidade, o sistema fica vulnerável a race conditions, como no exemplo abaixo:\nDois consumidores leem simultaneamente que há 1 token disponível Ambos acreditam que podem processar a mensagem Ambos decrementam o contador O limite é violado e mais mensagens são processadas do que o permitido Na primeira implementação de token bucket não tínhamos essa garantia, por serem múltiplos comandos do Redis, não uma única transação atômica. Isso significa que dois ou mais consumidores podem intercalar operações e violar o limite do balde. A lib do Bucket4j usa uma estratégia CAS (comparar e trocar) sobre um único estado de bucket serializado no Redis.\nConfigurando o bucket4j Configuração do Bucket4J, preferi separar em uma classe de configuração nesse caso:\n@Configuration class BucketConfig( @Value(\"\\${spring.data.redis.host:localhost}\") private val redisHost: String, @Value(\"\\${spring.data.redis.port:6379}\") private val redisPort: Int, @Value(\"\\${spring.data.redis.password}\") private val redisPassword: String ) { @Bean fun bucket4jRedisClient(): RedisClient { val redisUri = if (redisPassword.isNotBlank()) { \"redis://$redisPassword@$redisHost:$redisPort\" } else { \"redis://$redisHost:$redisPort\" } return RedisClient.create(redisUri) } @Bean fun bucket4jRedisConnection(redisClient: RedisClient): StatefulRedisConnection\u003cString, ByteArray\u003e { return redisClient.connect(RedisCodec.of(StringCodec.UTF8, ByteArrayCodec.INSTANCE)) } @Bean fun bucket4jProxyManager(connection: StatefulRedisConnection\u003cString, ByteArray\u003e): ProxyManager\u003cString\u003e { return LettuceBasedProxyManager.builderFor(connection).build() } @Bean fun bucket4jConfigurationConfig(): BucketConfiguration { return BucketConfiguration.builder() .addLimit { limit -\u003e limit.capacity(MAX_TOKENS) .refillGreedy(REFILL_TOKENS, Duration.ofSeconds(REFILL_PERIOD_SECONDS)) }.build() } @Bean fun bucket(proxyManager: ProxyManager\u003cString\u003e, bucketConfiguration: BucketConfiguration): Bucket { val bucket = proxyManager.builder().build(BUCKET_KEY) { bucketConfiguration } log.info(\"Bucket4j initialized with capacity: $MAX_TOKENS tokens, refill rate: $REFILL_TOKENS tokens per $REFILL_PERIOD_SECONDS second(s)\") return bucket } companion object { val log: Logger = LoggerFactory.getLogger(this::class.java) const val BUCKET_KEY = \"hero:orders:bucket4j:token_bucket\" const val MAX_TOKENS = 10L // Capacidade máxima do bucket const val REFILL_TOKENS = 2L // Tokens adicionados const val REFILL_PERIOD_SECONDS = 1L // Período de reabastecimento (1 segundo) } } Com isso o controle fica no service da aplicação, validando se existem tokens válidos para uso naquele momento.\n@Service class RedisDistributedTokenBucket4j( private val bucket: Bucket ) { /** * Tenta consumir um token do bucket * @param tokensNeeded número de tokens necessários (padrão 1) * @return true se o(s) token(s) foi(ram) consumido(s), false caso contrário */ fun tryConsume(tokensNeeded: Long = 1): Boolean { val consumed = bucket.tryConsume(tokensNeeded) if (consumed) { val availableTokens = bucket.availableTokens log.info(\"Token(s) consumed: $tokensNeeded. Available tokens: $availableTokens/$MAX_TOKENS\") } else { val availableTokens = bucket.availableTokens log.warn(\"Not enough tokens available. Current: $availableTokens/$MAX_TOKENS, Needed: $tokensNeeded\") } return consumed } companion object { val log: Logger = LoggerFactory.getLogger(this::class.java) } } Vantagem (token bucket com bucket4j) ✔ Não há race conditions, o CAS (Compare-And-Set) garante atomicidade ✔ Fácil de configurar, com uma API simples e intuitiva ✔ Testado em produção por milhares de empresas Sempre é bom considerar “opções de mercado” com soluções já prontas, onde a “roda” não precisa ser reimplementada.\nExecução (token bucket com bucket4j) Exemplo de itens sendo processados utilizandos os tokens no bucket:\n./mvnw spring-boot:run -Dspring.profiles.active=distributed-token-bucket4j\n📊 Semáforo vs. Token Bucket Colocando as duas opções lado a lado:\n💡 Conclusão Não existe “bala de prata” para rate limit.\nSe o controle é simples e o número de instâncias é fixo, um controle a nível de aplicação resolve! Se temos uma limitação relacionada a quantos itens simultâneos precisam ser processados, por exemplo, caso seu banco de dados não aguenta mais de 10 conexões simultâneas, use Semáforos. Se o controle precisa ser quantos processos são permitidos, como 100 itens por minuto, vá de Token Bucket. Mesmo que seja possível implementar soluções próprias, sempre busque utilizar soluções de mercado se possível. O segredo está em entender o gargalo do seu sistema e escolher a estratégia que melhor protege seus recursos sem sacrificar a resiliência.\nCaso tenha alguma crítica, sugestão ou dúvida fique à vontade para me enviar uma mensagem\nAté a próxima!\nReferências System Design — API Gateway | Matheus Fidelis (Token Bucket) Você Sabe o que é Token Bucket? Entenda de Forma Simples jjeanjacques10/rate-limit-hero-orders ","cover":"/posts/images/2026-01-17-resolvendo-problemas-de-rate-limiter-com-token-bucket-e-semaforos/cover.png","date":"2026-01-17","description":"","permalink":"https://jjeanjacques10.github.io/posts/2026-01-17-resolvendo-problemas-de-rate-limiter-com-token-bucket-e-semaforos/","tags":["AWS","Spring Boot","Sistemas Distribuídos","Rate Limiting"],"title":"Resolvendo problemas de Rate Limiter com Token Bucket e Semáforos"},{"content":"Envie Artigos do Medium para o Kindle com Facilidade Você é um leitor ávido que adora consumir conteúdo do Medium? Já imaginou poder ler seus artigos favoritos no Kindle, com a tela maior e mais confortável?\nCom o Article Kindle Converter, você pode fazer isso com apenas alguns cliques! Essa ferramenta gratuita converte artigos do Medium para o formato epub, permitindo que você os envie diretamente para o seu Kindle.\nComo Funciona Acesse o site: https://medium-kindle-sender.onrender.com Cole o link do artigo: Insira o link do artigo do Medium que você deseja ler no Kindle. Opcional: Adicione seu e-mail: Se desejar receber o artigo convertido por e-mail, digite seu endereço de e-mail. Converta e baixe: Clique em “Baixar epub” para converter o artigo para o formato epub e baixá-lo para o seu computador. Envie para o Kindle: Se você configurou o envio por e-mail, o artigo será enviado automaticamente para o seu Kindle. Caso contrário, você pode enviar o arquivo epub manualmente para o seu e-mail do Kindle. Configuração do E-mail do Kindle Para enviar artigos diretamente para o seu Kindle por e-mail, é necessário configurar o seu endereço de e-mail na Amazon (@kindle.com). Siga estas etapas que também estão descritas no site oficial Send to Kindle — E-mail (amazon.com.br):\nAcesse Gerencie seu conteúdo e dispositivos Vá para a guia “Preferências”. Em “Configurações de Documentos Pessoais”, role para baixo até “Lista de E-mails de Documentos Pessoais Aprovados”. Clique em “Adicionar um novo endereço de e-mail aprovado”. Digite o endereço de e-mail que você deseja usar e clique em “Adicionar Endereço”. Por enquanto esse é o único e-mail válido para usar com o Article Kindle Converter: automation@trial-3zxk54vne31ljy6v.mlsender.net\nRecursos Adicionais Código Aberto: O código do Article Kindle Converter está disponível no GitHub: https://github.com/jjeanjacques10/medium-kindle-sender. Você pode contribuir com o projeto ou sugerir melhorias. Suporte: Se precisar de ajuda ou tiver alguma dúvida, consulte a página “Como Usar” no site da ferramenta: https://medium-kindle-sender.onrender.com/how-to-send-to-kindle Conclusão O Article Kindle Converter é uma ferramenta valiosa para quem gosta de ler artigos do Medium no Kindle. Com sua interface simples e intuitiva, você pode converter e enviar seus artigos favoritos para o seu dispositivo com facilidade.\nEspero que goste e recomende para amigos!\nCaso tenha alguma crítica, sugestão ou dúvida fique a vontade para me enviar uma mensagem:\nAté a próxima!\n","cover":"/posts/images/2024-05-05-envie-artigos-do-medium-para-o-kindle-com-facilidade/cover.png","date":"2024-05-05","description":"","permalink":"https://jjeanjacques10.github.io/posts/2024-05-05-envie-artigos-do-medium-para-o-kindle-com-facilidade/","tags":["Python","Automação","Open Source","Produtividade"],"title":"Envie Artigos do Medium para o Kindle com Facilidade"},{"content":"Descomplicando a Configuração de Producers e Consumers com Kafka Entendendo como configurar Producers e Consumers com Kafka de forma simples e descomplicada visando o que faz sentido para os seus projetos\nEm algum momento pode ser que apareça uma tarefa em seu trabalho onde precise criar uma integração com o Kafka, quando você começa a desenvolver sua aplicação, criar sua lógica de negócio e então se de para com o arquivo cheio de parâmetros surgindo a pergunta: “Quais configurações devo fazer aqui que fazem sentido para o meu cenário?”.\nÉ comum esse tipo de questionamento, kafka como uma ferramenta robusta nos oferece diversos recursos e configurações, e é importante entender como cada uma delas pode ser utilizada para atender as necessidades do seu projeto. Vamos entender um pouco mais sobre o Kafka e trazer exemplos práticos de como você pode preencher esses parâmetros da melhor forma sem precisar “chutar” o que pode ou não ser o melhor para o seu cenário.\nO que é o Kafka Kafka é uma plataforma de streaming distribuída desenvolvida pelo LinkedIn em 2011 e posteriormente, foi doada para a Apache Software Foundation, que hoje cuida da ferramenta. Nasceu da necessidade de lidar com fluxos de dados em tempo real de forma escalável, durável e tolerante a falhas, tornando-se uma peça fundamental em arquiteturas modernas de processamento de dados com a capacidade de processar trilhões de mensagens por dia. Em exemplos reais, Kafka pode ser utilizado para:\nStreaming de Dados em tempo real Monitoramento e Alertas Processamento de Big Data Integração entre Microsserviços Tópicos Tópicos são utilizados para organizar o fluxo de dados no Kafka, onde os registros são armazenados na sequência em que foram gerados, vamos entrar em detalhes mais a frente de como isso funciona. Por exemplo, uma aplicação de processamento de pagamentos pode ter tópicos como “transacoes-cartao”, “transacoes-boletos” e “transacoes-pix”, cada um representando um tipo diferente de transação financeira.\nTópicos vs Filas: Ao discutir tópicos no Kafka, muitas vezes surge a dúvida sobre a diferença entre eles e as filas. Em uma fila, as mensagens são consumidas sequencialmente por um único consumidor, garantindo o processamento FIFO (First In, First Out). Por outro lado, em um tópico, segue-se o modelo publish/subscribe, onde cada mensagem publicada é consumida por todos os consumidores registrados no tópico. Além disso, são utilizadas técnicas de particionamento para garantir a escalabilidade na leitura das mensagens pelos grupos de consumidores.\nBrokers Os brokers no Kafka são os servidores encarregados de armazenar e gerenciar os dados dos tópicos. Cada broker é parte de um cluster Kafka e mantém uma cópia dos dados dos tópicos aos quais está atribuído. Eles são escaláveis e podem ser adicionados ou removidos do cluster conforme necessário para aumentar a capacidade ou a disponibilidade do sistema.\nPartições (Partitions) Partições são segmentações que permitem dividir o tópico em frações menores; novas mensagens são adicionadas a uma partition.\nCada partição possui offsets, que são a identificação da mensagem dentro de uma partição específica, o que ajuda a saber a ordem em que foram recebidas e que devem ser processadas. Serem separadas por offsets facilita na hora de processar as mensagens, pois é possível configurar para que sejam lidas a partir de um offset específico, como em casos de reprocessamento, regras de negócio que precisem “voltar no tempo” para processar mensagens antigas ou onde queremos apenas ler mensagens de um determinado ponto.\nUtilizar várias partições garante desempenho com grandes cargas de trabalho, aproveitando a replicação e a distribuição de carga entre os brokers. Por exemplo, se um tópico tiver 3 partições e 3 consumidores, cada consumidor lerá de uma partição diferente, garantindo que as mensagens sejam processadas de forma paralela.\nAvro O Kafka transfere bytes de um local para outro, Avro é uma solução para garantir o contrato nessa comunicação. Ele é uma ferramenta de serialização/deserialização de dados que define o formato das mensagens que serão enviadas e recebidas. Além disso, ele permite definir esquemas para os dados transferidos, o que auxilia na garantia de que os dados sejam interpretados corretamente pelos consumidores, mesmo quando os esquemas evoluem ao longo do tempo.\nAqui está um exemplo de um esquema Avro que define um objeto chamado TransactionItem:\n{ \"type\": \"record\", \"name\": \"TransactionItem\", \"namespace\": \"com.payment\", \"fields\": [ { \"name\": \"value\", \"type\": \"string\" }, { \"name\": \"origin\", \"type\": { \"type\": \"record\", \"name\": \"Origin\", \"fields\": [ { \"name\": \"account\", \"type\": \"string\" } ] } }, { \"name\": \"createdAt\", \"type\": { \"type\": \"string\" } } ] } Importante lembrar que o Avro não é obrigatório, mas é uma boa prática utilizá-lo para garantir a compatibilidade entre as mensagens enviadas e recebidas.\nAlém dos exemplos mencionados anteriormente, existem muitas outras aplicações para essa ferramenta. Neste artigo, vamos nos concentrar na comunicação entre microsserviços. Nosso exemplo será um sistema de pagamentos que processa transações PIX e notifica os clientes quando tudo ocorrer com sucesso!\nTodos os exemplos apresentados neste artigo podem ser encontrados no seguinte repositório no GitHub: jjeanjacques10/payment-async-kafka: This is a payment system that utilizes Kafka technology for asynchronous integration\nProducer O producer Kafka é responsável por gerar as mensagens e publicá-las nos tópicos. É possível ter diversos producers publicando mensagens em um mesmo tópico, e também é possível ter diversos producers publicando mensagens em tópicos diferentes. Para evitar de perder mensagens, lembre-se de configurar o “acks”, que define o número de replicas que devem confirmar o recebimento da mensagem, e também o “retries”, que define o número de tentativas que o producer deve realizar para enviar a mensagem.\nAqui está um exemplo de configuração para um produtor em Spring, onde as principais configurações estão relacionadas à forma como queremos enviar as mensagens.\nspring: application.name: pix-processor kafka: producer: bootstrap-servers: localhost:9092 key-serializer: org.apache.kafka.common.serialization.StringSerializer value-serializer: io.confluent.kafka.serializers.KafkaAvroSerializer compression-type: snappy # Configura o tipo de compressão que deve ser aplicado às mensagens acks: all # Configura o acks para \"all\" para garantir que todas as réplicas confirmem o recebimento da mensagem retries: 3 # Configura o número de tentativas que o producer deve realizar para enviar a mensagem template: default-topic: payment-topic bootstrap-servers: Define os endpoints utilizados para se conectar com o cluster Kafka. Ex: bootstrap-servers=kafka1:9092,kafka2:9092 key-serializer: Classe responsável pela serialização das chaves das mensagens produzidas. Ex: key-deserializer=org.apache.kafka.common.serialization.StringDeserializer value-serializer: Classe responsável pela serialização dos valores das mensagens produzidas, ele define o formato que os consumidores devem seguir. Ex: key-deserializer=io.confluent.kafka.serializers.KafkaAvroSerializer properties.schema.registry.url: URL do registro de esquemas utilizado para registrar e recuperar esquemas. Ex: schema.registry.url=http://localhost:8082 (exemplo para configuração local) auto.register.schemas: Indica se os esquemas que ainda não existem devem ser registrados automaticamente no schema registry. Ex: auto.register.schemas=false retries: Número de tentativas que o produtor deve realizar para enviar a mensagem. Ex: retries=3 acks O “acks” é uma propriedade que define o número de réplicas que devem confirmar o recebimento da mensagem. Existem três valores possíveis para essa propriedade:\n0: O produtor não aguarda nenhuma confirmação. 1: O produtor aguarda a confirmação do líder da partição. all: O produtor aguarda a confirmação de todas as réplicas da partição. value-serializer Um dos pontos principais a serem configurados no produtor é o “value-serializer”, que define como os valores das mensagens serão serializados. No exemplo acima, o “value-serializer” está configurado como “io.confluent.kafka.serializers.KafkaAvroSerializer”, que é um serializador Avro.\nio.confluent.kafka.serializers.KafkaAvroDeserializer org.apache.kafka.common.serialization.StringSerializer org.apache.kafka.common.serialization.ByteArraySerializer compression-type O “compression-type” é uma propriedade que define o tipo de compressão que deve ser aplicado às mensagens. Existem alguns tipos de compressão disponíveis, como:\nnone: Sem compressão. gzip: Compressão GZIP. snappy: Compressão Snappy. lz4: Compressão LZ4. Consumer Os consumidores são aqueles que se conectam aos nossos tópicos para ler as mensagens publicadas pelos produtores. É possível ter diversos consumers conectados a um tópico. Para evitar a leitura de mensagens repetidas, lembre-se de configurar o “group-id”, definindo que o novo consumidor faz parte de um grupo único e que realiza o mesmo processo e também realiza o “acknowledging” que remove a mensagem da fila para o grupo definido. É utilizado o offset para controlar a leitura das mensagens, garantindo que as mensagens sejam lidas apenas uma vez por aquele grupo consumidor (group-id).\nAqui está um exemplo de configuração para um consumidor em Spring, onde as principais configurações estão relacionadas à forma como queremos receber as mensagens e como elas devem ser tratadas.\nspring: application.name: notification kafka: consumer: bootstrap-servers: localhost:9092 key-deserializer: org.apache.kafka.common.serialization.StringDeserializer value-deserializer: io.confluent.kafka.serializers.KafkaAvroDeserializer group-id: notification-payment-group properties: auto.offset.reset: earliest # ou 'latest' listener: ack-mode: MANUAL_IMMEDIATE template: default-topic: payment-topic properties: spring.deserializer.key.delegate.class: org.apache.kafka.common.serialization.StringDeserializer spring.deserializer.value.delegate.class: io.confluent.kafka.serializers.KafkaAvroDeserializer spring.deserializer.value.fail-on-unknown-type: false spring.deserializer.value.type: io.confluent.kafka.serializers.subject.RecordNameStrategy schema.registry.url: http://localhost:8082 specific.avro.reader: true # Deserialize to the generated Avro class rather than a GenericRecord type auto.register.schemas: false # Whether schemas that do not yet exist should be registered Existem tantas configurações que seria difícil descrevê-las todas neste artigo, então vou focar em algumas das propriedades que costumo adicionar em minhas configurações, busque entender o que faz mais sentido para o seu cenário.\nkey-deserializer: Classe responsável pela desserialização das chaves das mensagens consumidas. Ex: key-deserializer=org.apache.kafka.common.serialization.StringDeserializer value-deserializer: Classe responsável pela desserialização dos valores das mensagens consumidas. Ex: value-deserializer=io.confluent.kafka.serializers.KafkaAvroDeserializer group-id: Identificador do grupo de consumidores ao qual este consumidor pertence. Ex: group-id=pix-consumer-group properties.spring.deserializer.key.delegate.class: Classe delegada responsável pela desserialização das chaves das mensagens, utilizada para configurações específicas do Spring. Ex: spring.deserializer.key.delegate.class=org.apache.kafka.common.serialization.StringDeserializer properties.spring.deserializer.value.delegate.class: Classe delegada responsável pela desserialização dos valores das mensagens, utilizada para configurações específicas do Spring. Ex: spring.deserializer.value.delegate.class=io.confluent.kafka.serializers.KafkaAvroDeserializer properties.spring.deserializer.value.fail-on-unknown-type: Indica se deve falhar ao desserializar um tipo desconhecido. Pode auxiliar em cenários onde o esquema é desconhecido. Ex: spring.deserializer.value.fail-on-unknown-type=false properties.spring.deserializer.value.type: Estratégia para obter o nome do registro do esquema para um determinado valor, usado em conjunto com o Schema Registry. Ex: spring.deserializer.value.type=io.confluent.kafka.serializers.subject.RecordNameStrategy specific.avro.reader: Indica se a desserialização deve ser feita para a classe Avro gerada específica ou para o tipo GenericRecord. Ex: specific.avro.reader=true ack-mode O “ack-mode” é uma propriedade que define como o consumidor deve confirmar o recebimento da mensagem. Existem três modos de confirmação:\nRECORD: Confirmação de registro por registro. BATCH: Confirmação de registros em lote. MANUAL: Confirmação manual. MANUAL_IMMEDIATE: Confirmação manual imediata. No exemplo abaixo, o “ack-mode” está configurado para MANUAL_IMMEDIATE, ou seja, a confirmação é feita manualmente e imediatamente após o processamento da mensagem.\ntry { log.info(\"Consume Kafka message - topic: {}, offset: {}, partition: {}\", topic, offset, partition) notificationService.process(transactionItem.toTransaction()) } catch ( ex: `Exception) {` log.error(\"Error processing message: {}\", ex.message, ex) } finally { ack.acknowledge() // Confirmação manual sendo realizada } consumer.properties.auto.offset.reset No Kafka, as configurações “earliest” e “latest” são usadas para determinar onde o consumidor deve começar a consumir mensagens.\nearliest: Quando o deslocamento da partição não está presente (por exemplo, quando o grupo de consumidores está consumindo a partição pela primeira vez), o consumidor começará a consumir a partir do início da partição. latest: Quando o deslocamento da partição não está presente, o consumidor começará a consumir a partir do final da partição, ou seja, ele não consumirá nenhuma mensagem que já esteja na partição no momento em que começou a consumir. Boas práticas É uma boa prática adiciona o log do offset e também a partition que está lendo, isso pode auxiliar na análise futura do problema de ambos os lados, tanto no consumer quanto no producer.\n@KafkaListener(topics = [\"payment-topic\"], containerFactory = \"kafkaListenerContainerFactory\") fun consumePayment( @Payload transaction: TransactionItem, @Header(KafkaHeaders.OFFSET) offset: Long, @Header(KafkaHeaders.RECEIVED_PARTITION) partition: Int?, @Header(KafkaHeaders.RECEIVED_TOPIC) topic: String?, ack: Acknowledgment ) { try { log.info(\"Consume Kafka message - topic: {}, offset: {}, partition: {}\", topic, offset, partition) } catch ( ex: `Exception) {` log.error(\"Error processing message: {}\", ex.message, ex) } finally { ack.acknowledge() } } Outro ponto importante é a segurança, em ambientes produtivos é essencial proteger o cluster Kafka. Isso pode incluir autenticação, autorização, SSL/TLS, entre outros. Aqui está um exemplo de configuração para habilitar SSL/TLS:\nspring.kafka.properties.security.protocol: SSL spring.kafka.properties.ssl.truststore.location: /path/to/truststore spring.kafka.properties.ssl.truststore.password: truststorePassword spring.kafka.properties.ssl.keystore.location: /path/to/keystore spring.kafka.properties.ssl.keystore.password: keystorePassword Essas configurações devem ser ajustadas de acordo com as necessidades específicas do ambiente e da política de segurança da organização.\nConclusão Desde que comecei a aprender sobre mensageria e todas as possíveis aplicações percebi como é um tema vasto. Até focando apenas no Kafka já vemos como não é algo simples, sendo ele uma ferramenta robusta que pode ser utilizada para diversos cenários, desde streaming de dados até integração entre microsserviços. Neste artigo, vimos algumas das principais configurações que pode ser feitas em nossas aplicações, para ir mais além vá mais a fundo na documentação e realize testes dentro de casa.\nA forma que mais aprendi sobre Kafka foi passando por problemas durante o desenvolvimento, e o que mais me auxiliou a resolver esses problemas foi entender melhor como a ferramenta funciona, por isso a introdução de conceitos básicos é essencial para quem está começando.\nEspero que este artigo tenha sido último em sua jornada e que seja um ponto de partida para você começar a explorar cada vez mais o Kafka em seus projetos.\nFique a vontade para compartilhar tópicos que ache interessantes nos comentários!\nGostaria de agradecer ao Gustavo Santos Madeira por ter me apresentado estes conceitos sobre o Kafka me incentivado a ir mais a fundo nos estudos.\nCaso tenha alguma crítica, sugestão ou dúvida fique a vontade para me enviar uma mensagem:\nAté a próxima!\nReferências https://kafka.apache.org/ org.springframework.kafka.listener (Spring for Apache Kafka 3.1.4 API) Avro Schema Serializer and Deserializer for Schema Registry | Confluent Documentation Kafka Schema Registry \u0026 Avro: Spring Boot Demo (2 of 2) Implementação de uma Fila e de um Tópico JMS ","cover":"/posts/images/2024-04-13-descomplicando-a-configuracao-de-producers-e-consumers-com-kafka/cover.png","date":"2024-04-13","description":"","permalink":"https://jjeanjacques10.github.io/posts/2024-04-13-descomplicando-a-configuracao-de-producers-e-consumers-com-kafka/","tags":["Kafka","Spring Boot","Mensageria","Sistemas Distribuídos"],"title":"Descomplicando a Configuração de Producers e Consumers com Kafka"},{"content":"A habilidade mais importante para um desenvolvedor é a comunicação Nem sempre escrever código vai resolver os maiores desafios na carreira de um desenvolvedor\nPodemos dizer que 90% dos problemas de uma empresa são causados pela falta de comunicação. Isso em nível de colegas de equipe e até entre áreas inteiras. Então, como podemos melhorar a forma como nos comunicamos e evitar dores de cabeça com problemas simples que acabam crescendo com o tempo? Neste artigo, proponho-me a trazer algumas dicas de como se expressar melhor, transmitir ideias de forma escrita e verbal.\nMuito além das habilidades técnicas, um bom desenvolvedor precisa entender como compartilhar suas soluções e problemas com outras pessoas.\nO poder de um bom texto… Você já deve ter participado de uma reunião que poderia ter sido substituída por um e-mail… Frequentemente, ela ocorreu apenas porque acreditavam que seria “impossível” transmitir a ideia por escrito. Posso afirmar que a maioria delas realmente deveria ter sido um texto objetivo. No entanto, para alcançar esse patamar, é necessário compreender previamente como redigir textos de qualidade.\nCuidado com erros de digitação Conheça seu público algo Faça as perguntas certas Cuidado com erros de digitação O primeiro é o mais simples de todos: Evite equívocos de digitação ou de português, você não precisa redigir a redação do ENEM, nem ser formal constantemente. O dilema ocorre quando tais equívocos podem prejudicar a compreensão do leitor, portanto revise sempre que possível antes de clicar em “ENVIAR”. Alguns segundos adicionais não comprometerão sua oportunidade de compartilhar algo, mas substituir “significado” por “sinificado” pode gerar confusão para o leitor.\nConheça seu público O segundo aspecto está intimamente relacionado à elaboração de palestras; um palestrante competente sempre pesquisa o público-alvo de seu conteúdo, a fim de aprimorar a forma como é apresentado. No texto, devemos ter a mesma preocupação; não é adequado chegar a uma reunião com profissionais de negócios discorrendo sobre conceitos altamente técnicos. Vejamos um exemplo:\nA melhor forma de expressar isso seria abstraindo essas informações e focando no que é importante para esse público:\nFaça as perguntas certas O último ponto é importante independente da senioridade, ele se baseia no “saber perguntar”, um dos sites mais famosos para desenvolvedores é o stackoverflow, nele é crucial descrever de maneira precisa o problema a fim de buscar auxílio de programadores de todo o mundo. Essa prática também ocorre dentro das empresas, quando surge uma dúvida, é necessário enviar uma mensagem no Teams ou no Slack para alguém com maior experiência ou algum profissional de negócios.\nMuitas vezes partimos do pressuposta que as outras pessoas tem o mesmo contexto que nós, que sabem todo o caminho que passamos para chegar até aquele ponto, mas nem sempre (grande parte das vezes) não é este o caso. Quando entrei na área, esse foi um dos melhores conselhos que recebi: “Mostre como você chegou a essa conclusão”. Segue mais um exemplo:\nCostumo incluir também imagens, pois isso evita que a pessoa precise navegar por vários links para me auxiliar em algo simples. Caso seu colega possua alguma deficiência visual, adicione o texto, se possível, ou descreva o que anteriormente seria uma imagem. Quanto mais detalhes forem acrescentados, mais valor você está dando ao tempo de seus colegas de equipe.\nDon’t ask to ask, just ask Um site que encontrei por acaso e enfatiza bastante esse aspecto é o Don’t ask to ask, just ask. Ele concentra-se nas interações que ocorrem principalmente em fóruns da internet, onde alguém chega com a seguinte pergunta:\nComo mencionado no tópico sobre formular as perguntas adequadas, é necessário sempre trazer o contexto do problema, preferencialmente descrevendo as medidas já tomadas para tentar solucioná-lo.\nAo adotar esse método, você obterá uma resposta mais rápida, pois a pessoa não precisará enviar uma nova mensagem perguntando sobre qual tipo de ajuda você precisa. Além de ajudar quem for pesquisar sobre este assunto futuramente, muitas vezes será você mesmo.\nAqui tem alguns exemplos do próprio stackoverflow de como escrever bons questionamentos (tradução livre feita por mim):\nBad: C# Problema de matemática Good: Por que usar float em vez de int me dá resultados diferentes quando todas as minhas entradas são números inteiros?\nBad: [php] dúvida sobre sessões Good: Como posso redirecionar usuários para páginas diferentes com base em dados de sessão em PHP?\nBad: problemas com if-else no Android Good: Por que str == “valor” retorna falso quando str é definido como “valor”?\nIniciando uma conversa Outra questão bastante comum é como iniciar uma conversa. Provavelmente, você já recebeu uma mensagem no chat com um simples “Oi, tudo bem?” que permaneceu ali até que você respondesse com um “Oi” de volta. No entanto, essa troca pode levar tempo, enquanto novas prioridades surgem e você não tem ideia da urgência daquela saudação sem contexto. Não devemos deixar as pessoas em “espera” como em uma ligação telefônica. Seja direto, fornecendo todas as informações necessárias de forma objetiva junto à saudação.\nNão faça isso:\nPrefira uma abordagem direta:\nTechWriter Em nossa área temos diversas vertentes, uma delas é a de TechWriters, que são profissionais especializados em escrever e comunicar informações técnicas de maneira clara e compreensível. Eles criam documentação, manuais de usuário e guias de instruções, ajudando a traduzir conceitos complexos em linguagem acessível. Embora os desenvolvedores não precisem ser TechWriters, é importante que aprendam com esses profissionais a compartilhar informações, garantindo o sucesso e a usabilidade de produtos e de software.\nSe você tiver a oportunidade de trabalhar com estes profissionais peça algumas dicas, com certeza eles lhe ensinarão algo novo, assim como você ensinará a eles.\nInteligência artificial Uma ótima ferramenta para lhe ajudar a escrever textos melhores é a Inteligência artificial (IA). Essa tecnologia tem se mostrado cada vez mais promissora na hora de revisar textos. Através de algoritmos avançados, as ferramentas de IA podem identificar erros gramaticais, sugerir correções e até mesmo oferecer alternativas de palavras e frases para melhorar a clareza e a fluidez do texto.\nAlgumas das opções mais utilizadas atualmente incluem o ChatGPT, Clarice.ai, copy.ai e várias outras.\nSe você pretende utilizar essas ferramentas em ambientes corporativos, certifique-se de não violar nenhuma política interna relacionada ao compartilhamento de informações.\nTimidez, uma barreira para se expressar Aqui está outra vantagem de se expressar por texto. Sempre fui uma pessoa muito tímida e busquei por muito tempo não dar minha opinião em conversas no trabalho. Por isso, comecei a escrever. Na escrita, você se expõe menos e tem a chance de revisar mais de uma vez o que será enviado. Apenas tome cuidado para não ficar lapidando um texto até chegar na versão “perfeita”, porque ela não existe. É mais importante você ter uma versão direta com as informações mais importantes do que cobrir todos os cenários.\nAlém disso, a comunicação escrita também proporciona uma forma de registro e referência para futuras consultas. Ao expressar ideias por meio de textos, você cria um histórico que pode ser utilizado como base para tomadas de decisão, análises retrospectivas e aprendizados.\nAlgumas dicas extras Leia bastante, quem escreve bem é aquele que lê muito Pegue feedback com seus colegas de trabalho Não tenha medo de expor suas ideias Conclusão Escrevi esse artigo para compartilhar com vocês ações que me auxiliaram muito em minha carreira, ainda estou aprendendo a me expressar melhor a transmitir minha ideias para as pessoas, isso tudo é um processo que só vai melhorar com muita prática, assim como programar!\nPratique realizando anotações sobre a tarefa que está fazendo, se possível documente seu projeto, vai ajudar você e será uma forma fácil de compartilhar com novas pessoas que entrarem em sua equipe (sem passar horas em reuniões de overview).\nEspero que tenha gostado e sempre que ver alguém fazendo uma pergunta sem muito contexto compartilhe esse artigo com essa pessoa! Vamos melhorar a forma que nos comunicamos e criar um mundo com menos desentendimentos!\nCaso tenha alguma crítica, dúvida ou sugestão, fique à vontade para comentar abaixo ou nos envie uma mensagem:\nJean Jacques, Backend Software Engineer — https://www.linkedin.com/in/jjean-jacques10/\nAté a próxima!\nReferências https://dontasktoask.com/ https://nohello.net/en/ http://catb.org/~esr/faqs/smart-questions.html https://stackoverflow.com/help/how-to-ask ","cover":"/posts/images/2023-06-09-a-habilidade-mais-importante-para-um-desenvolvedor-e-a-comunicacao/cover.png","date":"2023-06-09","description":"","permalink":"https://jjeanjacques10.github.io/posts/2023-06-09-a-habilidade-mais-importante-para-um-desenvolvedor-e-a-comunicacao/","tags":["Carreira","Comunicação","Engenharia de Software","Documentação"],"title":"A habilidade mais importante para um desenvolvedor é a comunicação"},{"content":"Perspectivas futuras: A visão de um desenvolvedor sobre o avanço da Inteligência Artificial Trago uma perspectiva do que imagino para o futuro com base nos meus estudos como entusiasta de IA Desde pequeno, ouço falar sobre Inteligência Artificial. Meu primeiro contato foi por meio dos filmes de ficção científica: “Eu, Robô” (2004) e “A.I. — Inteligência Artificial” (2001). Depois de vários anos, quando comecei a programar, o tema voltou à tona junto com Data Science e a “febre dos dados”, todos precisavam gerar dados para se manter no mercado e aproveitar os benefícios das IAs. Por volta de 2016, comecei a estudar mais a fundo e até considerei me tornar pesquisador nessa área. Hoje, sou apenas um entusiasta de IA e trabalho como desenvolvedor back-end Java.\nDepois daquela primeira explosão em que todos falavam sobre Inteligência Artificial, o mercado meio que se acalmou. Agora, em 2023, estamos presenciando mais uma onda de pessoas se surpreendendo com a capacidade dos algoritmos que podem automatizar nossos trabalhos.\nNão é de hoje esse alarde para a perda de empregos Meu primeiro trabalho como programador foi criar uma página em WordPress para consumir uma API que retornava “As carreiras que vão desaparecer em 10 anos”, com base em uma pesquisa que dizia o seguinte:\n“Até 2026, 30 milhões de vagas com carteira assinada poderão ser fechadas à medida que robôs avançam e ganham espaço. É o que aponta estudo realizado pelo Laboratório de Aprendizado de Máquina em Finanças e Organizações (LAMFO) da UnB (Universidade de Brasília), que avaliou 2,6 mil ocupações brasileiras utilizando denominações de cargos e funções do antigo Ministério do Trabalho e Emprego. A opinião especializada de 69 acadêmicos e profissionais atuantes em aprendizado de máquinas foi levantada para embasar o cálculo das probabilidades de automação.” — Robôs podem substituir 54% dos empregos formais do Brasil, prevê UNB — IT Forum\nEssa não é a única pesquisa que traz alertas como esses, mas um ponto interessante sobre a maioria dessas pesquisas é que foram realizadas antes da pandemia. Esse evento mudou de várias formas a maneira como as pessoas interagem entre si, e também a forma que consomem produtos, gerando necessidades de mercado que se alteraram significativamente desde então.\nEsse foi um ponto que levantei durante a leitura do livro “Inteligência artificial — Como os robôs estão mudando o mundo”, escrito por Kai-Fu Lee. Trata-se de uma obra excelente e que recomendo. O livro foi lançado em 2019, antes dos impactos da pandemia, e aborda algumas das mudanças que já estavam surgindo naquela época, incluindo pesquisas que alertavam sobre a perda em massa de empregos para sistemas automatizados. Minha perspectiva sobre o assunto é baseada nessa obra e em outras que li ao longo dos anos. Portanto, irei discorrer sobre os impactos atuais e avançarei para possíveis cenários futuros…\nFerramentas Atualmente venho utilizando bastante os modelos generativos, como o GitHub Copilot e o ChatGPT, para me dar um apoio no dia a dia de trabalho. Venho percebendo um aumento na minha produtividade por conta destas ferramentas, assim como as linguagens de programação, que mostram para os programadores outra forma de fazer suas tarefas.\nTivemos diversas revoluções com o tempo… Cada geração de desenvolvedores teve o seu apoio:\nLivros -\u003e Fóruns na internet -\u003e StackoverFlow -\u003e Copilot\nApenas trocamos a página (ou aba, por assim dizer) de onde encontramos as repostas para nossas dúvidas. Não adianta correr disso!\nDeixo aqui algumas sugestões de ferramentas que venho utilizando:\nGitHub Copilot: https://copilot.github.com/ ChatGPT: https://chatbot.gpt.dobro.ai/ Phind.ai: https://phind.ai/ Vamos perder nossos empregos? Quanto à possibilidade do cargo de desenvolvedor de software desaparecer, eu adotaria uma abordagem mais realista: as IAs não eliminam “empregos”, mas sim funções específicas. E quais são as principais funções de um desenvolvedor?\nAnálise de requisitos Alinhar expectativas com os clientes Design e arquitetura de software Codificação Documentação Trabalho em equipe Manutenção e suporte Embora existam muitas outras funções, com base nas mencionadas acima, quais você acredita que um robô seria capaz de realizar? A resposta é: todas as tarefas repetitivas e tediosas, mas aquelas que envolvem empatia ainda estão longe de serem realizadas por IAs especialistas. Talvez nossos cargos mudem novamente (como já aconteceu várias vezes desde o surgimento do WebMaster haha) e tenhamos que aprender novas habilidades para nos mantermos no mercado de trabalho. No entanto, isso não deve ser assustador, especialmente para aqueles que trabalham com tecnologia, pois já deveriam estar acostumados com mudanças e disrupções.\nPara quem está começando na área… Se você está buscando entrar agora no mercado de trabalho como desenvolvedor, não há muita diferença em relação ao que estava acontecendo quando eu me candidatei para minha primeira vaga. Foque em resolver os problemas atuais com as ferramentas disponíveis, mas mantenha-se atualizado sobre as novidades que surgem e tente aplicá-las no seu cotidiano, isso sempre será visto como um diferencial e irá ajudar você a criar uma mentalidade flexível.\n“Ser um(a) programador(a) vai além de escrever código; é ser capaz de trazer soluções inovadoras para os clientes, utilizando ferramentas e aplicando um pensamento sistêmico” Perspectivas de futuro Curto prazo Existem dois tipos de IA: especialistas e generalistas. As especialistas são treinadas para realizar tarefas específicas, como reconhecimento de imagens e de voz. As generalistas têm a capacidade de desempenhar diversas tarefas, embora ainda não tenhamos uma IA de propósito geral que possa resolver todos os nossos problemas. Nesse sentido, é importante estudar os impactos que teremos na indústria, no mercado e no meio acadêmico, visando desenvolver um plano de ação bem estruturado. Esse processo requer o envolvimento de diversas áreas, como Filosofia, Sociologia, Psicologia e, claro, a própria Tecnologia.\nNa minha visão, nos próximos anos veremos uma combinação do trabalho humano com o trabalho realizado pelas máquinas, o que já ocorre no meu dia a dia como desenvolvedor. Ambos irão se complementar. A Inteligência Artificial assumirá as tarefas pesadas, repetitivas e não criativas, enquanto os seres humanos se dedicarão ao trabalho criativo. Aqueles que não estão na área de tecnologia precisarão adotar a mesma mentalidade de adaptação, abandonando velhos hábitos para se manterem produtivos em um mundo onde essa habilidade será cada vez mais valorizada.\nDaqui para frente, existem 2 cenários possíveis:\nVamos perder empregos para a automatização e o mundo vai entrar em colapso Vamos perder empregos para a automatização e o mundo vai entrar em uma nova era de prosperidade Longo Prazo Para o longo prazo, prevejo muitas funções desaparecendo e a necessidade de profissionalização. Por exemplo, no caso do pacote Office: antes esse software ajudava nas tarefas que já eram realizadas, mas agora vamos precisar que as pessoas aprendam novas profissões e muitas delas ainda nem existem. Com o tempo, departamentos inteiros de empresas vão desaparecer, e precisaremos de uma coordenação entre os governos e as empresas privadas para entender como lidar com pessoas que não serão mais necessárias no mercado de trabalho. Não haverão tantos empregos como hoje, sendo que até no cenário atual faltam oportunidades de trabalho.\nAtualmente, as empresas privadas, principalmente aquelas sediadas na China e nos Estados Unidos, são líderes na criação e posse dos modelos de IA, o que direciona os lucros para essas nações. Essa realidade gera uma preocupante desigualdade social, que já é evidente hoje, mas que tende a se agravar caso os governos e empresas não atuem de forma global para mitigar esse cenário. Esse é um dos pontos levantados por Kai-Fu Lee em seu livro, sobre como a substituição dos empregos irá atingir os profissionais com formação superior e teremos desemprego generalizado, mesmo com pessoas migrando de uma área para a outra, não haverá mercado o suficiente para tantos profissionais. A partir daí, precisaremos pensar em formas de distribuir essa renda que se contra em poucos países e empresas. Caso contrário, teremos um caos social.\nDeixando claro que essas são perspectivas para um futuro distante, não acredito estar vivo para ver essa mudança drástica no modelo econômico mundial.\nConclusão Como mencionei, esses modelos inteligentes têm me ajudado há algum tempo e vejo que estão se popularizando cada vez mais. Acredito que daqui para frente a área que mais precisaremos investir seja a educação, pois nenhuma geração vivenciou, em um curto espaço de tempo, a quantidade massiva de mudanças que está por vir. Precisamos aprender tanto quanto os modelos artificiais que estamos ensinando.\nEstou animado para acompanhar os próximos passos que daremos em direção à uma sociedade que cresce junto com o desenvolvimento de novas tecnologias potencializadas pela Inteligência Artificial. Tenho, sim, minhas preocupações, principalmente em relação aos pontos que mencionei sobre desigualdade, mas acredito que podemos encontrar uma solução como sociedade para esses problemas!\nCaso tenha alguma crítica, dúvida ou sugestão, fique à vontade para comentar abaixo ou nos envie uma mensagem:\nJean Jacques, Backend Software Engineer — https://www.linkedin.com/in/jjean-jacques10/\nGabriel Petillo, Product Owner — https://www.linkedin.com/in/gabrielpetillo/\nAté a próxima!\nReferências Inteligência artificial — Kai-Fu Lee Robôs podem substituir 54% dos empregos formais do Brasil, prevê UNB — IT Forum Introdução a inteligência artificial | by Jean Jacques Barros | Medium ","cover":"/posts/images/2023-05-19-perspectivas-futuras-a-visao-de-um-desenvolvedor-sobre-o-avanco-da-inteligencia-artificial/cover.png","date":"2023-05-19","description":"","permalink":"https://jjeanjacques10.github.io/posts/2023-05-19-perspectivas-futuras-a-visao-de-um-desenvolvedor-sobre-o-avanco-da-inteligencia-artificial/","tags":["Inteligência Artificial","Carreira","Tecnologia","Engenharia de Software"],"title":"Perspectivas futuras: A visão de um desenvolvedor sobre o avanço da Inteligência Artificial"},{"content":"Como usar e criar Annotations em Kotlin — Spring Framework Este é um guia que crie para auxiliar na criação de annotations customizadas no Spring Framework utilizando Kotlin Trabalhando com Spring, uma das primeiras coisas que se nota é a quantidade de annotations que precisamos utilizar. Sendo que elas têm a finalidade de fornecer informações adicionais tanto para o compilador quanto para o Spring Framework, indicando como lidar com determinadas classes, métodos ou propriedades.\nComo exemplo temos o @Service, @Controller, @Component e dentre várias outras comumente utilizadas. Mesmo com um grande número de anotações disponíveis por padrão pode ser interessante criar a sua própria, nesse artigo vou explicar um pouco sobre como criar e utilizar anotações em Kotlin.\nRepositório projeto de exemplo:\nTodos esses exemplos podem ser encontrados no seguinte repositório no GitHub: https://github.com/jjeanjacques10/annotations-kotlin-spring-boot\nPondo a mão na massa 👨🏻‍💻 Para isso vamos criar uma nova aplicação Spring Boot para realizar o cadastro de livros. Vamos começar adicionando o seguinte controller, observe que estou utilizando o @Valid, essa annotation é utilizada para validar os dados de entrada de uma requisição, sem ela as próximas configurações na classe BookRequest não funcionarão.\nimport javax.validation.Valid @RestController class BookController { @PostMapping(\"/book\") fun create(@Valid @RequestBody book: BookRequest): Map\u003cString, Any?\u003e { return mapOf( \"status\" to 200, \"details\" to \"Book created - \".plus(book.title) ) } } Utilizando anotações de validação do Spring Um dos usos mais comuns quando trabalhamos com API’s Rest é validar os dados de entrada enviados pelo usuário. Para isso podemos importar a lib javax.validation.constraints, nela temos várias anotações que podem ser utilizadas para validar os dados de entrada. A classe BookRequest representa os dados de entrada que serão enviados pelo usuário.\nimport javax.validation.constraints.NotBlank data class BookRequest( @field:NotBlank val title: String? = \"\" ) Nela temos o atributo title que possui acima dele o @NotBlank, essa annotation indica que o atributo não pode ser nulo e nem vazio. Temos diversas possibilidades quando utilizamos essa lib de validação, seguem as principais:\n@NotNull — Indica que o atributo não pode ser nulo @NotEmpty — Indica que o atributo não pode ser nulo ou vazio @NotBlank — Indica que o atributo não pode ser nulo ou vazio e não pode conter apenas espaços em branco @Size(min = 1, max = 10) — Indica que o atributo deve ter no mínimo 1 e no máximo 10 caracteres @Email — Indica que o atributo deve ser um email válido @Pattern(regexp = “^[a-zA-Z0–9]*$”) — Indica que o atributo deve seguir o padrão regex informado ❗ Importante: Passei um tempo quebrando a cabeça por não saber que no caso de validações em Kotlin precisamos utilizar o @field:\u003cannotation\u003e para que a anotação seja aplicada ao atributo\nPor baixo dos panos 🕵🏼‍♂️ Vamos olhar um pouco mais fundo para entender como que essa flag com @ na frente funciona. Quando utilizamos uma annotation em Kotlin estamos utilizando uma feature chamada reflection, que basicamente permite que o código acesse informações sobre si mesmo em tempo de execução. Com isso podemos acessar informações sobre classes, métodos, atributos e etc.\nSe entrarmos em uma das anotações do Spring, como por exemplo a @NoEmpty, podemos ver que ela é uma interface que herda de @Constraint, @Target e @Retention.\n@Constraint(validatedBy = {}) @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) @Retention(RetentionPolicy.RUNTIME) public @interface NotEmpty { ... } Existem mais anotações que compõe NotEmpty, mas para fins didáticos não vou entrar em detalhes.\n@Target especifica os tipos possíveis de elementos que podem ser anotados. Os valores possíveis são:\nCLASS: Pode ser aplicada em classes, interfaces e enums. FUNCTION: Pode ser aplicada em funções e construtores. PROPERTY: Pode ser aplicada em propriedades de classes. FIELD: Pode ser aplicada em campos de classes. @Retention especifica se a annotation é armazenada nos arquivos de classe compilados e se ela é visível por meio de reflection em tempo de execução (por padrão, ambas são verdadeiras). Existem três possíveis valores para essa propriedade:\nSOURCE: É descartada pelo compilador e não está presente no bytecode gerado. CLASS: É mantida no bytecode, mas não está disponível em tempo de execução. RUNTIME: É mantida no bytecode e também está disponível em tempo de execução. Esse é o básico de como funcionam as configurações das principais annotations no Spring. Vamos agora ver como podemos criar nossas próprias aplicando esses conceitos.\nCriando uma annotation customizada Mas não apenas do padrão vive o dev, às vezes nossos projetos pedem por comportamentos específicos que o framework não está apto a fornecer. Nesse caso podemos criar nossas próprias annotations, vamos então adicionar a validação se o gênero do livro é válidos.\nComo comentado anteriormente, precisamos ter o @Target, que nesse caso será FIELD, pois queremos que apenas seja utilizada em atributos de classes. Também precisamos do @Retention, sendo ele definido como RUNTIME, já que esse tipo de validação é feita em tempo de execução. E por fim, a mais importante, a annotation @Constraint, nela que definimos a regra de validação que será utilizada.\nimport javax.validation.Constraint import javax.validation.Payload import kotlin.reflect.KClass @MustBeDocumented @Target(AnnotationTarget.FIELD) @Retention(AnnotationRetention.RUNTIME) @Constraint(validatedBy = [GenreValidator::class]) annotation class Genre( val message: String = \"O gênero do livro é inválido\", val groups: Array\u003cKClass\u003c*\u003e\u003e = [], val payload: Array\u003cKClass\u003cout Payload\u003e\u003e = [] ) Definimos na annotation @Genre três parâmetros: message, groups e payload. O primeiro é a mensagem de erro que será exibida caso a validação falhe, o segundo é um array de classes que podem ser utilizadas para agrupar validações e o terceiro também um array de classes que podem ser utilizadas para definir payloads customizados. Vamos preencher apenas a mensagem de erro, pois os outros parâmetros não serão utilizados nesse exemplo.\nenum class GenreType { FANTASY, SCI_FI, ROMANCE, MYSTERY, HORROR, THRILLER, NON_FICTION } class GenreValidator : ConstraintValidator\u003cGenre, String\u003e { override fun isValid(value: String?, context: ConstraintValidatorContext?): Boolean { if (value == null) false return try { GenreType.valueOf(value!!) true } catch (e: IllegalArgumentException) { false } } } A lógica da constraint se encontra na classe GenreValidator, nela apenas verificamos se o valor do atributo está presente na lista de GenreType, caso não esteja ele retornará que esse valor é inválido. Agora vamos aplicar essa annotation na classe BookRequest.\ndata class BookRequest( @field:Genre val genre: String ) Testando a annotation customizada Agora com nossas annotations criadas vamos testar se elas estão funcionando corretamente. Quando realizamos uma chamada na API com um gênero inválido o retorno será 422 Unprocessable Entity.\nTambém podemos retornar uma lista com diversos erros, como por exemplo, quando o título do livro é nulo e o gênero é inválido:\nAo realizar a chamada na API com valores válidos o retorno será 200 OK, mostrando que o livro foi criado com sucesso.\nConclusão Passei um bom tempo estudando sobre annotations no Spring para poder utilizar em um projeto, esse conteúdo é um resumo de tudo que pesquisei e consegui aplicar, espero que tenha lhe ajudado em sua jornada.\nCaso tenha alguma crítica, sugestão ou dúvida fique a vontade para me enviar uma mensagem:\nAté a próxima!\nReferências Doc Kotlin Annotations https://kotlinlang.org/docs/annotations.html\nKotlin Spring validation not working https://youtrack.jetbrains.com/issue/KT-50511/Kotlin-Spring-validation-not-working\n","cover":"/posts/images/2023-03-02-como-usar-e-criar-annotations-em-kotlin-spring-framework/cover.png","date":"2023-03-02","description":"","permalink":"https://jjeanjacques10.github.io/posts/2023-03-02-como-usar-e-criar-annotations-em-kotlin-spring-framework/","tags":["Kotlin","Spring Boot","APIs","Engenharia de Software"],"title":"Como usar e criar Annotations em Kotlin — Spring Framework"},{"content":"Estratégias utilizando Application Auto Scaling com ECS Como criar regras personalizadas com o Application Auto Scaling em aplicações AWS ECS utilizando CloudFormation Uma das grandes vantagens de utilizarmos a AWS ou qualquer outro provedor de Cloud é a possibilidade de pagar apenas pelos recursos que utilizamos. Uma ferramenta extremamente útil para gerenciar estes recursos de forma eficiente, evitando gastos desnecessários, é o Auto Scaling ou dimensionamento automático. Esta funcionalidade permite aumentar ou diminuir de forma dinâmica a quantidade desejada de tasks (máquinas rodando) em serviços como o Amazon Elastic Computer Service (ECS). Dessa forma, garantimos que a quantidade de tasks esteja adequada às necessidades da aplicação, optimizando custos e também mantendo nosso serviço disponível mesmo com o aumento de demanda.\nO Amazon ECS oferece diversas vantagens na hora de configurar estratégias de escalonamento. Podemos personalizar essas configurações de acordo com o tipo de serviço, como por exemplo aplicações de e-commerce no período de Black Friday ou aquelas com poucos acessos durante a noite, cenários que envolvem uma carga de trabalho variável.\nHoje vou apresentar algumas estratégias que podem ser úteis em seus projetos visando o escalonamento horizontal, replicando as máquinas ao invés de aumentar o tamanho delas.\nLembrando que todos os códigos apresentados nesse artigo podem ser encontrados no seguinte repositório do GitHub: https://github.com/jjeanjacques10/springboot-ecs-fargate\nComo funciona o Application Auto Scaling O AWS Application Auto Scaling é um serviço que permite escalar automaticamente seus recursos Amazon EC2, Amazon ECS, Amazon EKS, Amazon DynamoDB e Amazon Aurora em resposta à demanda de aplicativos em constante mudança.\nNo exemplo acima temos o fluxo de Auto Scaling, para definir o recurso a ser dimensionado criamos um Scalable Target, que especifica a capacidade mínima e máxima desse recurso e também as políticas de dimensionamento dele, que determinam como o recurso aumenta ou diminui em resposta a mudanças na demanda. O serviço ECS (Digimon Service) está se comunicando o tempo inteiro com o CloudWatch e enviando métricas para os alarmes presentes nele que serão gerenciados pelo Application Auto Scaling e tem as regras definidas no Scaling Policy.\nScalableTarget: Type: AWS::ApplicationAutoScaling::ScalableTarget DependsOn: Service Properties: MinCapacity: 2 MaxCapacity: 6 ResourceId: !Join - \"/\" - - service - !Ref EcsClusterName - !GetAtt Service.Name RoleARN: !GetAtt AutoScalingRole.Arn ScalableDimension: ecs:service:DesiredCount ServiceNamespace: ecs Agora para especificar como e quando escalar precisamos do Scaling Policy, vamos seguir então para alguns exemplos, começando pelo mais comum de encontrar em aplicações…\nUso de CPU ou Memória Esse tipo de métrica utilizada no Scaling Policy é comum porque utiliza características de nossos serviços que dizem muito sobre o status atual da aplicação a nível de hardware. Nestes casos devemos definir um valor alvo a ser atingido (TargetValue) em qualquer uma das duas métricas, CPU ou memória RAM, e quando o valor é alcançado, como por exemplo 30% de uso da CPU, temos o aumento no número de tarefas rodando até o limite pré-estabelecido no Scalable Target. No caso do exemplo abaixo são no máximo 6 tasks e no mínimo 2 tasks.\nScalingPolicy: Type: AWS::ApplicationAutoScaling::ScalingPolicy Properties: PolicyName: ScaleWithCPUUtilization PolicyType: TargetTrackingScaling ScalingTargetId: !Ref ScalableTarget TargetTrackingScalingPolicyConfiguration: PredefinedMetricSpecification: PredefinedMetricType: ECSServiceAverageCPUUtilization TargetValue: 30.0 Para trocar para o verificar 30% do uso de memória alteramos o PredefinedMetricType para “ECSServiceAverageMemoryUtilization”.\nEsse tipo de métrica pode ser utilizado para qualquer serviço, mas existem alguns cenários que outros tipos de alarmes podem ser mais interessantes. Por exemplo, podemos aumentar o número de tarefas observando serviços externos como filas SQS e utilizar a métrica de…\nQuantidade de mensagens na fila Quando trabalhamos com consumers a principal métrica utilizada é a quantidade de itens a serem processados, para isso podemos olhar para um SQS na AWS e de acordo com o aumento no número de mensagens antigas em uma fila disponibilizar mais tasks ou remover essa capacidade extra caso não tenha nada para ser processado.\nPondo a mão na massa vamos então vamos criar nossas Policies. Um ponto importante de saber é que podemos ter várias Policies, mas cada policy só pode ser dimensionada em uma única direção, por essa razão precisamos de duas neste caso.\nScalingPolicySQSUp: Type: AWS::ApplicationAutoScaling::ScalingPolicy Properties: PolicyName: ScaleWithSQSUp PolicyType: StepScaling ScalingTargetId: !Ref ScalableTarget StepScalingPolicyConfiguration: AdjustmentType: ChangeInCapacity Cooldown: 60 MetricAggregationType: Average StepAdjustments: - MetricIntervalLowerBound: 0 MetricIntervalUpperBound: 100 ScalingAdjustment: 1 - MetricIntervalLowerBound: 100 ScalingAdjustment: 2 A primeira é referente ao UpScaling, nela será adicionado 1 contêiner quando a fila estiver entre 1000 e 1100, mas adicionará 2 quando houverem mais itens. Agora para diminuir o número de tasks quando cair a demanda criamos outra Policy com valores negativos, nesse caso quando estiver entre 900 e 999 itens será removida 1 task.\nScalingPolicySQSDown: Type: AWS::ApplicationAutoScaling::ScalingPolicy Properties: PolicyName: ScaleWithSQSDown PolicyType: StepScaling ScalingTargetId: !Ref ScalableTarget StepScalingPolicyConfiguration: AdjustmentType: ChangeInCapacity Cooldown: 300 MetricAggregationType: Average StepAdjustments: - MetricIntervalUpperBound: 0 ScalingAdjustment: -1 Criadas as ações vamos agora configurar os alarmes no CloudWatch do tipo ApproximateNumberOfMessagesVisible. Vamos precisa de dois alarmes o primeiro é para que quando a fila “digimon-queue” atingir 1000 mensagens, nesse momento a Action de “ScalingPolicySQSUp” será ativada para aumentar a capacidade. O segundo é para nos alertar de quando esse valor diminuir e assim sermos capazes de diminuir o número de tasks ativando a Action “ScalingPolicySQSDown”.\n# Use SQS Queue to scale up CloudWatchSQSAlarmScalingUp: Type: \"AWS::CloudWatch::Alarm\" Properties: AlarmName: \"Alarm SQS Up\" AlarmDescription: \"Alarm if our SQS queue is higher than usual.\" Namespace: AWS/SQS EvaluationPeriods: 2 Statistic: Average MetricName: ApproximateNumberOfMessagesVisible ComparisonOperator: GreaterThanThreshold Period: 60 Dimensions: - Name: QueueName Value: digimon-queue Threshold: 1000 AlarmActions: - !Ref ScalingPolicySQSUp # Use SQS Queue to scale down CloudWatchSQSAlarmScalingDown: Type: AWS::CloudWatch::Alarm Properties: AlarmName: \"Alarm SQS Down\" AlarmDescription: \"Alarm if our SQS queue is smaller than usual.\" Namespace: AWS/SQS EvaluationPeriods: 1 Statistic: Average MetricName: ApproximateNumberOfMessagesVisible Threshold: !Ref SqsMessagesVisibleThresholdScaleDown ComparisonOperator: LessThanOrEqualToThreshold Period: 60 Dimensions: - Name: QueueName Value: digimon-queue Threshold: 1000 AlarmActions: - !Ref ScalingPolicySQSDown Quantidade de chamadas Quando se trabalha com API’s uma ótima métrica para utilizar é a de quantidade de chamadas recebidas, principalmente nos cenários onde já temos a quantidade de transações por segundo (TPS) mapeada. A criação das Scaling Policies é muito parecido com o de itens na fila, a maior diferença é o Alarme no CloudWatch que será utilizado aqui.\nScalingPolicyTCPUp: Type: AWS::ApplicationAutoScaling::ScalingPolicy Properties: PolicyName: ScaleWithFlowCountTCPStepPolicyUp PolicyType: StepScaling ScalingTargetId: !Ref ScalableTarget StepScalingPolicyConfiguration: AdjustmentType: ChangeInCapacity Cooldown: 60 MetricAggregationType: Average StepAdjustments: - MetricIntervalLowerBound: 0 ScalingAdjustment: +1 ScalingPolicyTCPDown: Type: AWS::ApplicationAutoScaling::ScalingPolicy Properties: PolicyName: ScaleWithFlowCountTCPStepPolicyDown PolicyType: StepScaling ScalingTargetId: !Ref ScalableTarget StepScalingPolicyConfiguration: AdjustmentType: ChangeInCapacity Cooldown: 60 MetricAggregationType: Average StepAdjustments: - MetricIntervalUpperBound: 0 ScalingAdjustment: -1 Agora os alarmes que temos que criar são usados para monitorar as requisições recebidas e acionar um Grupo de Auto Scaling para aumentar ou diminuir com base no número de solicitações. O alarme compara o número de solicitações TCP nos últimos 60 segundos com o limite de 100 solicitações. Se o número de solicitações for maior que o limite, o alarme acionará ScalingPolicyTCPUp e caso seja menor acionará o ScalingPolicyTCPDown para dimensionar o Grupo de Auto Scaling de acordo com a regra pré-definida.\n# Use Active flow count to scale up HighTpsAlarm: Type: AWS::CloudWatch::Alarm Properties: AlarmDescription: !Sub '${MicroServiceName} - High volume of requests' EvaluationPeriods: 1 Threshold: 100 ComparisonOperator: GreaterThanOrEqualToThreshold Period: 60 Statistic: Sum MetricName: ActiveFlowCount Dimensions: - Name: LoadBalancer Value: !GetAtt LoadBalancer.LoadBalancerFullName Namespace: AWS/NetworkELB AlarmActions: - !Ref ScalingPolicyTCPUp # Use Active flow count to scale down LowTpsAlarm: Type: AWS::CloudWatch::Alarm Properties: AlarmDescription: !Sub '${MicroServiceName} - Low volume of requests' EvaluationPeriods: 1 Threshold: 90 ComparisonOperator: LessThanOrEqualToThreshold Period: 60 Statistic: Sum MetricName: ActiveFlowCount Dimensions: - Name: LoadBalancer Value: !GetAtt LoadBalancer.LoadBalancerFullName Namespace: AWS/NetworkELB AlarmActions: - !Ref ScalingPolicyTCPDown No momento em que o alarme é acionado, temos a ação sendo ativada, como demonstrado no exemplo abaixo, onde podemos observar o número de tarefas aumentando após diversas requisições.\nPara verificar se essa configuração está certa podemos acessar nosso serviço ECS e entrar na aba “Auto Scaling”. Nela são apresenados detalhes da nossa configuração, como as regras de escalonamento e a política de subida e descida. Se a configuração estiver correta, esses parâmetros serão exibidos de acordo com as configurações feitas.\nHorário (CRON) Também é possível gerencia a quantidade de tarefas rodando por meio de Schedules que identificam o horário definido para aumentar ou diminuir o número de tasks.\nPara definir qual horário nossa ação deve rodar podemos utilizar o CRON, que é um utilitário normalmente utilizado em linha de comando para agendar ações a serem realizadas no sistema.\nQuando for configurar lembre-se que o tempo é em UTC, precisando então adicionar 3 horas (para quem está no Brasil)\nSeguem um exemplo onde definimos que todo dia às 8h00 (11 UTC) iremos seguir uma regra de capacidade e a partir das 20h00 iremos parar todas as tarefas rodando.\nScalableTarget: \u003c...\u003e ScheduledActions: - ScalableTargetAction: MinCapacity: 0 MaxCapacity: 0 Schedule: 'cron(0 0 22 ? * MON-FRI)' ScheduledActionName: scale-down - ScalableTargetAction: MinCapacity: 2 MaxCapacity: 6 Schedule: 'cron(0 0 10 ? * MON-FRI)' ScheduledActionName: scale-up Esta é uma excelente configuração para usar em ambientes de desenvolvimento ou teste, pois economiza recursos que normalmente não são usados em certos horários!\nCom o Scaling Policy são diversas as possibilidades, aqui tem a documentação com todos os valores possíveis do campo PredefinedMetricSpecification. Lá temos alguns outros exemplos utilizando DynamoDB, conexões RDS, Storage Kafka e muito mais!\nConclusão Apresentei as principais estratégias de como utilizar Auto Scaling e suas aplicações, o ponto mais importante é entender o melhor cenários para utilizar cada uma delas. Muitas vezes, acabamos não monitorando a métrica certa, o que acarreta problemas para os nossos aplicativos.\nEspero que esse conteúdo o tenha ajudado à entender melhor como implementar Auto Scaling em aplicações ECS na AWS!\nCaso tenha alguma crítica, sugestão ou dúvida fique à vontade para me enviar uma mensagem:\nAté a próxima!\nReferências AWS::ApplicationAutoScaling::ScalableTarget ScalableTargetAction — AWS CloudFormation (amazon.com) Programar expressões usando rate ou cron — AWS Lambda (amazon.com) aws-cloudformation-user-guide/aws-properties-applicationautoscaling-scalabletarget-scheduledaction.md at main · awsdocs/aws-cloudformation-user-guide (github.com) Auto Scaling Microservices on ECS | by Idan Lupinsky | The Startup | Medium ","cover":"/posts/images/2022-12-02-estrategias-utilizando-application-auto-scaling-com-ecs/cover.png","date":"2022-12-02","description":"","permalink":"https://jjeanjacques10.github.io/posts/2022-12-02-estrategias-utilizando-application-auto-scaling-com-ecs/","tags":["AWS","ECS","Escalabilidade","Arquitetura de Software"],"title":"Estratégias utilizando Application Auto Scaling com ECS"},{"content":"Atualizando dados no DynamoDB utilizando AWS Glue Uma forma simples de atualizar um grande número de registros no banco de dados não relacional utilizam a ferramenta AWS Glue Quando trabalhamos com grandes massas de dados diversas abordagens surgem na mente para realizar atualizações em lotes. Contudo, nem sempre essas soluções são práticas e dependem de configuração de uma infraestrutura e da criação de aplicações completas para um job que deveria ser simples de se fazer, como popular um campo ou removê-lo.\nUma das ferramentas que podemos utilizar nesses casos é o AWS Glue, que é uma plataforma serverless de ETL (Extract, Transform, Load), orientada a eventos fornecida pela Amazon. É um serviço de computação que executa código em resposta à eventos e gerência automaticamente os recursos de computação exigidos por esse código.\nTodos os códigos apresentados nesse artigo podem ser encontrados no seguinte repositório do GitHub: https://github.com/jjeanjacques10/update-dynamodb-glue\nCenário Como exemplo temos essa tabela no DynamoDB onde apenas as informações de número e nome de nossos Pokémon estão cadastradas. Surgiu uma nova regra de negócio e nosso cliente precisa que sejam retornadas as categorias de cada Pokémon, para que assim não seja necessário buscar em fontes externas. Vamos então realizar esse processo de atualização para ele!\nNesse exemplo temos 9 itens cadastrados (no total são 905 criaturinhas no mundo do anime), mas poderíamos ter terabytes de informação nesse tipo de base e realizar manualmente à atualização desses dados levaria muito tempo, sem falar que é necessário filtrar apenas os dados que precisam ser atualizados, assim evitando consumir recursos de forma desnecessária no DynamoDB, que é cobrado por capacidade de leitura e escrita.\nCriando AWS Glue ETL Job Adicionando as permissões Para poder executar o script Glue é necessário ter uma Role com os acessos necessários, o primeiro é o Trusted Entity do tipo Service, onde iremos selecionar o serviço do Glue.\nCom o arquivo de Role gerado sendo o seguinte:\n{ \"Version\": \"2012-10-17\", \"Statement\": [ { \"Effect\": \"Allow\", \"Principal\": { \"Service\": \"glue.amazonaws.com\" }, \"Action\": \"sts:AssumeRole\" } ] } Como iremos trabalhar com o DynamoDB teremos que adicionar a Policy que libera o acesso à esse banco de dados e também ao S3 que é onde o nosso arquivo script.py será armazenado.\nVocê não precisa dar o FullAccess, a melhor prática é adicionar apenas as permissões necessárias, como por exemplo apenas leitura ou escrita.\nCom a Role glue-pokemon-role criada e todas as permissões configuradas podemos ir para o desenvolvimento do script que vai fazer a mágica acontecer.\nScript Então vamos para a criação do nosso PySpark Script. O primeiro ponto é realizar o setup onde é criado o contexto e a sessão que iremos utilizar quando esse script for executado. Também temos aqui as importações que serão necessárias mais para frente.\nimport sys import os import requests from awsglue.dynamicframe import DynamicFrame from awsglue.transforms import * from awsglue.utils import getResolvedOptions from pyspark.context import SparkContext from awsglue.context import GlueContext from awsglue.job import Job # @params: [JOB_NAME] args = getResolvedOptions(sys.argv, ['JOB_NAME']) sc = SparkContext() glueContext = GlueContext(sc) spark = glueContext.spark_session job = Job(glueContext) job.init(args['JOB_NAME'], args) Após iniciar o job é feita a conexão com o banco de dados. Nele informamos qual o nome da tabela e os parâmetros de configuração para essa conexão.\nTABLE_NAME = \"tb_pokemons\" dyf = glueContext.create_dynamic_frame.from_options( connection_type=\"dynamodb\", connection_options={ \"dynamodb.input.tableName\": TABLE_NAME, \"dynamodb.throughput.read.percent\": \"1.0\", \"dynamodb.splits\": \"100\" } ) print(f\"Count Items: {dyf.count()}\") print(\"Items:\") dyf.show() Nas últimas linhas apresentamos a quantidade de itens listados e podemos dar um dyf.show() para mostrar todos os itens encontrados. Caso tenham muitas linhas em sua tabela é desejável que comente esse comando para evitar custos indesejáveis no CloudWatch 😅.\nVamos filtrar apenas os registros que nos interessam, fazemos isso por meio da função filter. Na Lambda a seguir digo que quero apenas os registros que não possuem “category”.\nfiltered_dyf = dyf.filter(f=lambda x: \"category\" not in x) print(f\"Count Items without category: {filtered_dyf.count()}\") print(\"Items without category:\") filtered_dyf.show() Dados filtrados, já sabemos onde atacar, para popular esse campo iremos utilizar a PokeAPI, que é um serviço gratuito onde podemos buscar informações sobre todos os Pokémon deste vasto universo. Na função buscamos pelo número do registro e então atualizamos a linha com o valor encontrado.\ndef add_category(row): url = f\"https://pokeapi.co/api/v2/pokemon/{row['number']}\" row[\"category\"] = requests.get(url).json()[\"types\"][0][\"type\"][\"name\"] return row # Filter out the rows that no have category updated_dyf = filtered_dyf.map(add_category) # Show date filtered updated_dyf.show() DataFrame atualizado agora é a hora de enviar esses dados para a tabela tb_pokemons utilizando o mesmo glueContext passamos como parâmetro o frame igual ao nosso DF.\nif filtered_dyf.count() \u003c 1: print('There are no items to process') os._exit(0) else: print('There are items to process') # Update the table new_data = DynamicFrame.fromDF(updated_dyf.toDF(), glueContext, \"new_data\") write_df = glueContext.write_dynamic_frame_from_options( frame=new_data, connection_type=\"dynamodb\", connection_options={ \"dynamodb.output.tableName\": TABLE_NAME, \"dynamodb.throughput.write.percent\": \"1.0\" }, transformation_ctx=\"write_df\" ) job.commit() Resultado Após rodar o script podemos verificar que os dados que faltavam de categoria agora estão inseridos na tabela!\nConclusão A Amazon Web Services possuí diversos serviços que podem facilitar a nossa vida, normalmente o Glue é utilizado para trabalhos envolvendo Data Science ou engenharia de dados, trouxe para vocês um exemplo de como desenvolvedores podem utilizar essa ferramenta para resolver problemas do dia a dia.\nConseguimos realizar a atualizar em lote de forma fácil e rápida, sem precisar gerenciar uma arquitetura complexa como a criação de uma Lambda ou de uma aplicação apenas para isso. Essa prova de conceito serviu para mostrar o potencial de um script Python para evitar que utilizemos um canhão para matar uma formiga.\nCaso tenha alguma crítica, sugestão ou dúvida fique à vontade para me enviar uma mensagem\nAté a próxima!\nReferências AWS Glue https://aws.amazon.com/glue/ Connection types and options for ETL in AWS Glue https://docs.aws.amazon.com/glue/latest/dg/aws-glue-programming-etl-connect.html#aws-glue-programming-etl-connect-dynamodb Update DynamoDB Glue GitHub https://github.com/jjeanjacques10/update-dynamodb-glue PySpark Glue Tutorial https://github.com/johnny-chivers/pyspark-glue-tutorial How to export an Amazon DynamoDB table to Amazon S3 using AWS Step Functions and AWS Glue https://aws.amazon.com/blogs/big-data/how-to-export-an-amazon-dynamodb-table-to-amazon-s3-using-aws-step-functions-and-aws-glue/ ","cover":"/posts/images/2022-11-13-atualizando-dados-no-dynamodb-utilizando-aws-glue/cover.png","date":"2022-11-13","description":"","permalink":"https://jjeanjacques10.github.io/posts/2022-11-13-atualizando-dados-no-dynamodb-utilizando-aws-glue/","tags":["AWS","DynamoDB","Python","Engenharia de Dados"],"title":"Atualizando dados no DynamoDB utilizando AWS Glue"},{"content":"DynamoDB Single-Table Design com Spring Boot Um exemplo prático de como utilizar Single-table Design em uma aplicação Java Spring Boot Desde que conheci o conceito de Sigle-Table Design quis entender melhor como isso poderia ser aplicado em um cenário real, criei essa prova de conceito e gostaria de apresentar para vocês!\nDynamoDB é um banco de dados No-SQL de chave-valor (key-value) criado pela AWS, ele tem como foco trabalhar com grandes volumes de dados oferecendo performance. Diversas empresas atualmente utilizam esse banco de dados, como por exemplo Itaú, Amazon, Mercado Livre e a própria AWS. Neste artigo irei abordar algumas diferenças que você teria ao utilizar a estratégia de Single-table.\nO que é Single-table Design Vamos começar lembrando um fato sobre o DynamoDB, sendo um banco de dados No-SQL, não tem o conceito de “joins”, portanto, se os dados estão na forma normalizada em várias tabelas, recuperar itens relacionados em mais de uma tabela exigirá várias solicitações em série para obter esses registros. No seguinte exemplo temos duas tabelas, a de personagens e a de revistas, que em nossa consulta retornam para o usuário informações sobre a os quadrinhos de um personagem específico e também seu perfil, aqui precisaríamos realizar duas consultas separadas para cada tabela.\nUm método mais simples de obter esse mesmo resultado é centralizando tudo em apenas 1 tabela, aplicando o Single-Table Design, assim sempre que for necessário retornar informações do personagem e das revistas em que ele participa podemos realizar apenas 1 consulta que obteremos todos os dados.\nSendo essa a principal razão para migrar para uma single table no DynamoDB, a possibilidade de recuperar vários tipos de dados heterogêneos usando uma única solicitação.\nOutra vantagem é que não será necessária a criação e acompanhamento de de alarmes ou métricas de monitoração para múltiplas tabelas, sendo que a cada uma que você provisiona novas configurações são necessárias, como permissões e monitoração como já citado. Quando centralizamos tudo em apenas uma tabela temos a redução de custos consumo para capacidades de leitura sendo que antes tínhamos o consumo dessa capacidade sendo feita em múltiplas queries e agora apenas 1 é necessária.\nModelando a tabela Vamos iniciar nossa modelagem determinando quais entidades queremos trabalhar, esse processo é muito parecido com o que fazemos ou modelar tabelas em um banco relacional, como MySQL ou Oracle. No nosso caso de uso as duas entidades são Character (Personagem) e Comic (Quadrinho), como é possível observar na imagem a baixo poderíamos ter uma tabela para cada um deles.\nEm banco relacionais temos a opção de realizar “JOIN’s” para obter a informação destas duas tabelas em conjunto, mas no DynamoDB isso não é possível.\nEntão vamos evoluir esse modelo aplicando o Single-Table Design a ele. Primeiro passo é definir os padrões de acesso que iremos utilizar:\nRetornar o perfil de um personagem Listar todos os perfis de personagens Listar todos os quadrinhos de um personagem Listar todos os quadrinhos Nesta nova modelagem, já no estilo de nosso banco No-SQL, temos como PK (PartitionKey) a informação de Character juntamente com o seu nickname, e nossa SK (SortKey) é composta pelo tipo (PROFILE/COMIC) e seu identificador. Antes o que eram 2 tabelas se transformaram em apenas uma onde cada linha representa um tipo de dado, como o DynamoDB é um banco com esquema “fraco” podemos ter diferentes colunas em cada linha da tabela, por conta disso não ficamos presos em colunas desnecessárias, exemplo disso são as colunas “comicId” e “name”.\nAgora, quando queremos buscar o Personagem e os Quadrinhos, podemos fazê-lo em uma única solicitação sem precisar de uma operação de concatenação.\nCom esse conceito e com nossa tabela já modelada, vou mostrar agora como colocar a mão na massa e implementar chamadas em uma aplicação Spring Boot!\nCriando nossa Aplicação Spring Boot Criei a base do projeto utilizando o site: https://start.spring.io/ . Selecione as bibliotecas padrões do Spring Web para criação de uma API Rest.\nDependências Após descompactar a pasta dele vamos iniciar adicionando as dependências necessárias, as duas libs que iremos utilizar são:\nAWS SDK DynamoDB Enhanced \u003cdependency\u003e \u003cgroupId\u003esoftware.amazon.awssdk\u003c/groupId\u003e \u003cartifactId\u003edynamodb-enhanced\u003c/artifactId\u003e \u003cversion\u003e2.17.263\u003c/version\u003e \u003c/dependency\u003e \u003cdependency\u003e \u003cgroupId\u003ecom.amazonaws\u003c/groupId\u003e \u003cartifactId\u003eaws-java-sdk-dynamodb\u003c/artifactId\u003e \u003cversion\u003e1.11.857\u003c/version\u003e \u003c/dependency\u003e Obs: Não irei entrar em muitos detalhes sobre a configuração dessas duas libs, então caso tenha alguma dúvida pode entrar no repositório de exemplo na pasta config (comics/config/DynamoDbConfig.java)\nEntidades Nessa próxima etapa vamos criar nossa entidade que será a referência do item salvo em na tabela do Dynamo. Aqui temos a Entity de Characters apenas com os campos que esse tipo de dado necessita, na visão da tabela seria a linha com o SK PROFILE#.\nAgora essa é referente ao item de Comics (Quadrinho), são muito parecidas, afinal apenas os campos “comicId” e “type” foram adicionados para esse tipo de objeto e que não existiam no outro. Na visão da tabela seria a linha com o SK COMIC#.\nAgora para o cenário que comentamos anteriormente, onde são retornados ambos: profile e comics, vamos utilizar essa nova entidade que possuí todos os campos necessários para os dois itens. Nos casos onde um não é possível encontrar o campo no registro apenas será ignorado, como por exemplo profile que não te “comicId” e comic que não possuí “name”.\nPronto! Entidades criadas, elas serão a base para os próximos passos onde iremos realizar as consultas.\nRealizando buscas O DynamoDb trabalha com dois tipos de buscas o Scan e a Query, sendo o primeiro o mais lento e custoso, que faz uma pesquisa na tabela e retorna todas as informações, podemos até realizar filtros, porém, eles apenas serão aplicados depois de mapear todos os dados. O método Query é o mais recomendado a ser utilizado, onde apenas solicitamos a informação que precisamos. Contudo, o query apenas funciona em buscas utilizando os campos PartitionKey e SortKey que juntos forma o PrimaryKey. Este campo no DynamoDB pode ser simples ou composto.\nSimple Primary Keys: Consistem em uma PartitionKey e nenhuma SortKey. Composite Primary Keys: Consistem em uma PartitionKey e uma SortKey. Para não ficarmos limitados apenas aqueles primeiros PK e SK que criamos podemos criar Global Secondary Indexes, que são PK’s e SK’s diferentes do que geramos na criação da tabela e correspondem a cópias de nossa tabela, facilitando assim na hora de realizar buscas. Por exemplo, seria a utilização do campo type (PK) e date (SK) para realizar filtros de tempo. Isso se mostra como uma vantagem, mas apenas podemos criar 20 desses indexes personalizados.\nPara realizar buscas mais avançadas sem precisar de Global Secondary Indexes para tudo podemos utilizar o begins_with que faz um filtro em nossa SortKey, e essa é uma das principais vantagens de utilizar um Single-table Design. Aqui é um exemplo de código onde busco apenas o perfil de um personagem:\nTambém conseguimos filtrar todos os quadrinhos de um personagem específico utilizando o begins_with COMIC#.\nSó é possível utilizar esse método begins_with em SortKeys.\nAgora para o tipo de consulta que comentamos acima onde devolvemos essas duas consultas anteriores juntas, em apenas uma busca na tabela fazemos a query sem passar a informação de SortKey, apenas a nossa partition.\nDe forma simples evitamos consumir muitos recursos na hora de fazer o que antes seria um “JOIN” procurando a informação em duas tabelas. Exemplo de retorno:\n[ { \"pk\": \"CHARACTER#greenlantern\", \"sk\": \"COMIC#58f59aa1\", \"type\": \"COMIC\", \"nickName\": \"greenlantern\", \"realName\": null, \"description\": \"Green Lantern: New Guardians is an American comic book series originally written by Tony Bedard with art by Tyler Kirkham and Batt and published by DC Comics. The team consists of representatives of each of the Corps that tap into a particular portion of the emotional spectrum.\", \"comicId\": \"58f59aa1\", \"title\": \"Green Lantern: New Guardians\" }, \u003c.. Comics ..\u003e { \"pk\": \"CHARACTER#greenlantern\", \"sk\": \"PROFILE#greenlantern\", \"type\": null, \"nickName\": \"greenlantern\", \"realName\": \"Hal Jordan\", \"description\": \"Harold 'Hal' Jordan, one of the characters known as Green Lantern, is a superhero appearing in American comic books published by DC Comics.\", \"comicId\": null, \"title\": null } ] Repositório projeto de exemplo:\nTodos esses exemplos podem ser encontrados no seguinte repositório no GitHub: jjeanjacques10/spring-dynamodb-single-table-design: Demo project for Spring Boot + DynamoDB Single Table Design (github.com)\nDesvantagens Em bases como essa temos algumas desvantagens, por exemplo a dificuldade para realizar análises de dados, como temos essas informações desnormalizadas, o processo de exportar esse tipo de tabela para ferramentas de analytic acaba se tornando complexo. Além disso, com o tempo surge a falta de flexibilidade para adicionar novos padrões de acesso, como por exemplo uma nova entidade que se relacionará com personagens e quadrinhos.\nOutro ponto é a curva de aprendizado que acaba sendo maior para quem inicia em um projeto em andamento, sendo que no inicio como vimos foram necessárias diversas conversas para chegar em um modelo ideal, esse conhecimento teria que ser passado a frente juntamente com as regras de negócio que ajudaram a modelar esse resultado atual do projeto.\nConclusão Com isso podemos concluir que o que irá determinar o uso desse tipo de design é o caso de uso que você tem em mãos. Como tudo em desenvolvimento de software esse tipo de abordagem é muito interessante, porém tem seus tradeoffs.\nO uso de tabelas DynamoDB facilita muito no dia a dia, principalmente quando nossos dados são utilizamos muito para consultas, convido vocês a clonar o repositório com os exemplos, nele tem um ambiente criado para testes locais utilizando o Localstack, simulando o DynamoDB. Só colocando a mão na massas que será possível entender se essa abordagem cumpre com os desafios propostos!\nCaso tenha alguma crítica, sugestão ou dúvida fique a vontade para me enviar uma mensagem\nAté a próxima!\nReferências The What, Why, and When of Single-Table Design with DynamoDB (alexdebrie.com)\nAmazon DynamoDB single-table design using DynamoDBMapper and Spring Boot | AWS Database Blog\nGetting started with DynamoDB and AWS SDKs — Amazon DynamoDB\n","cover":"/posts/images/2022-08-31-dynamodb-single-table-design-com-spring-boot/cover.png","date":"2022-08-31","description":"","permalink":"https://jjeanjacques10.github.io/posts/2022-08-31-dynamodb-single-table-design-com-spring-boot/","tags":["AWS","DynamoDB","Spring Boot","Arquitetura de Software"],"title":"DynamoDB Single-Table Design com Spring Boot"},{"content":"[PT-BR] Spring Data JPA — Overview Entendendo de verdade o Framework mais utilizado em aplicações Spring para manipulação de dados\nQuem já precisou trabalhar com bancos de dados relacionais utilizando o Spring Framework muito provavelmente já se deparou com o Spring Data JPA.\nO Spring Data JPA é um Framework que faz parte da família Spring Data, que é composta também pelo Spring MongoDB e Spring Data Redis. Tem como objetivo tornar mais fácil a construção de aplicações Java que necessitam de um mapeamento de banco de dados, servindo como um ORM (Object Relational Mapper).\nMesmo sendo frequente no cinto de utilidades dos desenvolvedores, ainda existem muitas dúvidas sobre o seu funcionamento e como trabalhar de forma eficiente com esse Framework, então lhes convido a seguir comigo nesse artigo, onde passaremos pelos tópicos mais importantes do Spring Data JPA!\nLembrando que todo código e exemplos podem ser encontrados no repositório do GitHub.\nInstalando as dependências Vamos iniciar adicionando a dependência do Spring Data no arquivo pom.xml, desta forma habilitamos o uso em nossa aplicação Spring Boot.\n\u003cdependencies\u003e \u003cdependency\u003e \u003cgroupId\u003eorg.springframework.data\u003c/groupId\u003e \u003cartifactId\u003espring-data-jpa\u003c/artifactId\u003e \u003c/dependency\u003e \u003cdependencies\u003e É possível trabalhar com diversos SGBD’s (Sistema de Gerenciamento de Banco de Dados), como por exemplo:\nMySQL PostgreSQL SQL Server MariaDB Oracle Database Sendo todos eles bancos de dados Relacionais. Contudo, para isso precisamos adicionar a dependência específica ao Driver que queremos utilizar. Segue exemplo para uma conexão MySQL:\n\u003cdependency\u003e \u003cgroupId\u003emysql\u003c/groupId\u003e \u003cartifactId\u003emysql-connector-java\u003c/artifactId\u003e \u003cversion\u003e8.0.25\u003c/version\u003e \u003c/dependency\u003e Configurando a aplicação Com as dependências instaladas devemos configurar a nossa conexão e também tomar algumas decisões importantes dependendo do ambiente que pretendemos alocar nosso App, no arquivo application.properties acrescentamos os seguintes atributos.\nÉ nessa etapa que você deve analisar bem qual ambiente deseja subir seu aplicativo. Quando vamos configurar o spring.jpa.hibernate.ddl-auto temos algumas opções que, caso não sejam bem configuradas, podem gerar grande dor de cabeça! Vamos conhecer elas:\ncreate: Deleta as tabelas que já existem e em seguida cria novas. create-drop: Muito parecido com o create, mas aqui as tabelas serão deletas após todas as demais operações. update: Observa as atualizações feitas e atualiza o esquema com as diferenças encontradas. validate: Valida se tudo que foi mapeado existe (tabelas, campos e etc…), caso contrário, lança uma exceção. none: Nenhuma das outras opções, desliga a atualização e criação de esquema. A escolha errada pode causa perda de dados ou corromper seu schema\nAnnotations O Spring Data JPA facilita a nossa vida abstraindo a configuração por meio de Annotations (Anotações). Ao invés de referenciarmos em um arquivo XML podemos apenas “anotar” nossas classes e atributos Java com os seguintes valores:\n@Entity Adicionamos na classe para informar para o Spring que ela será uma entidade mapeável para o banco de dados. @Table Adicionamos na classe para informar quais configurações devem ser feitas para a tabela que estamos mapeando. Podemos passar informações referentes a ela como o nome. @Column Adicionamos nos atributos da classe para especificar qual coluna corresponde no banco de dados. Configurações como tamanho, se aceita nulo dentre outras coisas podem ser feitas nessa anotação. @Id Adicionamos no atributo que corresponde ao ID da tabela. É obrigatório que toda Entidade possua um identificador único. @GeneratedValue Possibilita definirmos qual estratégia queremos para gerar o ID da entidade. Adicionamos acima do atributo com a anotação @Id @Enumerated Adicionamos nos atributos do tipo ENUM que desejamos mapear, podemos fazer configurações como utilizar número ou o valor do ENUM. Estas são as principais anotações, temos outras que podem ser encontradas no link a seguir — https://www.baeldung.com/spring-data-annotations\nTrabalhando com ID’s Esse é um ponto que gera muita dúvida: Qual estratégia devo utilizar para geração de chaves primárias em minha tabela? Vamos conhecer um pouco mais sobre cada uma das possibilidades e assim suas decisões podem ser tomadas com mais embasamento.\nGenerationType.AUTO É a estratégia padrão, sempre que não definimos um GenerationType para o nosso identificador estamos utilizando o AUTO, nele os valores serão determinados a partir do tipo de nosso atributo. São aceitos os types numeral e UUID. GenerationType.IDENTITY Estratégia mais utilizada em projetos. Os IDs serão gerados pela coluna de AUTO_INCREMENT do banco de dados. Alguns bancos podem não suportar essa opção. GenerationType.SEQUENCE Utiliza sequências quando elas são aceitas pelo banco de dados utilizado, como por exemplo: SEQUENCE em Oracle Database. Caso o banco não aceite mudara automaticamente para TABLE. GenerationType.TABLE Utiliza tabelas para gerar as chaves primárias. Aceito por todos os bancos de dados, porém, não é muito recomendado por não escalar bem e poder causar problemas de performance. Agora que conhece melhor sobre cada tipo de geração de identificadores escolha com sabedoria em seus próximos projetos.\nCriando um repositório Após a criação de nossas entidades precisamos interagir com os dados, o Spring Data JPA oferece uma interface que facilita esse processo. Estendemos da interface JpaRepository, por meio dela podemos utilizar alguns métodos padrões ou criar nossas próprias Queries.\nNesse exemplo temos os dois primeiros métodos que são padrões da interface JpaRepository, o primeiro retorna uma lista e o segundo nos retorna um objeto Optional da Entity Shinobi. Perceba que precisamos mapear os genéricos da interface com a primeira posição sendo a entidade (Shinobi) e a segunda o tipo de Id (Long), os retornos destes método mudam de acordo com o que for mapeado no repositório.\nMuitas vezes é necessário executar consultas SQL, e para isso utilizamos uma anotação chamada @Query que aceita esse código como parâmetro. Como por exemplo no findByNameLikeQuery, onde estamos passando o código SQL “SELECT s FROM Shinobi s WHERE name LIKE %:name%”, perceba que é necessário passar o nome da tabela com a primeira letra em maiúsculo e criar um alias (a letra “s”) para realizar a consulta (não é preciso quando utilizamos queries nativas)\nA abordagem utilizando o @Query nos dá mais poder sobre o Framework, não engessando o desenvolvimento e possibilitando a criação de consultas mais complexas e performáticas.\nConsultas nativas Para criar consultas nativas basta adicionar um novo parâmetro para a anotação chamado nativeQuery e setar o valor como “true”.\nInicializando a aplicação com dados Essa é uma necessidade que você pode vir a ter, a de inicializar seu banco de dados com alguns valores padrões, é possível fazer isso adicionando um arquivo de inicialização, no caso o populate-database.sql na pasta sql de nossa aplicação.\nQuando executarmos pela primeira vez veremos essa interação nos logs com os itens sendo inseridos.\nRepositório projeto de exemplo:\nTodos esses exemplos podem ser encontrados no seguinte repositório no GitHub: https://github.com/jjeanjacques10/spring-data-jpa-demo\nAlguns tópicos ficaram de fora, como por exemplo a parte de relacionamento, queries mais avançadas, pensando na performance da aplicação, paginação e dentro outros pontos. Mais para frente irei lançar uma parte dois e adiciono o link aqui!\nConclusão Após me aprofundar na biblioteca que vejo diariamente, percebi o quanto ainda tenho a aprender, e esses novos tópicos que descobri já começaram a me ajudar no dia a dia de trabalho diminuindo o tempo de desenvolvimento e buscas no Stack Overflow (além de evitar muita dor de cabeça ao debugar as aplicações). Espero que este artigo tenha lhe auxiliado em sua jornada de aprendizado com Spring Data JPA.\nAté a próxima!\nCaso tenha alguma crítica, sugestão ou dúvida fique a vontade para me enviar uma mensagem:\nAté a próxima!\nReferências Spring Data JPAhttps://spring.io/projects/spring-data-jpa Spring JPA — Multiple Databases https://www.baeldung.com/spring-data-jpa-multiple-databases Spring Data JPA Demo https://github.com/jjeanjacques10/spring-data-jpa-demo ","cover":"/posts/images/2022-07-07-pt-br-spring-data-jpa-overview/cover.png","date":"2022-07-07","description":"","permalink":"https://jjeanjacques10.github.io/posts/2022-07-07-pt-br-spring-data-jpa-overview/","tags":["Spring Boot","Java","Banco de Dados","JPA"],"title":"[PT-BR] Spring Data JPA — Overview"},{"content":"Tornando seu código mais SOLID Explicando os conceitos SOLID de orientação a objetos de uma forma simples e com um projeto prático Respondendo a primeira e mais importante pergunta, o que é SOLID? O SOLID são os 5 princípios de design de código voltados para orientação a objetos que auxiliam os desenvolvedores a escreverem um código para “serem humanos” não apenas para máquinas. O que significa que tornam o sistema criado mais fácil de se manter e também mais adaptável, facilitando alterações de escopo que podem surgir durante a evolução do projeto.\nEstes princípios se mantiveram importantes desde que foram definidos por Robert C. Martin (Uncle Bob) e a necessidade de implementa-los começa a surgir quando se sente um “cheirinho estranho” no código, ou melhor dizendo, um code smells, pode ser definido como qualquer ponto no código que não pareça muito bem e possa indicar algum problema mais profundo. Neste artigo vou lhe mostrar como identificar alguns destes pontos de atenção.\nOs exemplos utilizados neste artigo foram escritos em Java, mas podem ser replicados em qualquer linguagem! ☕\nPrincípios do SOLID Vamos começar dizendo o que cada letra significa:\nS — Single-responsibility Principle O — Open-closed Principle L — Liskov Substitution Principle I — Interface Segregation Principle D — Dependency Inversion Principle Vamos começar pelo primeiro princípio e também, em minha opinião, o mais importante de todos eles, o princípio de:\nSingle-Responsibility Principle No princípio da responsabilidade única, uma classe não deve ter mais do que um objetivo ou finalidade. Com cada parte do seu código responsável por um escopo bem definido, quando isso é feito da maneira certa é possível encontrar de forma fácil o que deseja modificar e também identificar qual o melhor local para implementar uma nova funcionalidade.\nOutra vantagem é que o SRP evita de você ter que guardar todo o programa em sua cabeça, sendo que pequenos “módulos” do código são mais fáceis de lembrarmos no dia a dia. Em projetos grandes é comum criar confusão na hora de lembrar onde fica uma certa função ou qual método deve ser chamado para realizar uma ação.\nExemplo de uma classe que tem mais responsabilidades do que o necessário:\nEste é um exemplo de uma God Class (Classe Deus), em POO damos esse nome quando uma classe “sabe demais”, ou seja, que implementa vários métodos e não tem um objetivo bem definido. Com o tempo a tendência é ela aumentar cada vez mais de tamanho. Um dos caminhos para evitar essa bola de neve que irá se formar é utilizar o princípio da responsabilidade única criando classes que são responsáveis por uma única tarefa:\nNeste novo exemplo fiz a separação das responsabilidades, agora temos o ReportService que cuida dos métodos de relatório e o PokedexService que é responsável pelo CRUD de Pokemon. O acesso ao banco de dados é feito por meio de um Repository. Agora ficou mais fácil para crescer a aplicação, com cada item com responsabiliades bem divididas.\nPrincipais problemas quando não implementado:\nDificuldade em escrever testes, principalmente de unidade; Falta de coesão no código; Alto acoplamento, a dependência entre as partes de sua aplicação irá aumentar. Open-closed Principle “Entidades de software (classes, módulos, funções e etc.) devem estar abertas para extensão, porém fechadas para modificação”\nEste princípio determina que uma classe deve ser “fechada para alteração e aberta para extensão”. Okay, mas o que isso significa? 😕\nIsso quer dizer que sempre que uma regra de negócio nova é adicionada não será necessário alterar um código já existente e sim adicionar uma nova implementação dele. Isso ficará muito mais claro depois de alguns exemplos:\nAqui temos diversas validações sendo realizadas ao tentar treinar um Pokémon, sempre que uma nova validação precisar ser adicionada precisaremos modificar esse código adicionando uma condicional. Como solução podemos fazer a seguinte modificação:\nAgora nosso Service recebe uma lista de validações (linha 5) que implementa a interface TrainingValidation. Desta forma sempre que uma nova validação precisar ser adicionada ao nosso treinamento basta criar uma nova classe que implemente está interface.\nObs: O OCP pode lembrar bastante o Design Pattern Strategy, é um tópico interessante para se aprofundar.\nPrincipais problemas quando não implementado:\nMétodos com várias condicionais Modificações constantes em entidades de software que já existem Aumento no número de bugs ocasionais ao alterar regras de negócio Liskov Substitution Principle “Se S é um subtipo de T, então os objetos do tipo T, em um programa, podem ser substituídos pelos objetos de tipo S sem que seja necessário alterar as propriedades deste programa” — Wikipedia\nVamos combinar que essa definição pode deixar nossa mente mais confusa do que explicar algo 😅, mas vamos por partes… Em outras palavras: Um novo objeto criado a partir de uma classe que possuí herança não pode quebrar o comportamento da classe ancestral.\nComo exemplo estou utilizando a classe Item que é estendida para as classes filho PokeBall, MasterBall e UltraBall. Mas quando vemos a implementação podemos perceber que MasterBall não implementa o método “buy” porquê não é possível comprar esse tipo de item, apenas adquiri-lo em um lugar específico durante a sua jornada.\nPor conta disso temos esse efeito inesperado quando tentamos vender um item MasterBall.\nPara seguirmos o princípio LSP a nossa MasterBall não deve herdar da classe “Item” e sim da nova Classe “ItemRare” que tem os métodos que se encaixam melhor no escopo desejado.\nCom essa nova implementação as classes filho podem substituir facilmente a classe ancestral.\nPrincipais problemas quando não implementado:\nMétodos que lançam exceções inesperadas Valores de tipos diferentes da classe base Implementar um método que não faz nada Muita lógica condicional espalhada pela aplicação Interface Segregation Principle Uma classe não deve implementar métodos que não irá utilizar. Por conta disso uma segmentação maior acaba sendo melhor para a organização do código. Ao desenvolver um software devemos preferir ter mais interfaces específicas, em vez de uma única interface grande e de uso geral, o que tem relação com o princípio de responsabilidade única (SRP), de acordo com essa ideia vejamos a seguinte implementação. Essa é a interface de Payment onde temos os métodos relacionados a um processo de transação dentro da aplicação:\nAgora vamos ver a implementação desta interfacenas Classes WalletService e LoanService.\nO service de Wallet precisa implementar todos os métodos da interface, o que acaba causando comportamentos inesperados quando métodos relacionados a empréstimo como o initiateLoanSettlement são chamados, nesse exemplo a classe Wallet não tem como iniciar um empréstimo assim como a classe de LoanService não tem como iniciar um pagamento.\nComo solução para este problema segue um exemplo de implementação de interfaces mais específicas que atendem melhor a necessidade de nossa aplicação:\nNeste novo desenho cada Service implementa de uma interface com os métodos que pertence ao seu domínio. Como dá para perceber o número de arquivos aumentou, mas isso não é um problema porquê o foco é manter as classes implementando apenas os métodos necessários.\nResumindo este princípio, caso uma interface comece a ganhar muitas responsabilidades ela deve ser dividida em interfaces menores, as quais serão implementadas pelos clientes (entidades de software). Lembrando que os clientes não devem implementar métodos que não utilizam.\nPrincipais problemas quando não implementado:\nComportamentos inesperados quando chamar uma função Utilização dos conceitos de herança de forma errada Ferir o primeiro princípio da responsabilidade única Dependency Inversion Principle O princípio de inversão de dependência é sobre como classes devem depender de abstrações, não de implementações específicas dessas abstrações. Quando fazemos isso evitamos que detalhes ditem como devemos implementar uma solução. Se você realiza a chamada de uma classe dentro de outra está classe irá depender da que foi chamada. Isso resulta em um grande acoplamento e dificulta na alteração de módulos externos e também na escrita de testes. Busque então incorporar essa noção em seu código caso queira torna-lo mais flexível, ágil e reutilizável.\nNeste exemplo a classe PokedexService está instanciando o modelMapper e também a Conexão do banco de dados dentro de seu construtor, da forma que está implementada temos uma dependência destes dois atributos. No caso temos acesso até as informações de URL, USER e PASSWORD referentes a conexão ao banco de dados, o que sai totalmente do escopo original da Pokedex.\nPara resolver esses problemas transformamos a conexão em uma interface de Repository, que é a abstração que comentei a cima, onde ela pode ser trocada facilmente por outros clientes de banco de dados. Além disso ambos Repository e ModelMapper são trazidos para classe de Service por meio da passagem da informação pelo construtor (Dependency Injection) sendo possível uma maior flexibilidade.\nPodemos utilizar este conceito também para estruturar melhor nossa aplicação, como exemplo no projeto criei uma interface por serviço, desta forma tenho uma visão melhor de quais são os métodos realmente necessários e também tenho uma facilidade maior para trocar uma implementação caso necessário.\nPrincipais problemas quando não implementado:\nAlto acoplamento entre classes Aumento da complexidade para trocar um sistema externo (adapter) Aumento da complexidade na escrita de testes Recomendações Conheci os princípios do SOLID por meio do livro Clean Code. É uma leitura que recomendo para aqueles que buscam melhorar a forma que escrevem código. Poderia recomendar também diversos vídeos e artigos como este, contudo, o mais importante é implementar o que viu aqui em seus projetos, só assim estes conceitos ficaram mais enraizados em sua mente.\nPara ajudar você a por a mão na massa segue um link que pode ser bem útil:\nRepositório GitHub Aqui está o link para o repositório no GitHub onde coloquei os exemplos de código escritos em Java: https://github.com/jjeanjacques10/solid\nConclusão Quando implementamos estes conceitos em nossos projetos temos um código melhor organizado, mais robusto e de fácil manutenção. Que é basicamente o sonho de todo desenvolvedor. O SOLID sozinho não vai fazer milagres, mas ajuda aqueles que trabalham com programação orientada a objetos a terem uma visão mais voltada para o reaproveitamento e boas práticas.\nNo começo pode ser um pouco complicado entender todos estes princípios, mas o que me ajuda a compreender cada dia mais o SOLID é buscar implementa-los todos os dias nos códigos que desenvolvo. Se quer aprender então coloque a mão na massa!\nCaso tenha alguma crítica, sugestão ou dúvida fique a vontade para me enviar uma mensagem. Até a próxima!\nReferências http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod\nInterface Segregation Principle in Java | Baeldung\nExploring S.O.L.I.D Principle in Android | by Anitaa Murthy | ProAndroidDev (medium.com)\n","cover":"/posts/images/2022-02-23-tornando-seu-codigo-mais-solid/cover.png","date":"2022-02-23","description":"","permalink":"https://jjeanjacques10.github.io/posts/2022-02-23-tornando-seu-codigo-mais-solid/","tags":["Engenharia de Software","SOLID","Java","Spring"],"title":"Tornando seu código mais SOLID!"},{"content":"Alexa Skill — Newsletter Reader (Filipe Deschamps) Uma maneira simples de ler newsletters que você recebe por e-mail\nDepois que comprei um Echo Dot, que utiliza a assistente virtual por voz Alexa, fiquei um pouco preguiçoso. Para mim, é complicado ler todas as newsletters que recebo por email, por isso decidi incumbir a minha nova amiga robô esse processo de leitura ❤.\nLogo de cara, tive algumas dificuldades para descobrir uma forma de receber as notícias e enviar para a Alexa, depois procurei no Google algumas alternativas e encontrei vários softwares e aplicações web, infelizmente era tudo pago 😥 (e as opções gratuitas eram limitadas). Porém, descobri o Portal de Desenvolvimento das Skills da Alexa, diferentemente das outras opções, nesta eu conseguia resolver o meu problema de forma TOTALMENTE GRATUITA!\nContudo… eu precisaria ter meu próprio código, mas isso não é um problema 😤😁\nVamos ver como criei a minha primeira Alexa Skill para ler uma das minhas Newsletters favoritas!!\nAlexa Skills Segundo o site oficial, as skills da Alexa são aplicativos ativados por voz que adicionam recursos a um dispositivo compatível com a assistente virtual. As skills adicionam novas funcionalidades à sua experiência com a Alexa. Elas estão disponíveis em várias categorias, como Educação, Notícias, Jogos ou Música.\nA Newsletter Esse é o Filipe Deschamps, programador e YouTuber que criou uma newsletter incrível e 100% gratuita onde compartilha novidades de tecnologia com seus assinantes. E foi ela que utilizei para criar minha skill. Todos os dias, meu robô lê o e-mail enviado por ele e se integra com a Alexa.\nAh, antes que eu esqueça: você pode se inscrever neste link\nGet Started Newsletter (Uma que goste) Alexa Developer Console NodeJS Zapier Google Sheets A primeira etapa é fazer o scrapping do e-mail:\nZapier Eu adoro esta ferramenta, Zapier é um automation helper (do inglês, ajudante de automação), você pode criar integrações com várias plataformas, como Planilhas Google, Trello, GitHub e outras. Estou acostumado a usar-lo para melhorar meus aplicativos, é bem mais fácil do que reinventar a roda sempre.\nPara isso criei uma integração de Planilhas Google + Gmail, o processo é simples:\n1. Crie uma label no Gmail No meu caso, eu marquei como “technewsletter”, então sempre que recebo um novo e-mail do contato “newsletter@filipedeschamps.com.br”, será definido este rótulo. Isso me ajudou a controlar meu fluxo de e-mail e não perder nenhum que for enviado.\n2. Crie um Zap Gmail Agora devemos configurar no Zapier o e-mail onde recebemos a newsletter. É a maneira mais simples de obter informações do Gmail ou de outros sistemas de e-mail.\nDepois de configurar seu e-mail, a etapa mais importante é configurar um trigger, que é como um gatilho para rodar a configuração que estamos fazendo. Como expliquei acima, você deve utilizar a label que criamos anteriormente como nosso gatilho principal.\nGmail configurado!\n3. Crie uma planilha no Google Sheets Como um “banco de dados” optei por utilizar uma planilha, era a maneira mais simples de salvar os dados e acessá-los posteriormente. Assim, usando o Zapier, criei um Zap do Google Sheet onde selecionei a opção “Criar linha da planilha”(Create Spreadsheet Row).\nNa etapa de action, você deve selecionar uma spreadsheet e um worksheet, no meu exemplo eu usei:\nNewsletter =\u003e Spreadsheet News =\u003e Worksheet Na mensagem passei o corpo do HTML. Quando utilizo este formato tenho mais controle sobre a estrutura do texto, o que mais para frente vou mostrar como pode ser útil…\nDepois de criar nossa tabela e configurá-la para preencher toda vez que recebermos uma nova newsletter por e-mail, temos uma planilha como esta:\nE se eu tivesse feito uma conexão direta com o Gmail, seria mais fácil, não seria? Bem… eu poderia fazer isso, mas o principal motivo para evitar essa abordagem é que ficaria mais complicado para gerenciar erros, por isso preferi enviar primeiro para o Planilhas Google. Além da possibilidade de reuso em outras aplicações.\nAlexa Skill O primeiro passo é criar uma conta de desenvolvimento no Developer Portal e acessar o console, onde você encontrará todas as configurações de suas habilidades. Depois disso, você pode prosseguir para as próximas etapas …\nOs códigos Desenvolvi dois códigos, o primeiro é uma Custom Skill onde leio diretamente o texto da newsletter da última linha escrita na planilha e o envio para a assistente. E o segundo é um flash briefing, onde gero um arquivo JSON que tem a separação entre as notícias e é lido pela Alexa. Sempre me perguntam o porque fazer duas skills que tem a mesma função, vamos lá… o principal motivo foi: A primeira que desenvolvi foi a custom, e ao criar as Skills não há a possibilidade de mudar a versão para Flash Briefing sem perder todos os dados. Criei a segunda versão, Flash Briefing, por conta dos pedidos de separação das notícias, porém, essa segunda tem um limite de itens (5 no total). Para manter os pontos positivos de ambas resolvi deixar as duas online.\nFlash briefing: Separação entre notícias, poder adicionar de forma mais fácil ao resumo diário.\nCustom Skill: Conseguir ler todas as notícias.\nAqui está como desenvolvi elas duas:\nCriando uma nova habilidade… Vamos começar criando a Skill, acesse o portal de desenvolvimento e após clicar no console você será redirecionado para este endereço: Create new Alexa Skill. Selecione o nome da sua habilidade e o tipo dela como no exemplo a baixo. Como podem ver temos as opções de Custom e Flash Briefing, irei tratar sobre as duas mais a frente.\nCustom Skill Caso tenha selecionado esta opção clique na aba de “Build”. O primeiro passo é criar sua intenção, que se refere ao objetivo que o cliente tem em mente ao falar com a assistente. Por exemplo, a intenção que criei foi a “NewsLetterIntent”, ela é ativada sempre que o usuário deseja ler as notícias. Como é possível ver, as opções de chamada são:\nnewsletter filipe deschamps newsletter do deschamps newsletter do filipe Com a intenção criada, acesse o portal e selecione a aba de código no Alexa developer console. Você verá a uma estrutura de arquivos bem parecida com um serverless. Adicione o código abaixo no arquivo index.js, com ele podemos fazer com que a intenção “NewsLetterIntent” retorne as notícias que estão sendo buscadas na tabela criada no Google Sheets.\n// Aqui está o código que lê a planilha const readSpreadSheet = require('./readSpreadSheet.js'); const NewsLetterIntentHandler = { canHandle(handlerInput) { return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest' \u0026\u0026 Alexa.getIntentName(handlerInput.requestEnvelope) === 'NewsLetterIntent'; }, async handle(handlerInput) { var speakOutput = await readSpreadSheet; return handlerInput.responseBuilder .speak(speakOutput) //.reprompt('add a reprompt if you want to keep the session open for the user to respond') .withShouldEndSession(true) .getResponse(); } }; const readSpreadSheet = require('./readSpreadSheet.js'); Nesta linha estou fazendo a importação do código que lê a minha planilha, limpa o texto e o retona para a Alexa. Mais a baixo quando falo sobre a Skill Flash Briefing eu explico detalhadamente cada um dos métodos utilizados nete arquivo.\nVocê pode encontrar o código completo da Skill customizada neste link: custom skill newsletter jjeanjacques10/alexa-newsletter-deschamps.\nFlash Briefing Está opção é bem mais simples, o que você vai precisar é de um JSON no formato certo para a Alexa ler. Então vamos lá! O e-mail é um código HTML, não posso enviar para Alexa diretamente, então criei um método onde pego esse código, limpo todas as tags e deixo em um formato entendível.\nPara facilitar separei meu código em 3 partes, que são estas:\ngetNews() Conecta o GoogleSpreadsheet utilizando o pacote google-spreadsheet | npm. Seleciona a última linha da planilha, que corresponde ao último e-mail enviado com a newsletter, e então chama a função que faz a limpeza do texto (cleanText).\ncleanText() Tive alguns problemas quando tentei publicar, a Amazon é muito rígida com o formato do texto e também o conteúdo que você vai apresentar para o usuário, depois de algumas revisões corrigi estes problemas:\nVírgulas no lugar errado Links de promoções não informadas anteriormente buildJson() Para poder fazer um Flash Briefing devemos utilizar um tipo de JSON pré-definido pela Amazon. Precisei criar um neste formato:\n[ { \"uid\": \"b89847c0-bd29-4ae6-a983-e1e715811b32\", \"updateDate\": \"2021-04-20T15:10:03.335Z\", \"titleText\": \"Notícias que chamaram a nossa atenção nesta terça-feira:\", \"mainText\": \"Notícias que chamaram a nossa atenção nesta terça-feira: \", \"redirectionUrl\": \"https://filipedeschamps.com.br/newsletter\" } ... ] Este código é responsável por construir a estrutura JSON e retornar um array de notícias.\nApós todo este processo você deve pegar o arquivo gerado e salvar em um servidor. Você pode utilizar um S3 da AWS (Amazon Web Services) para fazer isso ou em alguma máquina própria. Apenas saiba que precisará disponibilizar para acesso externo o arquivo. Com o link direcionando para ele entre no portal na parte de configurações de Flash Briefing e defina como feed padrão o endereço. Desta forma:\nConclusão Ok, com tudo configurado podemos ler nossa newsletter utilizando a Alexa! Convido vocês a testar as Skills “Newsletter Filipe Deschamps”!\nAqui estão os links para as duas Skills, não esqueça de dar uma nota no site da Amazon, isso ajuda muito!\nCustom Skill — https://www.amazon.com.br/dp/B08RG61BPD Flash Briefing — https://www.amazon.com.br/dp/B08SQTLJSK E gostaria de agradecer ao Filipe Deschamps, confesso que pensei que seria processado de inicio 😅, mas ele deu super apoio ao projeto, então meu super obrigado!\nCaso tenha interesse é possível acessar o código aqui:\nRepositório GitHub https://github.com/jjeanjacques10/alexa-newsletter-deschamps\nCaso tenha alguma crítica, sugestão ou dúvida fique a vontade para me enviar uma mensagem:\nRevisão de texto realizada por: Gabriel Petillo\nAté a próxima!\nReferências Alexa Skills Kit Official Site: Build Skills for Voice https://developer.amazon.com/pt-BR/alexa/alexa-skills-kit\nAlexa Skills and Features https://www.amazon.com/alexa-skills/b?ie=UTF8\u0026node=13727921011\nNewsletter Filipe Deschamps https://filipedeschamps.com.br/newsletter\n@jjean_dev • Instagram https://www.instagram.com/jjean_dev/\n","cover":"/posts/images/2021-04-21-alexa-skill-newsletter-reader-filipe-deschamps/cover.png","date":"2021-04-21","description":"","permalink":"https://jjeanjacques10.github.io/posts/2021-04-21-alexa-skill-newsletter-reader-filipe-deschamps/","tags":["AWS","Alexa","Serverless","Integrações"],"title":"Alexa Skill — Newsletter Reader (Filipe Deschamps)"}]