Como implementar o padrão Strategy utilizando Java e Spring

O que é o Padrão Strategy?

O padrão Strategy é um padrão de design comportamental que permite definir uma família de algoritmos, encapsulá-los e torná-los intercambiáveis. Esse padrão permite que o algoritmo varie independentemente dos clientes que o utilizam. Em outras palavras, ele permite que você escolha o algoritmo a ser usado em tempo de execução.

Benefícios do Padrão Strategy

  1. Manutenção Facilitada: Com o Strategy, você pode adicionar novas estratégias (algoritmos) sem modificar o código existente, seguindo o princípio aberto/fechado (Open/Closed Principle).
  2. Redução da Duplicação de Código: Encapsulando algoritmos em classes separadas, evitamos duplicação de código e promovemos o reuso.
  3. Melhoria na Testabilidade: Testar cada estratégia isoladamente é mais fácil do que testar uma classe com múltiplos comportamentos embutidos.
  4. Flexibilidade: Permite que os algoritmos sejam escolhidos e alterados em tempo de execução, oferecendo maior flexibilidade na aplicação.

Quando usar o Padrão Strategy?

  • Quando você tem muitas classes relacionadas que diferem apenas no comportamento.
  • Quando você precisa de diferentes variações de um algoritmo.
  • Quando os algoritmos específicos das classes podem ser expostos para que outros objetos possam interagir com eles.
  • Substituição de if e switch-case.

Exemplo de Implementação em Java

Enum para auxiliar na escolha de estratégia:
package com.example.demo.strategy;

public enum DiscountType {
    CHRISTMAS,
    NEW_YEAR,
    EASTER
}

Interface das estratégias:
package com.example.demo.strategy;

import java.math.BigDecimal;

public interface DiscountStrategy {

    BigDecimal applyDiscount(BigDecimal price);

    boolean selector(DiscountType discountType);
}
Estratégias
package com.example.demo.strategy;

import org.springframework.stereotype.Component;

import java.math.BigDecimal;

@Component
public class ChristmasDiscountStrategy implements DiscountStrategy{
    @Override
    public BigDecimal applyDiscount(BigDecimal price) {
        return price.multiply(BigDecimal.valueOf(0.90)); // 10% de desconto
    }

    @Override
    public boolean selector(DiscountType discountType) {
        return DiscountType.CHRISTMAS.equals(discountType);
    }
}
package com.example.demo.strategy;

import org.springframework.stereotype.Component;

import java.math.BigDecimal;

@Component
public class EasterDiscountStrategy implements DiscountStrategy{
    @Override
    public BigDecimal applyDiscount(BigDecimal price) {
        return price.multiply(BigDecimal.valueOf(0.80)); // 20% de desconto
    }

    @Override
    public boolean selector(DiscountType discountType) {
        return DiscountType.EASTER.equals(discountType);
    }
}
package com.example.demo.strategy;

import org.springframework.stereotype.Component;

import java.math.BigDecimal;

@Component
public class NewYearDiscountStrategy implements DiscountStrategy{
    @Override
    public BigDecimal applyDiscount(BigDecimal price) {
        return price.multiply(BigDecimal.valueOf(0.85)); // 15% de desconto
    }

    @Override
    public boolean selector(DiscountType discountType) {
        return DiscountType.NEW_YEAR.equals(discountType);
    }
}
Como implementar a seleção da estratégia

É muito comum ver essa seleção de estratégia sendo implementada com a inserção manual dessas mesmas estratégias na lista. Mas podemos utilizar o próprio spring pra isso, tendo declarado todas estratégias como componentes do spring. Como fizemos acima. Então quando criamos uma lista de estratégias, e pedimos o lombok pra criar um construtor com argumentos necessários, as estratégias já são inseridas automaticamente, sem a necessidade de inserção manual.

package com.example.demo.service;

import com.example.demo.strategy.DiscountStrategy;
import com.example.demo.strategy.DiscountType;
import jakarta.el.MethodNotFoundException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
@RequiredArgsConstructor
public class DiscountService {

    private final List<DiscountStrategy> strategies;
    public DiscountStrategy getStrategy(DiscountType discountType){
        return strategies.stream().filter(strategy -> strategy.selector(discountType))
                .findFirst()
                .orElseThrow(MethodNotFoundException::new);
    }
}

Conclusão

O padrão Strategy é uma poderosa ferramenta para separar os algoritmos das classes que os utilizam, permitindo flexibilidade e facilidade de manutenção. É especialmente útil em situações onde você precisa de múltiplas variações de um algoritmo e deseja selecionar qual usar em tempo de execução.

Código completo: https://github.com/nathalia-amarals/strategy-java-spring-boot

Referências:

Como configurar circuit breaker com openfeign e resilience4j

Se você está desenvolvendo um microserviço com Spring Boot e precisa se comunicar com outros serviços via HTTP, o OpenFeign é uma excelente escolha. Ele simplifica a criação de clientes HTTP declarativos. Além disso, quando estamos lidando com comunicação entre serviços, é essencial implementar padrões de tolerância a falhas, como o Circuit Breaker. Para isso, o Resilience4J é uma biblioteca robusta que pode ser facilmente integrada ao Spring Boot.

Neste post, vamos configurar e utilizar o OpenFeign e o Circuit Breaker do Resilience4J em um projeto Spring Boot com Gradle, utilizando Java 21. Também veremos por que é vantajoso usar Gradle com Kotlin ao invés de Groovy.

O que é um circuit breaker?

Um circuit breaker é essencial em comunicações síncronas REST para garantir a resiliência e robustez do sistema. Ele monitora interações entre componentes e bloqueia temporariamente solicitações a serviços com falhas ou alta latência, prevenindo sobrecargas e falhas em cascata. Isso permite que o sistema continue operando, melhora a latência percebida pelo usuário e fornece visibilidade sobre a saúde dos serviços. Além disso, o circuit breaker testa periodicamente os serviços problemáticos, reativando-os quando voltam a funcionar corretamente, o que garante uma recuperação controlada e eficiente. Possui também um esquema de fallback, que permite tratar APIs problemáticas.

Configuração do Projeto

  1. Inicializando o Projeto Spring Boot: Para começar, vamos criar um novo projeto Spring Boot. Você pode usar o Spring Initializr (https://start.spring.io/) para gerar um esqueleto de projeto com as dependências básicas. Selecione as seguintes dependências:
    • Spring Web
    • Spring Cloud OpenFeign
    • Spring Cloud Starter Circuit Breaker Resilience4J
  2. Como estamos utilizando Gradle, certifique-se de escolher esta opção no Initializr.
  3. Configuração do Gradle com Kotlin DSL: No arquivo build.gradle.kts (Gradle Kotlin DSL), adicione as seguintes dependências:
dependencies {

    implementation("org.springframework.boot:spring-boot-starter-web")
    // https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign
    implementation("org.springframework.cloud:spring-cloud-starter-openfeign:4.1.1")
    // https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-circuitbreaker-resilience4j
    implementation("org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j:3.1.1")
}

Configurando OpenFeign

  1. Habilitando o OpenFeign no Spring Boot: No arquivo principal da aplicação (Application.java), habilite o Feign adicionando a anotação @EnableFeignClients.
  2. Criando um Cliente Feign: Crie uma interface Feign para definir o cliente HTTP. Por exemplo:
@FeignClient(name = "metMuseumClient", url = "https://collection.metmuseum.org/public/collection/v1")
public interface MetMuseumClient {

    @GetMapping("/objects/{object_id}")
    ObjectResponse getObject(@PathVariable("object_id")int objectId);
}

Configurando o Circuit Breaker com Resilience4J

  1. Habilitando o Circuit Breaker no Feign: Configure o Feign para utilizar o Circuit Breaker do Resilience4J. Adicione as configurações no arquivo application.yml.
  2. Configurando o Feign com Circuit Breaker: Adicione a configuração para habilitar o Resilience4J Circuit Breaker nas chamadas Feign. Crie um configurador Feign na sua aplicação:

Criando uma Classe Service

@Service
@RequiredArgsConstructor
public class ArtObjectService {

    private final MetMuseumClient metMuseumClient;

    public ObjectResponse getObject(int objectId) {
        return metMuseumClient.getObject(objectId);
    }
}

Criando um RestController para Acionar o Cliente Feign

Para expor um endpoint REST que utilize o serviço criado, crie um controlador REST. Este controlador chamará o serviço que, por sua vez, utilizará o cliente Feign para obter os dados dos objetos do met museum.

@RestController
@RequestMapping("/met")
@RequiredArgsConstructor
public class ObjectController {

    private final ArtObjectService artObjectService;

    @GetMapping("/object/{object_id}")
    public ResponseEntity getObject(@PathVariable("object_id") int objectId){
        return  ResponseEntity.ok(artObjectService.getObject(objectId));
    }
}

Criando as configurações do Cliente Feign

spring:
  cloud:
    circuitbreaker:
      resilience4j:
        enabled: true
    openfeign:
      circuitbreaker:
        enabled: true
        alphanumeric-ids:
          enabled: true
      client:
        config:
          default:
            loggerlevel: full
            connectTimeout: 1000
            readTimeout: 1000

resilience4j:
  circuitbreaker:
    configs:
      default:
        registerHealthIndicator: true
        slidingWindowSize: 50 #como é baseado em tempo, ficará 50 segundos em monitoramento
        permittedNumberOfCallsInHalfOpenState: 3
        slidingWindowType: TIME_BASED #janela a ser monitorada
        minimumNumberOfCalls: 5
        waitDurationInOpenState: 500s
        failureRateThreshold: 3
        eventConsumerBufferSize: 10

Por que usar Gradle com Kotlin DSL ao invés de Groovy DSL

  1. Sintaxe Melhorada e Tipagem Estática: A sintaxe do Kotlin DSL é mais concisa e oferece tipagem estática, o que ajuda a detectar erros em tempo de compilação. Isso torna o processo de desenvolvimento mais seguro e reduz a probabilidade de erros em tempo de execução.
  2. Melhor Integração com IDEs: As IDEs, especialmente o IntelliJ IDEA, oferecem suporte superior para Kotlin DSL em comparação com Groovy DSL. A autocompletação, navegação de código e refatoração são mais eficientes e confiáveis, proporcionando uma melhor experiência de desenvolvimento.
  3. Consistência no Ecossistema Kotlin: Para equipes que já estão usando Kotlin em seus projetos, utilizar o Kotlin DSL para Gradle traz consistência no ecossistema de desenvolvimento. Isso facilita a curva de aprendizado e promove a reutilização de conhecimentos e práticas.
  4. Recursos Modernos do Kotlin: O Kotlin DSL aproveita recursos modernos do Kotlin, como lambdas, extensões de função e outras características avançadas, que permitem escrever scripts de build mais poderosos e expressivos.
  5. Manutenção a Longo Prazo: Gradle está investindo continuamente no Kotlin DSL, o que indica um forte suporte e melhorias futuras. Adotar o Kotlin DSL pode ser uma escolha estratégica para a manutenção e evolução a longo prazo dos scripts de build.

Conclusão

Configurar OpenFeign e o Circuit Breaker do Resilience4J em um projeto Spring Boot com Gradle e Java 21 é uma abordagem moderna e eficiente para criar microserviços resilientes. Usar Gradle com Kotlin DSL oferece várias vantagens sobre o Groovy, incluindo melhor suporte de IDE, tipagem estática e uma sintaxe mais clara e concisa. Experimente esta configuração em seu próximo projeto e veja como ela funciona.

Viu algum erro, tem alguma sugestão? Comenta aí.

Quer ver o código na íntegra? https://github.com/nathalia-amarals/openfeignresilienceconfig

Bibliografia: