Pular para o conteúdo principal

Capítulo 1: A Camada Service em Detalhes

Introdução​

A camada Service é o elemento central da arquitetura em 3 camadas. Ela faz a ponte entre o controller (que gerencia as requisições HTTP) e o repository (que acessa os dados). É aqui que reside a lógica de negócio da sua aplicação.

Objetivos do Capítulo​

Ao final deste capítulo, você será capaz de:

  • Compreender o papel e as responsabilidades da camada Service
  • Separar corretamente as responsabilidades entre as camadas
  • Implementar uma camada Service profissional
  • Utilizar a injeção de dependências corretamente
  • Aplicar boas práticas

Arquitetura em 3 Camadas​

Visão Geral​

Responsabilidades de Cada Camada​

CamadaResponsabilidadesExemplos
ControllerRecepção HTTP, Validação, Conversão, Respostas@RestController, @GetMapping
ServiceLógica de negócio, Orquestração, Transações, Validação de regras@Service, Cálculos
RepositoryAcesso a dados, Consultas ao BD, Mapeamento de entidades@Repository, JpaRepository

O que é Lógica de Negócio?​

Definição​

A lógica de negócio (business logic) é o conjunto de regras e processos que definem o comportamento da sua aplicação.

Exemplos de Lógica de Negócio​

❌ NÃO é lógica de negócio:

// Isto é acesso a dados
productRepository.findById(id);

// Isto é manipulação HTTP
return ResponseEntity.ok(product);

✅ Lógica de negócio:

// Verificar o estoque antes de criar um pedido
if (product.getStock() < order.getQuantity()) {
throw new InsufficientStockException();
}

// Calcular o preço total com impostos
double totalPrice = order.getSubtotal() * (1 + TAX_RATE);

// Aplicar uma promoção
if (customer.isPremium()) {
totalPrice = totalPrice * 0.9; // 10% de desconto
}

Criação de uma Camada Service​

Exemplo Básico​

package com.example.demo.service;

import com.example.demo.model.Product;
import com.example.demo.repository.ProductRepository;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class ProductService {

private final ProductRepository productRepository;

// Injeção por construtor (RECOMENDADO)
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}

// CREATE
public Product createProduct(Product product) {
// Lógica de negócio: validação
if (product.getPrice() < 0) {
throw new IllegalArgumentException("Le prix ne peut pas être négatif");
}

// Chamada ao repository
return productRepository.save(product);
}

// READ - Todos
public List<Product> getAllProducts() {
return productRepository.findAll();
}

// READ - Por ID
public Product getProductById(Long id) {
return productRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Product not found with id: " + id));
}

// UPDATE
public Product updateProduct(Long id, Product productDetails) {
Product product = getProductById(id);

// Lógica de negócio: validação
if (productDetails.getPrice() < 0) {
throw new IllegalArgumentException("Le prix ne peut pas être négatif");
}

// Atualização dos campos
product.setName(productDetails.getName());
product.setPrice(productDetails.getPrice());
product.setQuantity(productDetails.getQuantity());

return productRepository.save(product);
}

// DELETE
public void deleteProduct(Long id) {
Product product = getProductById(id);
productRepository.delete(product);
}
}

Pontos-Chave​

  1. @Service: Marca a classe como uma camada Service
  2. Injeção por construtor: Mais limpa e testável do que @Autowired nos campos
  3. Validação de negócio: Verificação das regras antes do acesso aos dados
  4. Tratamento de erros: Lançar exceções apropriadas

Injeção de Dependências​

Três Métodos de Injeção​

1. Injeção por Construtor (RECOMENDADA)​

@Service
public class ProductService {

private final ProductRepository productRepository;

public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
}

Vantagens:

  • ✅ Imutabilidade (final)
  • ✅ Fácil de testar
  • ✅ Obrigatória (erro de compilação se faltar)
  • ✅ Recomendado pelo Spring

2. Injeção por Campo (NÃO RECOMENDADA)​

@Service
public class ProductService {

@Autowired
private ProductRepository productRepository;
}

Desvantagens:

  • ❌ Difícil de testar
  • ❌ Dependências ocultas
  • ❌ Não é imutável

3. Injeção por Setter (RARAMENTE USADA)​

@Service
public class ProductService {

private ProductRepository productRepository;

@Autowired
public void setProductRepository(ProductRepository productRepository) {
this.productRepository = productRepository;
}
}

Separação de Responsabilidades​

Princípio SOLID: Single Responsibility​

Cada camada deve ter UMA única responsabilidade.

❌ Exemplo Ruim: Tudo no Controller​

@RestController
@RequestMapping("/api/products")
public class ProductController {

@Autowired
private ProductRepository productRepository;

@PostMapping
public Product create(@RequestBody Product product) {
// Lógica de negócio no controller (RUIM!)
if (product.getPrice() < 0) {
throw new IllegalArgumentException("Prix invalide");
}

// Acesso direto ao repository (RUIM!)
return productRepository.save(product);
}
}

Problemas:

  • ❌ Lógica de negócio no controller
  • ❌ Difícil de testar
  • ❌ Difícil de reutilizar
  • ❌ Violação do princípio da responsabilidade única

✅ Bom Exemplo: Separação Clara​

// Controller: Apenas manipulação HTTP
@RestController
@RequestMapping("/api/products")
public class ProductController {

private final ProductService productService;

public ProductController(ProductService productService) {
this.productService = productService;
}

@PostMapping
public Product create(@RequestBody Product product) {
return productService.createProduct(product);
}
}

// Service: Lógica de negócio
@Service
public class ProductService {

private final ProductRepository productRepository;

public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}

public Product createProduct(Product product) {
// Validação de negócio
if (product.getPrice() < 0) {
throw new IllegalArgumentException("Prix invalide");
}

// Chamada ao repository
return productRepository.save(product);
}
}

// Repository: Acesso a dados
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
// O Spring gera automaticamente a implementação
}

Exemplo Avançado: Orquestração​

A camada Service pode coordenar vários repositories:

@Service
public class OrderService {

private final OrderRepository orderRepository;
private final ProductRepository productRepository;
private final CustomerRepository customerRepository;

public OrderService(OrderRepository orderRepository,
ProductRepository productRepository,
CustomerRepository customerRepository) {
this.orderRepository = orderRepository;
this.productRepository = productRepository;
this.customerRepository = customerRepository;
}

public Order createOrder(Long customerId, Long productId, int quantity) {
// 1. Verificar se o cliente existe
Customer customer = customerRepository.findById(customerId)
.orElseThrow(() -> new RuntimeException("Customer not found"));

// 2. Verificar se o produto existe
Product product = productRepository.findById(productId)
.orElseThrow(() -> new RuntimeException("Product not found"));

// 3. Verificar o estoque
if (product.getStock() < quantity) {
throw new InsufficientStockException("Stock insuffisant");
}

// 4. Calcular o preço total
double totalPrice = product.getPrice() * quantity;

// 5. Aplicar desconto se cliente premium
if (customer.isPremium()) {
totalPrice = totalPrice * 0.9;
}

// 6. Criar o pedido
Order order = new Order();
order.setCustomer(customer);
order.setProduct(product);
order.setQuantity(quantity);
order.setTotalPrice(totalPrice);

// 7. Diminuir o estoque
product.setStock(product.getStock() - quantity);
productRepository.save(product);

// 8. Salvar o pedido
return orderRepository.save(order);
}
}

Gerenciamento de Transações​

Anotação @Transactional​

@Service
public class OrderService {

@Transactional
public Order createOrder(Long customerId, Long productId, int quantity) {
// Todas as operações estão em uma transação
// Se uma exceção for lançada, tudo é desfeito (rollback)

Product product = productRepository.findById(productId)
.orElseThrow(() -> new RuntimeException("Product not found"));

product.setStock(product.getStock() - quantity);
productRepository.save(product);

Order order = new Order();
// ... criação do pedido
return orderRepository.save(order);
}
}

Vantagens:

  • ✅ Consistência dos dados
  • ✅ Rollback automático em caso de erro
  • ✅ ACID (Atomicidade, Consistência, Isolamento, Durabilidade)

Boas Práticas​

DO (Boas Práticas)​

✅ Injeção por construtor

public ProductService(ProductRepository repository) {
this.repository = repository;
}

✅ Métodos com nomes explícitos

public Product createProduct(Product product)
public void activateUser(Long userId)
public double calculateTotalPrice(Order order)

✅ Validação de negócio no Service

if (product.getPrice() < 0) {
throw new IllegalArgumentException("Prix invalide");
}

✅ Tratamento adequado de exceções

return productRepository.findById(id)
.orElseThrow(() -> new ProductNotFoundException(id));

DON'T (O que evitar)​

❌ Lógica de negócio no Controller

// RUIM: No controller
@PostMapping
public Product create(@RequestBody Product product) {
if (product.getPrice() < 0) { // Lógica de negócio!
throw new Exception();
}
return service.create(product);
}

❌ Acesso direto ao Repository a partir do Controller

// RUIM
@RestController
public class ProductController {
@Autowired
private ProductRepository repository; // Direto!
}

❌ Injeção por campo

// RUIM
@Autowired
private ProductRepository repository;

Exercícios Práticos​

Exercício 1: Service Básico​

Objetivo: Criar uma camada Service completa

Instruções:

  1. Crie uma entidade Student:
    • Long id
    • String firstName
    • String lastName
    • String email
    • int grade
  2. Crie StudentRepository
  3. Crie StudentService com:
    • Todas as operações CRUD
    • Validação: grade entre 0 e 100
    • Validação: email válido
  4. Crie StudentController que utiliza o service

Exercício 2: Orquestração​

Objetivo: Coordenar vários repositories

Instruções:

  1. Crie Library (id, name, address)
  2. Crie Book (id, title, author, libraryId)
  3. Crie LibraryService com um método:
    public Library createLibraryWithBooks(Library library, List<Book> books)
    • Criar a biblioteca
    • Criar todos os livros associados
    • Tudo em uma transação

Resumo do Capítulo​

Pontos-Chave​

  • A camada Service contém a lógica de negócio
  • Separação de responsabilidades: Controller → Service → Repository
  • Injeção por construtor (recomendada)
  • @Transactional para consistência dos dados
  • Validação no Service, não no Controller

O que Você Sabe Agora​

✅ Papel e responsabilidades da camada Service
✅ Separação de responsabilidades
✅ Injeção de dependências
✅ Orquestração de múltiplos repositories
✅ Boas práticas profissionais


Próximo capítulo: Validação de Dados

Voltar ao Módulo 3