Arquitetura em camadas: presentation, business e data

1. Introdução à Arquitetura em Camadas

A arquitetura em camadas é um dos padrões mais antigos e amplamente adotados no desenvolvimento de software. Seu princípio fundamental é organizar o sistema em níveis hierárquicos, onde cada camada possui responsabilidades bem definidas e se comunica apenas com as camadas adjacentes. O objetivo central é separar preocupações (separation of concerns), promovendo coesão interna e baixo acoplamento entre os componentes.

Historicamente, esse modelo ganhou força com a migração dos sistemas mainframe para arquiteturas cliente-servidor nos anos 1990, e se consolidou com o surgimento de frameworks como J2EE e .NET. A popularização da internet e das aplicações web reforçou a necessidade de separar claramente a interface do usuário (presentation), a lógica de negócio (business) e o acesso a dados (data).

Os benefícios dessa separação são múltiplos:
- Coesão: cada camada agrupa funcionalidades relacionadas.
- Baixo acoplamento: alterações em uma camada impactam minimamente as demais.
- Reutilização: a lógica de negócio pode ser reutilizada por diferentes interfaces (web, mobile, API).
- Testabilidade: é possível testar cada camada de forma isolada.

2. A Camada de Apresentação (Presentation Layer)

A camada de apresentação é responsável por toda a interação com o usuário. Ela gerencia a exibição de informações e a captura de entradas, delegando o processamento para as camadas inferiores. Suas principais responsabilidades incluem:

  • Renderização de interfaces (telas, páginas, formulários).
  • Validação básica de entrada (formato de campos, obrigatoriedade).
  • Orquestração de navegação e estados da interface.
  • Tratamento de erros de usuário (mensagens amigáveis).

Padrões comuns associados a essa camada incluem:
- MVC (Model-View-Controller): separa a interface em modelo, visão e controlador.
- MVP (Model-View-Presenter): o presenter contém a lógica de apresentação.
- MVVM (Model-View-ViewModel): popular em frameworks reativos como Angular e WPF.

Exemplo de um controlador MVC em uma aplicação web:

// Presentation Layer - Controller
public class UsuarioController {
    private UsuarioService usuarioService;

    public UsuarioController(UsuarioService service) {
        this.usuarioService = service;
    }

    public ViewModel obterUsuario(int id) {
        UsuarioDTO dto = usuarioService.buscarPorId(id);
        return new ViewModel(dto.nome, dto.email);
    }
}

O que NÃO deve pertencer à apresentação:
- Regras de negócio (ex.: cálculo de imposto, validação de elegibilidade).
- Acesso direto a banco de dados ou APIs externas.
- Lógica de persistência ou cache de dados.

3. A Camada de Negócio (Business Layer)

Esta é a camada central da aplicação, onde residem as regras de negócio, validações complexas e fluxos de domínio. Ela deve ser independente de frameworks e tecnologias de interface ou persistência, garantindo que a lógica fundamental do sistema possa ser testada e evoluída sem amarras técnicas.

Componentes típicos da camada de negócio:
- Entidades de domínio: objetos que representam conceitos do negócio (ex.: Pedido, Cliente, Produto).
- Serviços de aplicação: orquestram casos de uso, coordenando entidades e serviços de domínio.
- Serviços de domínio: encapsulam regras que não pertencem a uma única entidade.

Exemplo de serviço de aplicação:

// Business Layer - Application Service
public class PedidoService {
    private PedidoRepository pedidoRepository;
    private EstoqueService estoqueService;

    public PedidoService(PedidoRepository repo, EstoqueService estoque) {
        this.pedidoRepository = repo;
        this.estoqueService = estoque;
    }

    public void criarPedido(List<ItemPedido> itens, Cliente cliente) {
        // Regra de negócio: verificar estoque antes de criar o pedido
        for (ItemPedido item : itens) {
            if (!estoqueService.temDisponibilidade(item.produto, item.quantidade)) {
                throw new EstoqueInsuficienteException(item.produto);
            }
        }
        Pedido pedido = new Pedido(cliente, itens);
        pedidoRepository.salvar(pedido);
    }
}

Para manter a testabilidade, a camada de negócio deve:
- Depender de abstrações (interfaces), não de implementações concretas.
- Não conter referências a bibliotecas de interface gráfica ou ORMs.
- Permitir injeção de dependências para facilitar mocks em testes unitários.

4. A Camada de Dados (Data Layer)

A camada de dados é responsável por toda a comunicação com fontes de armazenamento: bancos relacionais, NoSQL, sistemas de arquivos, APIs externas, etc. Seu objetivo é abstrair os detalhes de persistência para as camadas superiores, oferecendo uma interface limpa e consistente.

Padrões comuns:
- Repository: abstrai o acesso a dados, simulando uma coleção em memória.
- DAO (Data Access Object): encapsula operações CRUD específicas.
- Unit of Work: gerencia transações e rastreia alterações.

Exemplo de um repositório:

// Data Layer - Repository Interface
public interface PedidoRepository {
    Pedido buscarPorId(int id);
    void salvar(Pedido pedido);
    List<Pedido> buscarPorCliente(int clienteId);
}

// Data Layer - Concrete Implementation
public class PedidoRepositoryJPA implements PedidoRepository {
    private EntityManager entityManager;

    public Pedido buscarPorId(int id) {
        return entityManager.find(Pedido.class, id);
    }

    public void salvar(Pedido pedido) {
        entityManager.persist(pedido);
    }

    public List<Pedido> buscarPorCliente(int clienteId) {
        return entityManager.createQuery(
            "SELECT p FROM Pedido p WHERE p.cliente.id = :clienteId", Pedido.class)
            .setParameter("clienteId", clienteId)
            .getResultList();
    }
}

O Princípio da Inversão de Dependência (DIP) é essencial aqui: as camadas superiores definem as interfaces que a camada de dados implementa. Isso permite trocar a tecnologia de persistência (ex.: de JPA para JDBC ou MongoDB) sem afetar a lógica de negócio.

5. Regras de Dependência e Fluxo de Comunicação

A regra fundamental da arquitetura em camadas é que camadas superiores dependem de camadas inferiores, nunca o contrário. O fluxo típico de comunicação é descendente:

  1. Apresentação → chama serviços da camada de negócio.
  2. Negócio → chama repositórios da camada de dados.
  3. Dados → interage com o banco ou API externa.

A comunicação ascendente (da camada inferior para a superior) deve ser feita por meio de eventos ou callbacks, evitando dependências diretas. Por exemplo, a camada de dados pode notificar a camada de negócio sobre alterações via eventos de domínio.

Violações comuns:
- Vazar entidades de dados (ex.: objetos JPA) para a camada de apresentação, expondo detalhes de implementação.
- Colocar lógica de negócio em controllers ou repositórios.
- Criar dependências circulares entre camadas (ex.: negócio chamando apresentação).

6. Comparação com Arquiteturas Contemporâneas

A arquitetura em camadas clássica serviu de base para padrões mais modernos:

  • Clean Architecture (Robert C. Martin): mantém a separação em camadas, mas inverte a direção das dependências. O domínio fica no centro, e as camadas externas (interface, infraestrutura) dependem dele. É uma evolução que reforça o DIP.
  • Arquitetura Hexagonal (Ports and Adapters): similar à Clean Architecture, define portas (interfaces) no núcleo e adaptadores (implementações) na periferia. A comunicação é bidirecional através de portas.

A abordagem clássica ainda é a melhor escolha quando:
- O sistema é relativamente simples e não justifica a complexidade adicional.
- A equipe está familiarizada com o modelo e os prazos são curtos.
- Não há necessidade de suportar múltiplas interfaces ou fontes de dados heterogêneas.

7. Boas Práticas e Armadilhas Comuns

Organização de pacotes:
- Por camada: controller/, service/, repository/. Simples, mas pode gerar muitos arquivos.
- Por funcionalidade: pedido/, cliente/, produto/. Melhor coesão, mas requer cuidado com dependências.

Armadilhas:
- Camadas anêmicas: quando a camada de negócio se torna apenas um pass-through para a camada de dados.
- Dependências circulares: geralmente resolvidas com inversão de dependência ou introdução de uma nova abstração.
- Vazamento de abstrações: expor detalhes de ORM (ex.: @Entity, @Column) para a apresentação.

Estratégias de evolução:
- Comece com uma arquitetura simples em três camadas.
- Refatore incrementalmente: introduza interfaces, depois serviços de domínio, e eventualmente eventos.
- Adote padrões como CQRS (Command Query Responsibility Segregation) se a complexidade crescer.


Referências