Introdução

O Spring Boot é um framework para criação de serviços stand-alone, ou serverless, como é o termo usado hoje em dia. Faz parte do ecossistema Spring, que contém um grande número de bibliotecas para auxiliar os desenvolvedores a criar suas aplicações.

Neste artigo, iremos demonstrar o design pattern Chain of Responsibility e como implementá-lo com baixo acoplamento no Spring Boot.

Inversion of control (ioc) e a injeção de dependências

Hoje o IOC (Inversion of Control) é um padrão de projeto muito forte e muito utilizado principalmente nas aplicações Java EE, tendo a injeção de dependências (Dependency Injection) como uma implementação deste pattern. Consiste em injetar dependências de componentes nas classes onde as declaramos, através de anotações específicas, de acordo com o framework/tecnologia utilizados.

Injeção de Dependências no Spring Boot

Para anotar uma classe no Spring Boot, são necessárias duas coisas: que as classes sejam Components (ou seus subtipos) e que seja adicionada à anotação @Autowired.

@Component
public class AlunoService {
    // ... código omitido
}
@Component
public class MatriculaService {

    // Injeção de alunoService 
    @Autowired
    private AlunoService alunoService;

    public void matricula(Aluno aluno, Curso curso) {
        // ... código omitido
    }
}

No Spring Boot, temos outros subtipos de @Component, como o @Repository e o @Service, mas estes produzem o mesmo efeito de injeção de dependências.

Design Pattern “Chain of Responsibility”

Talvez você já tinham ouvido falar neste design pattern, que consiste em criar uma “cadeia de responsabilização” das suas regras de negócio, isolando-as em classes menores e “passando a bola” para o próximo item da lista, como numa cadeia de responsabilização.

A idéia deste padrão de projeto é produzir um baixo acoplamento entre estas classes, de forma que adicionar novas regras não impacte nas regras já existentes.

Exemplo de aplicação

Uma forma mais tradicional de realizar esse pattern é você declarar todas as classes que irão compor essas cadeias e, então, encadeá-las para as chamadas, como no exemplo abaixo:

Exemplo 1: Chain of responsibility com POJO’s

/// Classe model de aluno
public class Aluno {
 // dados omitidos
}

public interface IAlunoProcessor() {
 void setNext(IAlunoProcessor next);
 void chain(Aluno aluno);
}

public class FinanceiroService implements IAlunoProcessor {
 private IAlunoProcessor next;

 public void setNext(IAlunoProcessor next) {
  this.next = next;
 }

 public void chain(Aluno aluno) {
  // Realiza tratamentos de negócio para o financeiro
  // Depois retorna o aluno para a próxima cadeia
  if (this.next != null) next.chain(aluno);
 }
}

public class BibliotecaService implements IAlunoProcessor {
 private IAlunoProcessor next;

 public void setNext(IAlunoProcessor next) {
  this.next = next;
 }

 public void chain(Aluno aluno) {
  // Realiza tratamentos de negócio para a biblioteca
  // Depois retorna o aluno para a próxima cadeia
  if (this.next != null) next.chain(aluno);
 }
}

public class CantinaService implements IAlunoProcessor {
 private IAlunoProcessor next;

 public void setNext(IAlunoProcessor next) {
  this.next = next;
 }

 public Aluno chain(Aluno aluno) {
  // Realiza tratamentos de negócio para a cantina
  // Depois retorna o aluno para a próxima cadeia
  if (this.next != null) next.chain(aluno);
 }
}

// Classe de negócio de matrícula
public class MatriculaService {

 public void matricular(Aluno aluno) {
  // Criação dos serviços
  FinanceiroService financeiroService = new FinanceiroService();
  BibliotecaService bibliotecaService = new BibliotecaService();
  CantinaService cantinaService = new CantinaService();

  // Aninhamento das cadeias
  financeiroService.setNext(bibliotecaService);
  bibliotecaService.setNext(cantinaService);

  // Chama o primeiro serviço, que dispara todas as cadeias.
  financeiroService.chain(aluno);
 }
}

Percebe-se no exemplo que ainda existe um certo acoplamento, visto que a classe MatriculaService precisa ter conhecimento de todas as classes da cadeia para que a cadeia possa ser executada.

Spring Boot e as múltiplas Injeções

Um recurso no Spring Boot (aqui foi testado na versão 2.0) permite que injetemos diversos Componentes, ou Services, filtrando os mesmos por uma interface ou até por uma anotação específica.

Vejamos este mesmo exemplo usando Spring Boot:

// Classe model de aluno
public class Aluno {
 // dados omitidos
}

public interface IAlunoProcessor() {
 void setNext(IAlunoProcessor next);
 void chain(Aluno aluno);
}

public class FinanceiroService implements IAlunoProcessor {
 private IAlunoProcessor next;

 public void setNext(IAlunoProcessor next) {
  this.next = next;
 }

 public void chain(Aluno aluno) {
  // Realiza tratamentos de negócio para o financeiro
  // Depois retorna o aluno para a próxima cadeia
  if (this.next != null) next.chain(aluno);
 }
}

public class BibliotecaService implements IAlunoProcessor {
 private IAlunoProcessor next;

 public void setNext(IAlunoProcessor next) {
  this.next = next;
 }

 public void chain(Aluno aluno) {
  // Realiza tratamentos de negócio para a biblioteca
  // Depois retorna o aluno para a próxima cadeia
  if (this.next != null) next.chain(aluno);
 }
}

public class CantinaService implements IAlunoProcessor {
 private IAlunoProcessor next;

 public void setNext(IAlunoProcessor next) {
  this.next = next;
 }

 public Aluno chain(Aluno aluno) {
  // Realiza tratamentos de negócio para a cantina
  // Depois retorna o aluno para a próxima cadeia
  if (this.next != null) next.chain(aluno);
 }
}

// Classe de negócio de matrícula
public class MatriculaService {

 public void matricular(Aluno aluno) {
  // Criação dos serviços
  FinanceiroService financeiroService = new FinanceiroService();
  BibliotecaService bibliotecaService = new BibliotecaService();
  CantinaService cantinaService = new CantinaService();

  // Aninhamento das cadeias
  financeiroService.setNext(bibliotecaService);
  bibliotecaService.setNext(cantinaService);

  // Chama o primeiro serviço, que dispara todas as cadeias.
  financeiroService.chain(aluno);
 }
} // Classe model de aluno
public class Aluno {
 // dados omitidos
}

public interface IAlunoProcessor() {
 Integer getPriority();
 void chain(Aluno aluno);
}

@Component
public class FinanceiroService implements IAlunoProcessor {

 public Integer getPriority() {
  return 1;
 }

 public void chain(Aluno aluno) {
  // realiza tratamentos de negócio para o financeiro...
 }
}

@Component
public class BibliotecaService implements IAlunoProcessor {
 public Integer getPriority() {
  return 2;
 }

 public void chain(Aluno aluno) {
  // realiza tratamentos de negócio para a biblioteca...
 }
}

@Component
public class CantinaService implements IAlunoProcessor {
 public Integer getPriority() {
  return 3;
 }

 public Aluno chain(Aluno aluno) {
  // realiza tratamentos de negócio para a cantina...
 }
}

// Classe de negócio de matrícula
@Component
public class MatriculaService {

 @Autowired
 private List < IAlunoProcessor > processors;

 public void matricular(Aluno aluno) {
  // Aqui já temos todas as classes que extendem de IAlunoProcessor injetadas
  processors
   // Transformamos a mesma em stream
   .stream()
   // Ordenamos pelo método getPriority() (Esta parte é opcional)
   .sorted((a, b) -> a.getPriority().compareTo(b.getPriority()))
   // E executamos cada uma das regras.
   .forEach(next -> next.chain(aluno));
 }
}

Perceba que as classes FinanceiroService, BibliotecaService, e CantinaService, não têm mais vínculo entre si, e a classe MatriculaService não tem qualquer dependência com as três anteriores, ou seja, aplicamos baixo acoplamento. Para adicionar novas classes, basta implementar a interface IAlunoProcessor e definir a prioridade, que a mesma será automaticamente executada pelo serviço de matrícula.

Conclusão

O Spring é um framework bem poderoso para criação de aplicações java, principalmente para microsserviços e aplicações stand-alone. O recurso de múltiplas injeções e o pattern chain of responsibility é um, entre muitos, que permite criar regras de negócio claras e isoladas entre si, permitindo um código limpo e de fácil compreensão a todas as pessoas desenvolvedoras.