Spring Fundamentals — DataSource Routing

Emrullah YILDIRIM
2 min readJan 31, 2021

--

Çoğunlukla transaction’lar, yürütülen sorgunun anlamsallığına bağlı olarak birden çok datasource örneği arasında dağıtılması arzu edilir. En basit ve en tipik senaryoda, yoğun SELECT sorgularını replikalara aktararak, gerçek datasource’da (INSERT/UPDATE) işlemlerini sorunsuz şekilde gerçekleştirmek isteriz.

Routing, teknik nedenlerle (örneğin load balancing) veya daha genel olarak işlevsel nedenlerle (filtering, aggregation, enriching) yararlı olabilir. Ancak bir transaction için yalnızca bir datasource ile işleme devam edilebilir. Spring, datasource yönlendirmesini dinamik olarak genişletip gerçekleştirebileceğimiz ve Spring Boot üzerinde kolayca yapılandırabileceğimiz AbstractRoutingDataSource sınıfını sağlar.

İmplementasyon aşamasına geçmeden örnek proje kodlarına buradan ulaşabilirsiniz.

Router Implementation

UML Diagram

Yukarıdaki gibi datasource kaynaklarımızı temsil eden bir enum sınıfı ve bu kaynakları kullandığımız thread’in, thread local’inde, thread safe bir şekilde tutmak için bir util sınıfı oluşturuyoruz.

AbstractRoutingDataSource sınıfını extend ettiğimizde over-ride edilen determineCurrentLookupKey methodu, TransactionsManager her yeni bir connection isteği oluşturduğunda çalışır. Her bir transaction ayrı bir thread ile ilişkilidir. Bu nedenle, farklı transactionların farklı datasource’lara gönderilmesi için, her thread’in hangi datasource kaynağını kullanması gerektiğini güvenilir bir şekilde tanımlayabildiğinden emin olmalıyız. Bu sebepten ötürü yukarıdaki util sınıfımızda ThreadLocal kullanıyoruz.

ThreadLocal hakkında daha detaylı bir bilgi almak isterseniz şu yazıyı okumanızı tavsiye ederim.

Daha sonra datasource için bean’lerimizi tanımlayalım.

Artık hangi datasource’un kullanılacağını kontrol edebilir ve istekleri istediğimiz gibi iletebiliriz. İyi görünüyor!

Ama birşeyler eksik gibi, aslında herşey DatabaseContextHolder’ın büyülü bir şekilde static cağrılarına dayanıyor.

Yazdığımız kodu incelediğimiz göze şu detaylar çarpıyor;

  • Business Logice ait olmayan bir kod
  • DatabaseContextHolder temizlenmesi
  • BusinessLogic esnasında bir exception durumu

İşte yukarıdaki sebeplerden ötürü aslında çokta iyi bir best practice uygulamış olmadık. Gelin şimdi kodumuzu refactor edelim.

Spring’in temel taşı olan aspect imdatımıza bu noktada yetişiyor. Anlamlı, küçük bir anatasyon tanımlayalım.

Bu yazdığımız anatasyon ve pointcut ile tanımlanmış her method replika datasource üzerinde işlemlerine devam edecektir. Business logic exception fırlatsa bile bu istisna güvenli bir şekilde contextimizi temizleyecektir.

Açıklığa kavuşturulacak son bir konu kaldı. @Value (“20”). Burada, ReadOnlyConnection anatasyonunun @Transactional anatasyonu başlamadan önce çalıştığından emin olmamız gerekiyor. Aksi takdirde, @ReadOnlyConnection çalışmaya başladığında connection zaten iş parçacığına atanmış olacaktır. Bu nedenle, anastasyon altındaki sırayı ayarlamamız gerekir.

Transactionları neye göre ayırmalı?

Outer-method üzerinde @Transactional anatasyonu varsa, ınner-method doğrudan transaction üzerinde datasource değiştiremez ve yanlış bir datasource üzerine yönlenmiş olursunuz. Bu durumda outer-methoda @Transactional (propagation = Propagation.REQUIRES_NEW) ekleyebilirsiniz. Fakat bu seferde outer-methodunuz rollback yapıldığında inner-method daki değişiklikler geri alınamaz.

Sonuç olarak business logic, gereksiz kavramlarla işgal edilmedi ve anatasyon ile bu methodun sadece read only veri kaynağımızı kullanmak için tasarlandığını belirttik.

--

--

Emrullah YILDIRIM

Kendine Blogger. Yazarak öğrenen, yazdıklarini paylaşan Java Software Engineer.