Strategy Pattern no Spring Boot Usando Enum
Tags: java, strategypattern, enum, springboot
Published: 2022-10-21 18:44:39

Recentemente me passaram uma Feature que se encaixava muito bem com a utilização do [Strategy Pattern](https://refactoring.guru/pt-br/design-patterns/strategy), decidi colocar a mão na massa e logo me deparei com os seguintes desafios: 

- Implementar essa solução aproveitando os Recursos de [Injeção de Dependência](https://pt.wikipedia.org/wiki/Inje%C3%A7%C3%A3o_de_depend%C3%AAncia) do Spring com [Inversão de Controle](https://pt.wikipedia.org/wiki/Invers%C3%A3o_de_controle) mantendo-a o mais desacoplado possível
- Obter a instância gerenciada do Spring da implementação da Estratégia em tempo de execução
- Não a criar Instâncias da Estratégia que não será utilizada na memória desnecessariamente sobrecarregando a mesma.
- Criar um factory simples e de fácil extensão que irá entregar a instância da Estratégia baseado em um identificador que será passado como parâmetro em uma requisição REST

Como em uma pesquisa rápida, não achei nenhum material em português, resolvi acabar com o medo de escrever e criei um projetinho de exemplo.
Para quem não tem paciência em ler artigos e já quer ver o código é só clicar em: [Talk is Cheap. Show me the Code](https://github.com/rafaellemes/enum-strategy-java-spring-boot).


Antes de Seguirmos, gostaria de deixar claro que esse texto:
- Assume que o leitor já tenha um conhecimento médio ou Familiaridade com Java e Spring Boot
- Não tem a intenção de ensinar os conceitos básico do Design Pattern ou do Spring Boot
- Tem como objetivo ser uma referência direta e rápida de uma maneira de implementar o Pattern
- Explicar o motivo de algumas decisões de Design

Vamos lá.

Uma característica dessa Feature (Nesse caso é um requisito não funcional ligado ao design da aplicacão), é que essa 'Estratégia' só pode ser usado por seu 'Caso de Uso' correspondente, logo, para evitar o vazamento dessa abstração para outros casos de uso dentro da mesma aplicação, todas as classes e interfaces ficarão dentro do mesmo pacote, e com exceção da Interface 'FooUseCase' que será _'public'_, as demais classes terão o modificador de acesso como 'default' de modo a restringir o acesso por outras classes fora do Pacote. 
**Obs:** Esta é só uma forma de implementação não é uma restrição, se não houver esse tipo de 'característica' em sua feature, pode-se deixar todas as classes como _'public'_).
Segue abaixo a estrutura de Pacote:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6tzk1mhzrz9jmdjr40pr.png)

Dito isso, vamos por partes:

Primeiramente, cria-se uma Interface que irá representar o 'Contrato', em outras palavras, a estrutura/comportamento padrão da 'Estratégia' que será utilizado pelo 'Client'(Classe que irá efetivamente utilizá-la, no nosso caso a Classe que representa o Caso de uso em si)

```
package br.com.lemes.enumstrategy.usecase.foo;

interface FooStrategy {
    String execute();
}
```

Em seguida, as classes que implementarão cada Estratégia:

```
package br.com.lemes.enumstrategy.usecase.foo;

import org.springframework.stereotype.Component;
@Component
final class FooStrategyImpl1 implements FooStrategy{

     @Override
     public String execute() {
         return "Foo Strategy Impl 1";
     }
 }
```

```
package br.com.lemes.enumstrategy.usecase.foo;

import org.springframework.stereotype.Component;

@Component
final class FooStrategyImpl2 implements FooStrategy{

     @Override
     public String execute() {
         return "Foo Strategy Impl 2";
     }
 }
```

```
package br.com.lemes.enumstrategy.usecase.foo;

import org.springframework.stereotype.Component;

@Component
final class FooStrategyImpl3 implements FooStrategy{

     @Override
     public String execute() {
         return "Foo Strategy Impl 3";
     }
 }

```

Em seguida, vamos criar a Interface do UseCase e sua Classe Concreta que irá Implementa-la:

```
package br.com.lemes.enumstrategy.usecase.foo;

public interface FooUseCase {
    String call(String id);
}
```

```
package br.com.lemes.enumstrategy.usecase.foo;

import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;

import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Stream;

@Service
@RequiredArgsConstructor
final class FooUseCaseImpl implements FooUseCase {

    @NonNull
    private final ApplicationContext context;


    @Override
    public String call(String id){
        
        Objects.requireNonNull(id);
        FooEnumStrategy fooEnumStrategy = FooEnumStrategy
                .findById(id)
                .orElseThrow(IllegalArgumentException::new); //Could be a Business Exception

        FooStrategy impl = fooEnumStrategy.getImpl(context);

        return impl.execute();
    }



    private enum FooEnumStrategy{

        IMPL1("Foo1", FooStrategyImpl1.class),
        IMPL2("Foo2", FooStrategyImpl2.class),
        IMPL3("Foo3", FooStrategyImpl3.class);

        FooEnumStrategy(String id, Class<? extends FooStrategy> impl) {
            this.id = id;
            this.impl = impl;
        }

        final String id;
        final Class<? extends FooStrategy> impl;

        public static Optional<FooEnumStrategy> findById(String id){


            Objects.requireNonNull(id);
            Supplier<Stream<FooEnumStrategy>> fooEnumStrategyStream
                    = () -> Stream.of(values())
                    .filter(strategy -> strategy.id.equals(id));


            if(fooEnumStrategyStream.get().count() > 1){
                fooEnumStrategyStream
                        .get()
                        .findFirst()
                        .ifPresent(strategy -> {
                            throw new RuntimeException(
String.format("Same id For two or more different Strategy: %s ", strategy.id));
                        });
            }


            return fooEnumStrategyStream.get().findFirst();
        }

        public FooStrategy getImpl(ApplicationContext context) {

            return context.getBean(impl);
        }
    }

}
```

Considerações Importantes:

- Foi uma decisão de design criar a _Enum _que servirá como Factory como uma 'Inner Class' com o modificador de acesso 'private', uma vez que, como fora supracitado, essas 'Estratégias' só devem ser usadas dentro do Caso de Uso. Também seria possível criar essa _Enum _em um arquivo separado mantendo o modificador dela como _'Default'_ mantendo-a inacessível para outras classes fora do pacote de forma a atender a restrição.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cq8ax6lvbhnzinixk0mm.png)



- Todas as classes concretas estão marcadas como _'final'_ que é para garantir que não haja extensibilidade das mesmas. Cada 'Estratégia' deve ter sua própria regra, e se por ventura uma estratégia depender de outra, a ideia é que essa dependência seja explicitada em forma de composição, embora seja bem improvável a necessidade disso, mas dessa maneira ainda é possível aplicar o [Proxy Pattern](https://refactoring.guru/design-patterns/proxy).
Exemplo:

```
@Component
final class FooStrategyImpl1 implements FooStrategy{

     @Override
     public String execute() {
         return "Foo Strategy Impl 1";
     }
 }
```

- A anotação _@RequiredArgsConstructor_ do Lombok cria um construtor em tempo de compilação que recebe como parâmetro as variáveis declaradas como final, obrigando o Injetor de dependência a construir a Classe de implementação com todas as suas dependências. Esta é uma maneira de implementar a Inversão de controle garantindo o Estado da Classe. **Vale ressaltar que: Declarar a Variável utilizando o _@Autowired_ apenas indica para o Spring injetar a Dependência, isso não é inversão de controle e provavelmente deixará seus testes um pouco mais complicado**

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rxno3rrplogpvksjk8wm.png)


- Foi decidido usar _Optional_ no método _findById_ da _FooEnumStrategy_ pois o cliente poderia passar por parâmetro para o _Enum_  um id inexistente, dessa forma fica a cargo da classe que implementa o 'Caso de uso' decidir a melhor maneira de lidar caso não haja uma 'Estratégia'. No caso do exemplo em questão, está lançando um _IllegalArgumentException_ como pode-se ver abaixo, mas poderia ser um erro de negócio, ou retornar um 404 para o Client Rest, enfim, o que a feature pedir =).

```
@Override
    public String call(String id){
        
        Objects.requireNonNull(id);
        FooEnumStrategy fooEnumStrategy = FooEnumStrategy
                .findById(id)
                .orElseThrow(IllegalArgumentException::new); //Could be a Business Exception

        FooStrategy impl = fooEnumStrategy.getImpl(context);

        return impl.execute();
    }
```

- Ao decidir **não** utilizar a Ordenação natural do _Enum_, conforme o projeto for evoluindo e novas 'Estratégias' forem sendo adicionada, corre o risco do desenvolvedor erroneamente criar dois itens do _Enum_ com o mesmo Identificador, gerando o que chamamos de 'Erro Silencioso' pois o _Enum_ pode entregar a 'Estratégia' errada para o Caso de Uso, em outras palavras, um bug que compilará normalmente e que pode ser percebido tarde demais pois não dará uma Exceção explicita. Portanto, para evitar isso o método _findById_ da _Enum_ faz uma validação da quantidade elementos com o id que foi passado por parâmetro e se houver mais que um lançará uma '_RuntimeException_' descrevendo o erro e apontando qual identificador está repetido. 
 Pode haver o caso de haver dois identificadores diferentes utilizando a mesma 'Estratégia' e não necessariamente ser um erro, nesse caso não consegui pensar em muito no que fazer =/
Exemplo:

```
public static Optional<FooEnumStrategy> findById(String id){


            Objects.requireNonNull(id);
            Supplier<Stream<FooEnumStrategy>> fooEnumStrategyStream
                    = () -> Stream.of(values())
                    .filter(strategy -> strategy.id.equals(id));


            if(fooEnumStrategyStream.get().count() > 1){
                fooEnumStrategyStream
                        .get()
                        .findFirst()
                        .ifPresent(strategy -> {
                            throw new RuntimeException(
String.format("Same id For two or more different Strategy: %s ", strategy.id));
                        });
            }


            return fooEnumStrategyStream.get().findFirst();
        }
```

- O método _getBean_ da _ApplicationContext_ que tu encontra no trecho abaixo: 

```
 public FooStrategy getImpl(ApplicationContext context) {

            return context.getBean(impl);
        }
```
também aceita como parâmetro uma _String_ que representa o nome do Bean, Este também é um jeito de implementar esse 'Factory'. Ficaria mais fácil de aplicar um Proxy Pattern na 'Estratégia' utilizando os recursos da anotação @Qualifier sem precisar mexer na Enum, entretanto deixaria de ser 'Type Safe', permitindo passar qualquer bean ou até mesmo um nome errado, resultando em um erro de _ClassCastException_ ou _BeansException_ em tempo de Execução. Em todo caso, ainda é possível aplicar o Proxy Pattern mas precisaria alterar a Enum passando como parâmetro do Elemento a Classe do Proxy (não é o melhor dos mundos mas é o que tem pra hoje kkk). 
 por exemplo:

```
IMPL4("Foo2", ProxyFooStrategyImpl2.class),
```

Bom, é isso. Disse que iria ser rápido mas percebi que escrevi demais. Paciência. --\O/--

Segue Links para Referência:
- [Projeto de Exemplo no Github](https://github.com/rafaellemes/enum-strategy-java-spring-boot)
- [Strategy Pattern](https://refactoring.guru/pt-br/design-patterns/strategy)
- [Injeção de Dependência X Inversão de Controle](https://www.devmedia.com.br/inversao-de-controle-x-injecao-de-dependencia/18763)
- [Proxy Pattern](https://refactoring.guru/design-patterns/proxy)