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
| Camada | Responsabilidades | Exemplos |
|---|---|---|
| Controller | Recepção HTTP, Validação, Conversão, Respostas | @RestController, @GetMapping |
| Service | Lógica de negócio, Orquestração, Transações, Validação de regras | @Service, Cálculos |
| Repository | Acesso 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
- @Service: Marca a classe como uma camada Service
- Injeção por construtor: Mais limpa e testável do que @Autowired nos campos
- Validação de negócio: Verificação das regras antes do acesso aos dados
- 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:
- Crie uma entidade
Student:- Long id
- String firstName
- String lastName
- String email
- int grade
- Crie
StudentRepository - Crie
StudentServicecom:- Todas as operações CRUD
- Validação: grade entre 0 e 100
- Validação: email válido
- Crie
StudentControllerque utiliza o service
Exercício 2: Orquestração
Objetivo: Coordenar vários repositories
Instruções:
- Crie
Library(id, name, address) - Crie
Book(id, title, author, libraryId) - Crie
LibraryServicecom 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