Separando as responsabilidades

Apesar da aplicação estar funcionando isso não significa que temos um bom código criado. O código atual possui diversas responsabilidades, como por exemplo, a validação dos dados, a persistência dos dados e a exibição dos dados. Isso é um problema, pois dificulta a manutenção e a evolução da aplicação. Além de que, quando trabalhamos com código trabalhamos em equipe, ou seja, outras pessoas precisarão entender o código e fazer alterações. Por isso, é importante que o código seja bem organizado e cada parte do código tenha sua responsabilidade bem definida.

A primeira parte é com relação ao Controller criado. Atualmente ele tem muitas responsabilidades. Ele está responsável por manipular a fonte de dados da aplicação - a lista de produtos, receber as requisições e enviar os dados para a visão. Isso é um problema grave.

A primeira modificação importante é que a manipulação da fonte de dados não deve ser realizada pelo controlador. Por enquanto nossa fonte de dados é apenas uma lista, mas em uma aplicação real a fonte de dados pode ser um banco de dados, um arquivo, uma API, etc. Por isso, é importante que a manipulação da fonte de dados seja realizada por uma camada específica para isso. Essa camada é chamada de repositório.

Criando o repositório

O repositório é uma classe que possui a responsabilidade de manipular a fonte de dados da aplicação. Para isso, ele possui métodos para adicionar, remover, atualizar e buscar os dados. Esses métodos são conhecidos pelo acrônimo CRUD de Create, Read, Update e Delete.

Não esqueça: O repositório é o responsável por realizar as operações CRUD na fonte de dados da aplicação.

Para criar o repositório, crie uma nova classe chamada ProductRepository. Aproveite para separar os diferentes tipos de arquivos em suas respectivas pastas. Crie o repositório dentro de uma pasta repository.

Essa classe deve possuir um atributo privado do tipo List<Product> que será a fonte de dados da aplicação. Além disso, a classe deve possuir os métodos CRUD para manipular os objetos dessa lista.

1
public class ProductRepository {
2
3
private List<Product> products = new ArrayList<>();
4
5
public List<Product> findAll() {
6
return products;
7
}
8
9
public void save(Product product) {
10
products.add(product);
11
}
12
13
public void update(Product product) {
14
Product productToUpdate = findById(product.getId());
15
productToUpdate.setName(product.getName());
16
productToUpdate.setPrice(product.getPrice());
17
productToUpdate.setDescription(product.getDescription());
18
}
19
20
public void delete(String id) {
21
Product productToDelete = findById(id);
22
products.remove(productToDelete);
23
}
24
25
public Product findById(String id) {
26
for (Product product : products) {
27
if (product.getId().equals(id)) {
28
return product;
29
}
30
}
31
return null;
32
}
33
34
}

No total foram criados 5 métodos. O método findAll retorna todos os produtos da lista. O método save adiciona um novo produto na lista. O método update atualiza um produto existente na lista. O método delete remove um produto da lista. E o método findById busca um produto pelo seu id. Perceba que toda a manipulação da lista é feita dentro do repositório. O controlador não precisa e nem deve se preocupar com isso.

Utilizando o repositório

Agora que o repositório foi criado, é necessário utilizá-lo no controlador. Para isso, é necessário criar um atributo do tipo ProductRepository no controlador e inicializá-lo no construtor. Além disso, é necessário alterar os métodos do controlador para que eles utilizem o repositório para manipular a fonte de dados.

1
@Controller
2
public class ProductController {
3
4
private ProductRepository productRepository = new ProductRepository();
5
6
@GetMapping("/products")
7
public String index(Model model) {
8
model.addAttribute("products", productRepository.findAll());
9
return "list";
10
}
11
12
@GetMapping("/products/create")
13
public String create(Model model) {
14
Product product = new Product();
15
model.addAttribute("product", product);
16
return "create";
17
}
18
19
@PostMapping("/products/store")
20
public String store(@Valid Product product, BindingResult result) {
21
if (result.hasErrors()) { return "create"; }
22
productRepository.save(product);
23
return "redirect:/products";
24
}
25
26
@GetMapping("/products/show")
27
public String show(Model model, @RequestParam("id") String id){
28
Product product = productRepository.findById(id);
29
model.addAttribute("product", product);
30
return "show";
31
}
32
33
@GetMapping("/products/edit")
34
public String edit(Model model, @RequestParam("id") String id){
35
Product product = productRepository.findById(id);
36
model.addAttribute("product", product);
37
return "edit";
38
}
39
40
@PostMapping("/products/update")
41
public String update(Product product){
42
productRepository.update(product);
43
return "redirect:/products";
44
}
45
46
@GetMapping("/products/delete")
47
public String delete(@RequestParam("id") String id){
48
productRepository.delete(id);
49
return "redirect:/products";
50
}
51
52
}

Três grandes modificações foram feitas no controlador. Primeiramente foi criado o atributo productRepository que é inicializado no construtor. Depois, todos os métodos que manipulavam a lista de produtos foram alterados para que utilizem os métodos implementados no repositório. Por fim o método findProductById foi movido para o repositório. Perceba que o controlador ficou mais simples e com menos responsabilidades.

O controlador não deve se preocupar com a manipulação da fonte de dados. Ele deve ser responsável apenas por receber as requisições e enviar os dados para a visão.

Por fim teste a aplicação e certifique-se que ela continua funcionando.