Spring Data com banco dedicado para leitura

iundarigun
Dev Cave
Published in
3 min readNov 14, 2020

Com o aumento do volume de dados, é frequente usar bancos réplica dedicados para leitura. Isso significa que o banco principal é usado para escrita enquanto tem uma réplica que é usada única e exclusivamente para leitura. Isso tem vários benefícios, mas traz uma complexidade na hora de acessar esses dados pelo caminhos habituais (Hibernate, JPA, Spring Data).

Aqui na Wavy temos alguns bancos com essa configuração e fiquei interessado em achar uma solução. Procurando um pouco, achei algumas abordagens diferentes mas, na essência, todos usam o conceito de roteamento de transação. Em inglês fica mais chique: Transaction Routing

Transaction Routing

Spring fornece um tipo de Datasource que, dado um mapa de Datasources e uma função decisória, usa efetivamente algum dos dataSources do mapa:

O caso de exemplo pode ser usado para determinar o banco da dados em função do cliente.

Nota: Esse tipo de arquitetura de software é conhecida como multitenancy, permitindo que distintos clientes tenham uma mesma “versão” da aplicação, usando a mesma infra, mas isolando os dados. Uma das estratégias desta arquitetura é separar os dados de clientes em bancos diferentes (tem outra de separar por schemas, e outra de adicionar uma coluna em cada tabela, dependendo do nível de isolamento necessário/desejado). Tem um repositório no meu github implementando isso

Cada requisição informa o client correspondente que é settado no ThreadLocal. Quando precisa ser tomada a decisão, o valor é recuperado. Vejam a implementação do TenantDataSource:

Isso será a base de nossa solução.

Usando o contexto transacional

Um tempo atrás escrevi um artigo sobre transações usando a stack padrão: Java, Spring Boot/Data, JPA e Hibernate. Pode dar uma olhada aqui.

Se você já conhece como usar transações em Spring, vai lembrar que só precisa anotar o método com Transactional, tendo a opção de explicitar que a transação é readOnly. Parece uma boa ideia usar esse valor readOnly para usar uma transação ou outra. O único “problema” é ter a disciplina de marcar os métodos como readOnly ou até Transactional, mas isso já é outra história :D

Consideramos duas opções para descobrir esse valor:

1- Lembrando do primeiro exemplo do multitenancy, poderíamos usar o ThreadLocal para marcar se aquele contexto é transacional ou não. Para fazer isso, podemos usar o AOP.

2- Usando o método estático isCurrentTransactionReadOnly da classe TransactionSynchronizationManager do próprio Spring

As duas implementações estão no meu github. Podem fazer clone do repositório:

> git clone https://github.com/iundarigun/transaction-routing

Aparentemente a segunda implementação parece mais simples, não é? Sempre é meio estranho ter que socar magia no meio do código usando AOP. Só que ao implementar encontrei um problema: O método devolve sempre false, como se não estivesse funcionando. Por fazer funcionar, precisei duas coisas:
- Marcar o hikariConfig como isAutoCommit = false
- Marcar o HibernateJpaDialect com prepareContext = false

Isso já me fez torcer o nariz. Mas beleza, vamos embora. Só que ao testar fazendo um insert em uma transação readonly=true percebi que não estava respeitando o readonly e estava fazendo o insert mesmo assim. Tive que fazer mais uma coisa:
- Marcar o hikariConfig como readonly para o datasource de leitura.

O código completo está no pacote br.com.devcave.jpa.configuration.mapping:

Solução com AOP

Pela complexidade da solução, eu achei mais interessante a opção de AOP, por ser menos invasiva. No final das contas, o Spring usa o tempo inteiro e não achamos ruim :D
Basicamente, criamos os datasources e via AOP settamos o valor do tipo de transação.

O código está no pacote br.com.devcave.jpa.configuration.aop:

E ai? Qual solução achou melhor? Comenta ai!

Referências

https://vladmihalcea.com/read-write-read-only-transaction-routing-spring/
https://fable.sh/blog/splitting-read-and-write-operations-in-spring-boot/

--

--

iundarigun
Dev Cave

Java and Kotlin software engineer at Clearpay