No Spring, nem todo @Service deveria existir
Em muito projeto Spring, existe um desenho que aparece antes mesmo da necessidade.
Chega um Controller, alguém cria um Service, depois um Repository.
Na superfície, isso parece arquitetura.
Na prática, muitas vezes é só um corredor a mais dentro do código.
O problema não é usar @Service.
O problema é quando ele vira reflexo.
Uma classe anotada com @Service deveria representar uma responsabilidade da aplicação.
Quando ela só repassa chamada para a próxima camada, o nome sugere regra de negócio, mas o comportamento real é só delegação.
Quando a camada do meio não segura nada
O Spring registra essa classe como Bean, injeta dependências e deixa tudo funcionando.
Mas isso só prova que o framework conseguiu gerenciar a instância.
Não prova que a abstração faz sentido.
Esse ponto conversa diretamente com os posts sobre Bean, IoC e DI.
O Spring cria, conecta e gerencia objetos. O desenho da aplicação ainda continua sendo responsabilidade sua.
Quando todo fluxo vira controller -> service -> repository por obrigação, a camada de service começa a existir mesmo quando não tem decisão, coordenação, regra nem fronteira relevante para sustentar sua presença.
Um exemplo que parece organizado, mas não acrescenta quase nada
@RestController
@RequestMapping("/pedidos")
public class PedidoController {
private final PedidoService pedidoService;
public PedidoController(PedidoService pedidoService) {
this.pedidoService = pedidoService;
}
@GetMapping("/{id}")
public PedidoResponse buscar(@PathVariable Long id) {
Pedido pedido = pedidoService.buscarPorId(id);
return PedidoResponse.from(pedido);
}
}
@Service
public class PedidoService {
private final PedidoRepository pedidoRepository;
public PedidoService(PedidoRepository pedidoRepository) {
this.pedidoRepository = pedidoRepository;
}
public Pedido buscarPorId(Long id) {
return pedidoRepository.buscarPorId(id);
}
}
Tudo funciona.
Mas o que exatamente PedidoService está protegendo aqui?
Se remover essa classe, o sistema perde uma regra de negócio, uma transação, uma orquestração importante ou uma política de aplicação?
Se a resposta for não, essa camada provavelmente existe mais por costume do que por necessidade.
Esse tipo de classe adiciona nome para manter, ponto extra de debug, método extra para atravessar e mais um lugar para esconder responsabilidades que ainda nem foram definidas com clareza.
Em alguns cenários simples, o próprio ecossistema Spring já mostra que essa camada intermediária nem sempre é obrigatória.
Com Spring Data, operações básicas de repositório já nascem de contratos prontos.
E com Spring Data REST, em certos casos, até a exposição do recurso pode acontecer a partir do repositório, sem pedir um @Service só para repassar chamada.
Um exemplo mínimo ficaria assim:
@Entity
public class Pedido {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String cliente;
}
@RepositoryRestResource(path = "pedidos")
public interface PedidoRepository extends JpaRepository<Pedido, Long> {
}
Nesse desenho, o próprio ecossistema resolve o básico.
O repositório já nasce com operações prontas e o Spring Data REST consegue exportar esse repositório como recurso HTTP, sem exigir uma classe PedidoService vazia só para encaminhar findById, save ou delete.
Ou seja: não é que a anotação crie um @RestController seu no código. O ponto é que a infraestrutura do Spring Data REST já consegue publicar esse CRUD básico a partir do contrato do repositório.
Isso não significa que controller e service deixaram de ter valor.
Significa só que o framework já deixa claro, pelo próprio desenho, que classe intermediária não deveria existir por cerimônia.
Em cenários simples, uma arquitetura mais direta pode ser a escolha mais honesta.
Se não existe regra relevante, coordenação entre fluxos, fronteira transacional específica nem lógica de aplicação que precise ficar explícita, insistir em todas as camadas só porque “é assim que se faz” pode adicionar mais rito do que clareza.
Quando essa complexidade aparece, controller e service voltam a ter um papel claro.
O que costuma justificar um @Service de verdade
Um service começa a fazer sentido quando ele representa um caso de uso ou uma coordenação real da aplicação.
É quando existe trabalho de orquestrar mais de um colaborador, aplicar uma regra de negócio, definir uma fronteira transacional ou deixar explícita uma operação importante do sistema.
@Service
public class FecharPedidoService {
private final PedidoRepository pedidoRepository;
private final EstoqueService estoqueService;
private final NotificacaoService notificacaoService;
public FecharPedidoService(
PedidoRepository pedidoRepository,
EstoqueService estoqueService,
NotificacaoService notificacaoService
) {
this.pedidoRepository = pedidoRepository;
this.estoqueService = estoqueService;
this.notificacaoService = notificacaoService;
}
@Transactional
public void fechar(Long pedidoId) {
Pedido pedido = pedidoRepository.buscarPorId(pedidoId);
pedido.fechar();
estoqueService.reservarItensDo(pedido);
notificacaoService.enviarConfirmacao(pedido);
}
}
Aqui a classe segura uma operação reconhecível.
Ela coordena dependências, protege uma ação relevante e deixa visível que existe um fluxo de negócio acontecendo.
Esse tipo de @Service não está preenchendo um espaço entre controller e repository.
Ele está representando uma responsabilidade.
Também vale o cuidado inverso: nem tudo que é regra precisa ir para um service.
Se o comportamento pertence à própria entidade, empurrar tudo para uma classe de serviço só troca um problema por outro e ajuda a criar o clássico Service gigante.
Quando o construtor começa a pedir dependências demais, muitas vezes o problema não é a injeção. É o fato de a abstração já ter crescido além do que deveria.
O ponto que muda sua leitura do @Service
@Service não deveria ser um pedágio obrigatório entre a entrada HTTP e o acesso a dados.
Ele deveria marcar uma responsabilidade de aplicação que realmente merece existir.
Se tirar a classe do caminho não muda nada além de remover um encaminhamento intermediário, talvez ela nunca tenha conquistado esse espaço.
Abstração boa protege complexidade real.
Abstração vazia só multiplica arquivos, nomes e desvios na leitura.
No Spring, o container gerencia essa classe sem reclamar.
Quem precisa perguntar se ela deveria existir é você.
