Spring R2DBC ile Reactive Relational Database — Reactive Programlama

Gökhan Ayrancıoğlu
Delivery Hero Tech Hub
4 min readJan 21, 2022

R2DBC, ilişkisel veritabanları için reactive ve non-blocking API’lar sağlayan bir yapıdır. R2DBC (Reactive Relational Database Connectivity) kullanarak geliştirdiğimiz reactive uygulamada reactive/asenkron bir şekilde ilişkisel veritabanından bilgi okuma ve yazma işlemlerini gerçekleştirmemize aracılık eder. R2DBC desteği olmayan relational database’ler ile reactive programlama desteklenemez.

Daha önceki yazılarımda teoriden başlayarak, reactive endpoint ve reactive web client konularını işledik. Bunları okumanızı, reactive programlama ile ilgili temel kavramlara öğrenmeniz ve bu yazıyı daha kolay anlayabilmeniz için öneriyorum. Bu yazıyı anlamak için de temel database bilgisine ihtiyaç duyulmaktadır.

Bir uygulamada özellikle verilerle uğraşılıyorsa database ihtiyacı olduğu aşikar, reactive programlama da bir uygulamanın reactive olabilmesi için database bağlantısının ve driver yapısının da diğer tüm bileşenleri gibi reactive olması gerekmektedir.

Spring dünyasında reaktifliği relational database boyutunda sağlamak için R2DBC yapısı kullanılmaktadır. Şu anda Oracle, Microsoft SQL Server, MySQL, PostgreSQL, H2, MariaDB ve Google Cloud Spanner için driver desteği mevcuttur. Biz bugün oldukça popüler olarak kullanılan PostgreSQL üzerinden örneklerimizi işleyeceğiz, ancak tüm yapı R2DBC desteği bulunan databaseler için geçerlidir.

Spring R2DBC, JPA gibi tam bir ORM olarak nitelendirilmeyebilir.— caching ve lazy loading gibi özellikler sunmaz. Ancak object mapping özelliği ve abstraction sağlar. Daha anlaşılır açıklayacak olursak, reactive programlama ile birlikte; tablolarımızı entity olarak oluşturabilir ve sorgularımızı veritabanına yöneltebiliriz. Neleri yapamıyoruza gelince OneToMany gibi ilişkileri direkt olarak sınıflarımız üzerinden yapamıyoruz. Yani OneToMany gibi ilişkisel anotasyonlar desteklenmediği için bu işlemleri gerçekleştirirken direkt olarak ham SQL sorgular yazmamız ya da tablolar arasındaki bağlantıları kendimiz el yordamıyla kod üzerinden gerçekleştirmemiz gerekiyor. Yani R2DBC ile çalışırken SQL bilgimizin iyi seviyede olmasa gerekiyor.

Spring R2DBC ile Uygulama Geliştirme

R2DBC ileilgili bağımlılıkları yönetmek için bir spring-boot-starter-data-r2dbc mevcuttur. PostgreSQL veritabanını kullanarak bir uygulama geliştireceğimiz için onun bağımlılıklarını da eklememiz gerekiyor. Tüm kodlara Github üzerinden ulaşabilirsiniz.

implementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-r2dbc'
implementation group: ‘io.r2dbc’, name: ‘r2dbc-postgresql’
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-webflux'

Projemizde PostgreSQL tercih ettiğimiz için ona uygun şekilde configrasyonlarımızı gerçekleştirebiliriz. Configurasyon dosyamızı AbstractR2dbcConfiguration ‘den extendsederek ConnectionFactory’imize bütün bağlantı ayarlarını girebiliriz.

@Configuration
@EnableR2dbcRepositories
public class DatabaseConfig extends AbstractR2dbcConfiguration {

@Override
@Bean
public ConnectionFactory connectionFactory() {
return new PostgresqlConnectionFactory(
PostgresqlConnectionConfiguration.builder()
.host("localhost")
.port(5432)
.username("postgres")
.password("postgres")
.database("mydatabase")
.build());
}
}

Şimdi de hızlıca tablolarımızı üretelim daha sonra bütünüyle kod sürecinde yaptıklarımızı ve neden yaptığımızı ele alabiliriz.

CREATE TABLE reactive_user(id serial PRIMARY KEY, name VARCHAR(50) NOT NULL,score VARCHAR(50) NOT NULL);

Şimdi oluşturulan tablonun entity karşılığı olan bir sınıf oluşturabiliriz.

@Table("reactive_user")
public class User {
@Id
private Integer id;
private String name;
private Integer score;
// Getters, Setters ...
  • Bir entity sınıfının id’si, Spring Data’nın @Id anotasyonu ile primary key olarak tanımlanır. Bilmeyenler için açıklayalım: bu tanımda id değeri NULL ise, Spring nesneyi kaydederken tabloda yeni bir kayıt oluşturacaktır. id NULL değilse güncelleme işlemi yapmaya çalışır.
  • JPA’daki gibi @GeneratedValue anotasyonu yoktur. Dolayısıyla primary keyotomatik olarak artırılması Spring tarafından yapılandırılmaz. Bu yapılandırmayı tmanuel olarak veritabanımızı oluştururken gerçekleştirmeliyiz.
  • @Table ile birlikte bunun bir tablo olduğunu belirtebiliyoruz. Aynı zamanda içine değer vererek tablonun ismini belirtebiliyoruz.

Artık tablomuza karşılık gelen entity’imiz hazır diyebiliriz. Şimdi database ile iletişim kurabilecek ve Query’lerimizi yazabileceğimiz bir Repository oluşturmaya geldi sıra. Burada yapacağımız ilk hareket reactive bir repository oluşturmak için ReactiveCrudRepository kullanarak ondan extends edilmiş bir UserRespoistory interface’i oluşturmaktır. Bu Repository’yi kullanarak hali hazırdaki metotlarını (findAll, findById, save and delete gibi) kullanabilir ya da örnekteki gibi yeni Query’ler yazabiliriz.

public interface UserRepository extends ReactiveCrudRepository<User, Long> {
@Query("select id,name,score from reactive_user where name=$1")
Flux<User> findByName(String name);
Mono<User> findById(int id);
Mono<Void> deleteById(int id);
}

Ek olarak, daha spesifik ya da complex query’ler atabilmek için @Query anotasyonunu kullanabilir ve böylece ham SQL sorguları yazabiliriz.

Servisimizi de hazırlayıp önceden yazdığımız reactive endpoint’lere kendi veritabanımız ile iletişim kurabilen repository’lerimizi bağlayarak gerçek bir servis haline getirelim. Burada servisimize repository’imizi inject etmemiz gerekiyor.

@Service
public class UserServiceImpl implements UserService {

private final UserRepository userRepository;

public UserServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}

@Override
public Mono<User> getUserById(int id) {
return userRepository.findById(id);
}

@Override
public Flux<User> getUsers() {
return userRepository.findAll();
}

@Override
public Mono<User> saveUser(User userDTO) {
return userRepository.save(userDTO);
}

@Override
public Mono<User> updateUser(int id, User userDTO) {
return userRepository.findById(id).flatMap(user -> {
userDTO.setId(user.getId()); // update the id
return userRepository.save(userDTO);
});
}

@Override
public Mono<Void> deleteUser(int id) {
return userRepository.deleteById(id);
}
}

R2DBC ile JPA uygulamalarına kıyasla uygulamanın ORM katmanında framework yerine geliştiriciler olarak bizim yapmamız gereken çok daha fazla manuel işlem vardır. Spring bu konuda geliştirmeleri gerçekleştiriyor ve çok uzun zaman almadan reactive veritabanları konusunda çok daha belirgin özelliklerle birlikte hazır olacaklarını düşünüyorum. Reactive programlama gelecekte trend olacak konuların başında geliyor, çok da geç olmadan en azından konsepti anlamının büyük faydası var.

Tüm kodlara Github reposundan ulaşabilirsiniz.

— Bana ulaşmak için: Twitter, Linkedin

— Kodlar konuşsun: Github

— 1:1 görüşmeler için: Superpeer

https://gokhana.dev

--

--

Gökhan Ayrancıoğlu
Delivery Hero Tech Hub

Software Engineer @Yemeksepeti • #Java • #Spring Boot • #Kotlin • #Spark • #Microservices • https://gokhana.dev