Jakarta Data: uma melhor integração entre Java e banco de dados

Neste artigo você vai ver:

Lidar com banco de dados é um dos maiores desafios dentro de uma arquitetura de software, pois além de escolher um dentre diversas opções no mercado, é preciso considerar as  integrações de persistência de uma aplicação. O objetivo deste artigo é mostrar um pouco desses padrões e conhecer uma nova proposta de especificação, o Jakarta Data, que tem o intuito de facilitar a vida de quem desenvolve com Java.

Entendendo as camadas que podem compor um software

Sempre que falamos sobre complexidade em um sistema corporativo, focamos na antiga estratégia militar romana: dividir e conquistar ou divide et impera, na qual para simplificar o todo, quebramos ele em pequenas unidades.

No mundo do software, uma das maneiras de simplificar e quebrar a complexidade é através de camadas, sejam elas físicas ou lógicas. Essas camadas, principalmente as lógicas, podem ser fragmentadas entre si, tornando-se componentes e/ou funcionalidades.

Esse movimento de quebrar camadas, inclusive, é um dos temas no livro Clean Architecture ao mencionar, por exemplo, a estratégia de separar o código de negócio com o de infraestrutura.

Os 3 tipos de camadas em uma aplicação

Indiferente do estilo arquitetural com o qual você esteja trabalhando, como monólito ou microsserviços, existe um número mínimo de 3 tipos de camadas em uma aplicação, de acordo com o livro Learning Domain-Driven Design. São elas:

  1. Presentation Layer (camada de apresentação): é a camada de interação com o usuário consumidor desse recurso, seja um usuário final ou alguém que atue na engenharia de software.
  2. Business Logic Layer (camada de negócio): como o próprio nome sugere, é onde ficará a lógica de negócio, ou nas palavras de Eric Evans, “é a camada que encontra o coração do negócio“.
  3. Data Access Layer (camada de acesso a dados): é a camada que mantém todo o mecanismo de persistência. No contexto de arquitetura Stateless, podemos pensar que é o local com alguma garantia de estado, salientando que o mecanismo de armazenamento é transparente, seja ele um banco de dados NoSQL, SQL, uma API Rest, etc. 

Imagem com três formas retangulares, que lista os três tipos de camadas descritas no livro "Learning Domain-Driven Design": a camada de apresentação, a camada de lógica de negócio e, por fim, a camada de acesso a dados.

Estes três tipos de camadas tendem a estar presentes na grande variedade de estilos de arquitetura de software, desde as simples, como o MVC, até mesmo as mais complexas, tal como CQRS, modelo hexagonal, onion, dentre outras.

Além dos três tipos de camadas, os modelos arquiteturais se preocupam bastante com a comunicação entre esses componentes para garantir a alta coesão e o baixo acoplamento, além de seguir o princípio de injeção de dependência, o DIP.

Este isolamento da camada de acesso a dados garante, principalmente, que o paradigma do banco de dados “vaze” para dentro do modelo de negócios. Sabemos que existe uma grande diferença – também conhecida como impedância -, em especial entre banco de dados relacionais e a programação orientada a objetos.

Camada de persistência

Realizada a devida introdução sobre camadas e da sua importância, focaremos na camada de persistência ou banco de dados. Um ponto de crítica e de admiração dentro do Paradigma de Orientação a Objeto é que existe uma grande gama de opções e padrões para comunicação. 

Para este artigo, falaremos sobre alguns padrões levando em consideração o princípio de responsabilidade única e também o acoplamento com o banco de dados da camada de negócio.

Vale salientar que não existe uma solução “bala de prata”, por isso é importante ter atenção em qual contexto esse padrão será utilizado. 

Tipos de padrão quando o assunto é banco de dados

Certamente, temos diversos modelos de integração e padrões entre o banco de dados e OOP. Este artigo foca nos seguintes:

  • Padrão 1: Data-Oriented programming
  • Padrão 2: Active Record
  • Padrão 3: Mapper

A seguir, vamos ver esses padrões em detalhes.

Padrão 1: Data-Oriented programming

Em seu livro Data-Oriented, o autor Yehonathan Sharvit traz como proposta uma redução de complexidade promovendo e tratando os dados como “cidadão de primeira classe”. 

Este padrão se resumo em três princípios:

  1. O código é separado por dados.
  2. Os dados são imutáveis.
  3. Os dados têm acesso flexível.

Essa proposta elimina a camada de entidades, focando explicitamente nos dicionários de dados através, por exemplo, de uma implementação de Map.

Padrão 2: Active Record

Esse padrão se tornou bastante popular depois do livro “Patterns of Enterprise Application Architecture” do Martin Follower.

De forma resumida, o padrão tem um único objeto, que é responsável tanto pelos dados quanto pelo comportamento. Isso permite simplificar as regras de negócio, uma vez que a ideia é que haja o acesso direto aos dados.

Contudo, no caso de arquiteturas mais complexas, o Active Record tende a se tornar bem difícil de lidar, já que adicionamos um alto acoplamento entre a solução de banco de dados sem falar na quebra de responsabilidade única. Ou seja: além de responsável pelas regras de negócio, a mesma classe fica também responsável pelas operações do banco de dados.

Dentro do mundo Java, a solução mais recente e que vem sendo amplamente utilizada é o Panache do Quarkus. Dessa forma, é possível realizar uma aplicação com microsserviços com apenas duas classes.


<pre=java>
@Entity
public class Person extends PanacheEntity {
    public String name;
    public LocalDate birth;
    public Status status;
}

Person person =...;

// persist it
person.persist();
ist<Person> people = Person.listAll();

// finding a specific person by ID
person = Person.findById(personId);
</pre>

Exemplo de trecho de código Panache e Quarkus extraído da documentação oficial.

Padrão 3: Mapper

Outra solução de comunicação entre banco de dados que vai ao encontro à premissa anterior é o Mapper. De modo geral, ele deixa separados a entidade e as operações de banco de dados, o que, no máximo, tende a aparecer dentro das classes são os metadados ou anotações do mundo Java.

Este padrão garante uma maior separação entre a camada de negócio e do banco de dados, além de garantir o Princípio de Responsabilidade Única. Com isso, simplificamos a manutenção e a leitura em aplicações, já que temos uma classe para a entidade e outra para operações de banco de dados.

Porém, para projetos simples, o Mapper tende a ser inviável. É o caso, por exemplo, de aplicações extremamente simples e com CRUD que, a depender do estilo, precisará de três ou quatro classes para alcançar o mesmo objetivo, ao contrário de padrões como Active Record, que realizaria esta função com metade do número de objetos.

O exemplo mais comum, certamente, é o ORM Hibernate. Porém, esse tipo de solução não é exclusividade para os bancos de dados relacionais, pois existem soluções para os NoSQL como o Spring Data e o Jakarta NoSQL.

<pre=”java”>
@Entity
public class Person {
    @Id
    String name;
    @Column
    LocalDate birth;
    @Column
    Status status;
}

Person person =...;
template.insert(person);

List<Person> people = template.query("select * from Person");

// finding a specific person by ID
person = template.find(Person.class, personId);

</pre>

Exemplo de trecho de código utilizando o Jakarta NoSQL.

Padrões DAO versus Repository 

A partir de um Mapper, é possível pensar em mais dois padrões. São eles:

  1. DAO (Data Access Object)
  2. Repository

Estes padrões levam em consideração o desacoplamento entre o modelo e a camada de persistência, mas a semelhança acaba por aí.

As diferenças estão no acoplamento por estar ligado à camada de acesso aos dados, e não ao modelo, além do foco nas camadas de serviços, na semântica e na nomenclatura. Vamos entender melhor esses modelos separadamente:

Data Access Object (DAO)

O padrão Data Access Object (DAO) isola as camadas de aplicação e negócio da persistência por meio de uma API de abstração. Esta API, por sua vez, traz uma semelhança próxima ao mecanismo do banco de dados.

<pre=”java>
Person person =...;
personDAO.insert(person);

// finding a specific person by ID
person = personDAO.read(id);
</pre>

Exemplo de trecho de código com operação de API a partir do padrão DAO.

Repository

O padrão Repository (Repositório) é um mecanismo que permite encapsular o comportamento de armazenamento, recuperação e pesquisa, emulando uma coleção de objetos. Dessa forma, o padrão esconde ainda mais a relação do mecanismo de persistência.

Este padrão foca na maior proximidade das entidades e esconde de onde os dados vêm, o que possibilita um Repository utilizar um padrão DAO em si.

Resumindo DAO x Repository

Um ponto importante é a semântica. Vamos dar um exemplo: considerando que uma entidade Carro teria como classe Garagem, que representa a coleção das entidades. O resultado seria o do trecho de código a seguir:

<pre=”java>
Car car =...;
garage.save(car);

// finding a specific car by ID
car = garage.get(id);
</pre>

Exemplo de trecho de código com uma aplicação que utiliza o padrão Repository.

Em resumo: se compararmos os dois tipos de padrão dentro do Mapper:

  • O DAO foca em uma abstração para acesso a algum tipo de dados.
  • O Repository foca em uma coleção, não importando a origem do objeto. Por esse motivo, é possível que um Repository utilize um DAO para alcançar o seu objetivo.

Nasce uma nova proposta de API para acesso a dados: Jakarta Data

Depois de conhecer todos os padrões, e suas respectivas importâncias e trade-offs, é importante darmos mais um passo: o de conhecer uma API que explore melhor esses padrões e, assim, facilite a adoção de boas práticas com Java e recursos de dados.

É aí que nasce a proposta de uma nova especificação chamada Jakarta Data. Conheça mais sobre ela aqui (Google Documentos).

O Jakarta Data traz como premissa três princípios:

  1. Uma API fácil e declarativa.
  2. Uma coesão nessa camada.
  3. Um desacoplamento das outras camadas.

Ainda em sua primeira versão, o foco desta nova proposta está na entrega do padrão Repository de modo que, com apenas uma interface e algumas anotações, seja possível implementar o Jakarta Data, inicialmente, em três projetos: Relacionais com JPA, NoSQL com Jakarta NoSQL e Rest com Rest Client for MicroProfile.

<pre=”java”>
@Entity
public class Person {
    @Id
    String name;
    @Column
    LocalDate birth;
    @Column
    Status status;
}

public interface PersonRepository extends Repository<Person, String> {
    
}


Person person =...;
repository.save(person);

Optional<Person> person = repository.findById(id);
repository.deleteById(id);
</pre>

Exemplo de trecho de código que mostra uma inicial da interface do padrão Repository do qual o cliente não saberá a origem dos dados, pois essa questão se torna responsabilidade de cada vendor.

Em resumo: a partir do Jakarta Data, é mais fácil lidar com a camada de negócio tanto em relação à persistência poliglota quanto com o seu desacoplamento.

Imagem que traz a abstração da camada de negócio, que desce para camada de dados e se divide nos bancos de dados SQL e NoSQL, além do Web Service.

Jakarta Data: mais facilidade e integração 

De fato, trabalhar com Orientação a Objetos e Persistência traz, em si, bastante complexidade por sua riqueza de opções, indiferente da sua origem. 

No geral, sempre levamos em conta o grau de isolamento das outras camadas, além da sua facilidade de uso e manutenção. Deste cenário, nasceu o Jakarta Data, cujo objetivo é auxiliar o uso desses padrões de um modo simples e integrado ao mundo de especificação Java. E, como padrão, a expectativa é o total suporte da comunidade, auxiliando e dando feedback.

Gostou de conhecer mais sobre o Jakarta Data? Então deixe suas impressões nos comentários!

Capa do artigo Jakarta Data em que vemos um jovem negro usando laptop enquanto está sentado perto da janela no escritório de desenvolvimento de software ao fundo, enquanto vemos uma tela de código em primeiro plano.
Foto Otávio Santana
Distinguished Software Engineer
Capacitando devs em todo o mundo para fornecer melhores softwares na nuvem.

Artigos relacionados

Capa do artigo em foto com duas pessoas escrevendo códigos em frente a dois notebooks.
Back-End
Postado em:
Imagem capa do conteúdo sobre Framework NestJS com a foto de uma mulher de cabelos trançados de costas de frente para um computador com códigos.
Back-End
Postado em:
Imagem capa do conteúdo sobre testes unitários, onde uma pessoa branca está em pé, segurando um notebook aberto dentro de um data center.
Back-End
Postado em:

Este site utiliza cookies para proporcionar uma experiência de navegação melhor. Consulte nossa Política de Privacidade.