Spring Boot&ELK 1 – ElasticSearch

Merhabalar.

Bu yazıda Spring Boot ve Elasticsearch’ün birlikte nasıl kullanılacağına değineceğiz.

ElasticSearch Nedir?

Öncelikle ElasticSearch’ün ne olduğuna bir değinelim.

ElasticSearch full text arama imkanı sunan bir NoSQL veritabanıdır. Dağınık ve anlamsız halde duran veri kümelerinden anlamlı veriler elde edebilmek için kullanılır. ElasticSearch sorgulamalarını indexler üzerinden gerçekleştirir ve bu da bir performans getirisi sağlar. Biz ElasticSearch’e bir veri kaydettiğimizde ElasticSeach o verinin içinde geçen kelimeleri index yapısı ile kaydeder. Böylece biz o kelimeyi arattığımızda handi dokümanlarda geçtiğini indexlerden bildiği için hızlıca bulabiliyor.

Spring Boot & ElasticSearch

Şimdi ElasticSearch ve Spring Boot’un birlikte nasıl kullanıldığına bakalım.

Ben ElasticSearch’ü Docker üzerinden çalıştırıyorum ve içerisindeki verileri görüntülemek için de ElasticVue kullanıyorum. ElastichSearc ve ElasticVue’nun kurulum ve kullanımına ayrı bir yazı ile değineceğiz, bu nedenle bu yazıda bu kısmı şimdilik geçiyoruz.

Bu yazıda geçen örneklerin yer aldığı projenin kodlarına https://github.com/ilkgunel/SpringBootELK linkinden erişebilirsiniz.

Şimdi kodları incelemeye başlayalım.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.ilkaygunel</groupId>
    <artifactId>SpringBootELK</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>SpringBoot MongoDB Logstash Demo Project</name>
    <url>http://maven.apache.org</url>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.10</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-elasticsearch</artifactId>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.28</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>jakarta.json</groupId>
            <artifactId>jakarta.json-api</artifactId>
            <version>2.0.1</version>
        </dependency>

    </dependencies>
    <build>
        <finalName>SpringBootELK</finalName>
    </build>
</project>

Bu uygulama için

  • 2.7.10 versiyon numaralı Spring-Boo-Starter-Parent üst bağımlılığını kullanacağız. Spring Boot’dan getireceğimiz bağımlılıklar 2.7.10 versiyonundan gelecek.
  • Bir Rest API uygulaması geliştireceğimiz için Spring Boot Starter Web bağımlılığını dahil ettik.
  • ElasticSearch kullanacağımız için Spring-Data-ElasticSearch bağımılılığını dahil ettik.
  • Getter-Setter ve ilave bazı kullanımlar için 1.18.28 versiyonu ile Lombok kütüphanesini dahil ettik.
  • Son olarak da ElastichSearch’ün JSON dönüşümlerinde ihtiyaç duyduğu Jakarta.json-api kütüphanesini 2.0.1 versiyonu ile dahil ettik.
package com.ilkaygunel.application;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;

@SpringBootApplication
@ComponentScan(basePackages = {"com.ilkaygunel.*"})
@EnableElasticsearchRepositories(basePackages = "com.ilkaygunel.*")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }
}

Application.java sınıfımız bizim Spring Boot uygulamamızı başlatıcı sınıf. Bu sınıfı

  • Spring Boot uygulamasını başlatıcı sınıf olduğunda @SpringBootApplication notasyonu ile işaretledik.
  • Uygulama ayağa kalkarken com.ilkaygunel altındaki tüm paketleri ve sınıfları taraması için @ComponentScan notasyonu ile işaretledik.
  • ElastichSearch’ü Spring Boot ile birlikte kullanacağımız için veri operasyonlarını yürüteceğimiz repo interface’lerini taraması için @EnableElasticSearchRepositories notasyonu ile işaretledik.
package com.ilkaygunel.configuration;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration;

@Configuration
public class ElasticsearchConfig extends ElasticsearchConfiguration {

    @Bean
    public ClientConfiguration clientConfiguration() {
        return ClientConfiguration.builder() //
                .connectedTo("localhost:9200") //
                .build();
    }

}

ElasticsearchConfig bizim Spring Boot uygulamamızın ElastichSearch’e bağlantı kurması için kullanacağımız sınıfımız. Bu sınıf

  • Bir konfigürasyon ayarlaması yaptığından @Configuration notasyonu ile işaretli.
  • Bağlantı ayarlaması yapacağımızdan ElasticsearchConfiguration sınıfından kalıttık.
  • @Bean notasyonu ile işaretli clientConfiguration metodu uygulamamız ayağa kalkarken connectedTo metoduna verdiğimiz parametredeki adrese bağlanacak.
package com.ilkaygunel.documents;

import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.lang.Nullable;

import java.io.Serializable;

@Data
@Document(indexName = "driver_index")
public class Driver implements Serializable {

    @Id
    @Nullable
    private String id;

    @Field(type = FieldType.Text, name = "name")
    private String name;

    @Field(type = FieldType.Text, name = "surname")
    private String surname;

    public Driver() {

    }

    public Driver(String name, String surname) {
        this.name = name;
        this.surname = surname;
    }

    @Override
    public String toString() {
        return String.format(
                "Driver [id=%s, firstName='%s', lastName='%s']",
                id, name, surname);
    }
}

Driver.java sınıfımız bizim ElastichSeach’de tutacağımız Driver dokümanlarına karşılık gelen entity sınıfımızdır. Driver entity’imiz id, name ve surname alanlarından ibaret olacak. Bu sınıf içerisinde önemli olan nokta entity’nin @Document notasyonu ile işaretli olması. Biz bu @Document notasyonu içine indexName = “driver_index” parametresi geçtik. Böylece driver dokümanına kaydedilen her veri aynı zamanda driver_index’inde de kelime bazlı indexlenecek.

package com.ilkaygunel.repository;

import com.ilkaygunel.documents.Driver;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface DriverRepository extends ElasticsearchRepository<Driver, String> {

    public Driver findByName(String name);

}

DriverRepository interface’imiz bizim için uygulamamızı ElastichSearch ile konuşturacak ve veri alışverişini gerçekleştirecek repository’dir. Bu repository

  • Klasik bir şekilde @Repository notasyonu ile işaretli. RDMS kullanırken de Repository sınıflarımız @Repository sınıfı ile işaretleniyor.
  • DriverRepository sınıfımızı ElasticsearchRepository interface’inden kalıtıyoruz. ElasticsearchRepository ElasticSearch üzerinde veri arama, getirme, ekleme işlemlerinin nasıl yapılacağını biliyor ve bizim yerimize connection açma kapama vs işlerini yapacak.
  • Klasik RDMS’de olduğu gibi burada da findBy…’lı metotlarımızı yazabiliyoruz.
package com.ilkaygunel.service;

import com.ilkaygunel.documents.Driver;
import com.ilkaygunel.repository.DriverRepository;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class DriverService {

    private final DriverRepository driverRepository;

    public Driver getDriverByName(String driverName) {
        return driverRepository.findByName(driverName);
    }

    public Driver getDriverById(String id) {
        return driverRepository.findById(id).orElseThrow(() -> new RuntimeException("There is no driver with this Id".concat(id)));
    }

    public DriverService(DriverRepository driverRepository) {
        this.driverRepository = driverRepository;
    }

    public void saveSingleDriver(Driver driver) {
        driver = driverRepository.save(driver);
    }

    public void saveBulkDriver(List<Driver> drivers) {
        driverRepository.saveAll(drivers);
    }

    public void updateSingleDriver(String driverId, Driver driverToUpdate) {
        Driver existingDriver = getDriverById(driverId);
        existingDriver.setName(driverToUpdate.getName());
        existingDriver.setSurname(driverToUpdate.getSurname());
        driverRepository.save(existingDriver);
    }

    public void deleteSingleDriver(String id) {
        driverRepository.deleteById(id);
    }

    public void deleteAllDrivers() {
        driverRepository.deleteAll();
    }

}

DriverService sınıfımız bizim klasik Spring servis sınıfımız. Bu sınıf içerisinde repository’i inject edip veri ekleme, silme vs. işlemlerimizi gerçekleştiriyoruz.

package com.ilkaygunel.api;

import com.ilkaygunel.documents.Driver;
import com.ilkaygunel.service.DriverService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
public class DriverAPI {

    private final DriverService driverService;

    public DriverAPI(DriverService driverService) {
        this.driverService = driverService;
    }

    @GetMapping(value = "/driver/{driverName}")
    @ResponseBody
    public Driver getDriverByName(@PathVariable(value = "driverName") final String driverName) {
        return driverService.getDriverByName(driverName);
    }

    @PostMapping(value = "/driver")
    public ResponseEntity<String> saveSingleDriver(@RequestBody Driver driver) {
        driverService.saveSingleDriver(driver);
        return ResponseEntity.status(HttpStatus.CREATED).body("Successfully Inserted!");
    }

    @PostMapping(value = "/drivers")
    public ResponseEntity<String> saveBulkDriver(@RequestBody List<Driver> drivers) {
        driverService.saveBulkDriver(drivers);
        return ResponseEntity.status(HttpStatus.CREATED).body("Successfully Inserted!");
    }

    @PutMapping(value = "/driver/{driverId}")
    public ResponseEntity<String> updateSingleDriver(@RequestBody Driver driver, @PathVariable(value = "driverId") final String driverId) {
        driverService.updateSingleDriver(driverId, driver);
        return ResponseEntity.status(HttpStatus.OK).body("Successfully Updated!");
    }

    @DeleteMapping(value = "/driver/{driverId}")
    public ResponseEntity<String> deleteSingleDriver(@PathVariable(value = "driverId") final String driverId) {
        driverService.deleteSingleDriver(driverId);
        return ResponseEntity.status(HttpStatus.OK).body("The driver with this id deleted:".concat(driverId));
    }

    @DeleteMapping(value = "/drivers")
    public ResponseEntity<String> deleteAllDrivers() {
        driverService.deleteAllDrivers();
        return ResponseEntity.status(HttpStatus.OK).body("All Drivers Deleted!");
    }

}

DriverAPI sınıfımız da bizim API isteklerimizi karşılayacak sınıfımız.

  • /driver/{driverName} path’ine gelen istekler ile path’de yer alan isimdeki driver sorgulanıp dönülüyor.
  • /driver path’ine HTTP POST ile gelen istekler ile request’te gelen Driver’lar kaydedilecek.
  • /drivers path’ine HTTP POST ile gelen istekler ile requestte gelen Driver listesi kaydedilecek.
  • /driver/{driverId} paht’ine HTTP PUT ile gelen istekler ile path’de yer alan driverId bilgisi ile drvier bulunup request’te gönderilen driver bilgileri ile güncelleniyor.
  • /driver/{driverId} path’ine HTTP DELETE ile gelen istekler ile path’de yer alan driverId bilgisine sahip driver siliniyor.

DEMO

Şimdi veri kaydetme ve getirme üzerinden demo’muzu yapalım.

Şu anda ElasticSearch’de Rubens Barichello ismindeki bir driver var.

Şimdi body’si şu şekilde olan bir isteği http://localhost:8181/driver adresine gönderiyorum.

{
    "name":"Michael",
    "surname":"Schumacher"
}

Dönen cevapta başarılı bir şekilde kayıt edildiği söyleniyor.

ElasticVue üzerinden ElasticSearch’ü kontrol ettiğimde ise verinin kaydedildiğini görüyorum.

Şimdi de http://localhost:8181/driver/Michael adresine bir GET isteği gönderiyorum ve az önce eklediğim kaydın geldiğini görüyorum.

Bu yazıda anlatacaklarım bu kadar. Bu yazı ile Spring Boot ve Elasticsearch’ün birlikte nasıl kullanıldığını öğrenmiş olduk. Başka bir yazıda görüşene kadar hoşçakalın.

Latest posts by İlkay Günel (see all)

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir