Yazılım Mimarileri ve Tasarım Desenleri Üzerine

Elasticsearch Nasıl Kullanılır? .NET Uygulamaları İçin Güzel Bir Servis Geliştirelim

Merhaba,

Bu içeriğimizde Elasticsearch’ün temel düzeyde kullanım mantığını inceleyecek ve biryandan da .NET uygulamaları için tarafımızca geliştireceğimiz gayet kullanışlı, esnek ve özgün bir Elasticsearch servisi inşa edeceğiz. O halde fazla vakit kaybetmeksizin buyurun başlayalım…

Elasticsearch & Kibana Kurulumu (Dockerize)

Kibana Elasticsearch’ü kullanırken tercih edeceğimiz arayüz olacaktır. Nedir bu? diye sorarsanız Elasticsearch Nedir? Temel Kavramları Nelerdir? başlıklı makalemizde yaptığımızı özeti direkt alıntılamakta fayda görmekteyim;

Kibana, Elasticsearch ile etkileşimli görselleştirmeler ve veri analizi sağlayan bir kullanıcı arayüzüdür. Verileri görselleştirme, raporlama, anlık durum panoları oluşturma, etkileşimli analiz yapma vs. gibi yetenekleri mevcuttur.

Evet, şimdi ilk olarak Elasticsearch ile Kibana’nın kurulumundan başlayacağız. Ben kurulum açısından her iki uygulamayı da docker ortamında ayağa kaldıracağımdan dolayı aşağıdaki docker compose dosyasından istifade edeceğim. Tabi ki de sizler istediğiniz farklı şekillerde kurulum gerçekleştirebilir ve içeriğimize eşlik edebilirsiniz.

version: '3.8'
services:
  elasticsearch:
    image: elasticsearch:8.15.0
    environment:
      - discovery.type=single-node
      - bootstrap.memory_lock=true
      - xpack.security.enabled=false
    volumes:
      - es_data:/usr/share/elasticsearch/data
    ports:
      - "9200:9200"
  kibana:
    image: kibana:8.14.3
    environment:
      ELASTICSEARCH_HOSTS: http://elasticsearch:9200
    ports:
      - "5601:5601"

volumes:
  es_data:
    driver: local

Kibana’yı Tanıyalım

Kibana, yukarıdaki satırlardan da anlaşılacağı üzere verisel analizlerden tutun raporlamaya, veri görselleştirmeden anlık durum panoları oluşturmaya kadar birçok kompleks operasyonu yürütebilecek geniş yeteneğe sahip olan bir araçtır. Haliyle bu aracı tanımak bu içeriğin amacının oldukça dışında bir uğraş olacağından dolayı bizler direkt konuya odaklı bir şekilde hareket edecek ve Kibana’nın Elasticsearch veritabanı üzerinden işlem yapmamızı sağlayacak olan modülüne odaklanacağız. Bunun için tek yapılması gereken yukarıdaki docker compose dosyasını çalıştırdıktan sonra tarayıcı üzerinden localhost:5601 adresine gitmek ve soldaki menü üzerinden aşağıdaki görselde olduğu gibi Management kategorisinden Dev Tools menüsüne tıklamaktır.Bu menü Elasticsearch üzerinde CRUD işlemleri yapmamızı sağlayacak olan Console penceresine bizleri yönlendirecektir. Burada görüldüğü üzere veri okuma ve yazma işlemlerinde sanki bir http request’inde bulunuyormuş gibi isteğin mahiyetine uygun GET, POST, PUT ve DELETE sorguları çalıştırıyor olacağız.(keza özünde de REST API üzerinden verisel işlemler yürütülür, o yüzden http istekleriyle CRUD işlemleri gerçekleştirilir) Yapısal olarak oldukça basit olan bu sorguların yapı iskeleti her daim aşağıdaki görselde belirtilen şemaya dayanacaktır.Bu şemayı açıklamamız gerekirse eğer;

Velhasıl kelam, Kibana üzerinde bu şemaya uygun herhangi bir isteği Elasticsearch’e gönderebilmek için aşağıdaki görselde olduğu gibi yandaki ok ikonuna tıklanması yeterlidir.Sanırım Kibana’ya dair temel keşif bu kadar yeterlidir diye düşünüyorum 🙂 Artık Kibana üzerinden Elasticsearch’ün sorgulama mantığına odaklanabiliriz.

Elasticsearch Temel CRUD İşlemleri

Şimdi mümkün mertebe ihtiyaç duyulabilecek operasyonları tek tek başlıklandırarak nasıl uygulanabileceğini incelemeye başlayalım.

Evet, artık Kibana üzerinden Elasticsearch’ün kullanımını hasbel kader de olsa tecrübe etmiş bulunuyoruz. Artık bu aşamadan sonra .NET uygulamaları üzerinde Elasticsearch’ün nasıl kullanılabileceğine odaklanalım ve içeriğimizin ilk satırlarında bahsettiğimiz gibi bu işe odaklı oldukça kullanışlı özgün servisimizi inşa etmeye başlayalım.

NET Uygulamalarında Elasticsearch’ün Kullanımı

Her şeyden önce içerik seyrinde yapacağımız geliştirmeleri hızlıca test edebilmek için bir Asp.NET Core uygulaması oluşturalım ve Elasticsearch çalışmalarını gerçekleştirebilmek için ilk olarak bu projeye Elastic.Clients.Elasticsearch kütüphanesini kurarak başlayalım.

Ardından Elasticsearch sorgulaması sürecinde çalışacağımız veri formatını belirleyebilmek için aşağıdaki gibi bir entity sınfı tasarlayalım;

    public class Product : IElasticsearchModal
    {
        public Guid Id { get; set; } = Guid.NewGuid();
        public string Name { get; set; }
        public int Quantity { get; set; }
        public decimal Price { get; set; }
    }

Tabi uygulamada olası diğer veritabanı entity’lerinden Elasticsearch entity’lerini ayırt edebilmek için (bir nevi Marker Pattern mantığında) IElasticsearchModal adında bir interface oluşturalım ve yukarıdaki çalışmada yaptığımız üzere Elasticsearch entity’lerini bu interface’ten implemente ederek gerekli işaretlemeyi sağlayalım.

    public interface IElasticsearchModal
    {
        Guid Id { get; set; }
    }

Evet, bu temelden sonra artık Elasticsearch veritabanı işlemlerini gerçekleştirecek olan servisimize odaklanmaya başlayabiliriz. Bunun için oluşturacağımız ‘IElasticsearchService’ isimli interface bize rehber olacaktır ve bu interface’e tanımlayacağımız imzalar bahsi konu olan servisin yeteneklerini özetliyor olacaktır. Şimdi gelin interface’i oluşturalım ve içeriğini aşağıdaki gibi dolduralım.

    public interface IElasticsearchService
    {
        Task<bool> CreateIndexAsync(string indexName = ElasticsearchIndexes.DefaultIndex, CancellationToken cancellationToken = default);

        Task<bool> CreateDocumentAsync<T>(T document, string indexName = ElasticsearchIndexes.DefaultIndex, CancellationToken cancellationToken = default) where T : IElasticsearchModal;

        Task<bool> UpdateDocumentAsync<T>(string documentId, object partialDocument, string indexName = ElasticsearchIndexes.DefaultIndex, CancellationToken cancellationToken = default);

        Task<bool> DeleteDocumentAsync<T>(string documentId, string indexName = ElasticsearchIndexes.DefaultIndex, CancellationToken cancellationToken = default);

        Task<long> CountDocumentsAsync<T>(string indexName = ElasticsearchIndexes.DefaultIndex, CancellationToken cancellationToken = default) where T : IElasticsearchModal;

        Task<IReadOnlyCollection<T>> GetDocumentsAsync<T>(string indexName = ElasticsearchIndexes.DefaultIndex, CancellationToken cancellationToken = default) where T : IElasticsearchModal;

        Task<T> GetDocumentAsync<T>(string documentId, string indexName = ElasticsearchIndexes.DefaultIndex, CancellationToken cancellationToken = default) where T : IElasticsearchModal;

        Task<IReadOnlyCollection<T>> SearchAsync<T>(Action<SearchRequestDescriptor<T>> searchRequestDescriptor, CancellationToken cancellationToken = default) where T : IElasticsearchModal;

        Task<IReadOnlyCollection<T>> MatchQueryAsync<T>(Expression<Func<T, object>> field, string queryKeyword, string indexName = ElasticsearchIndexes.DefaultIndex, CancellationToken cancellationToken = default, int from = 0, int size = 10) where T : IElasticsearchModal;

        Task<IReadOnlyCollection<T>> FuzzyQueryAsync<T>(Expression<Func<T, object>> field, string queryKeyword, string indexName = ElasticsearchIndexes.DefaultIndex, CancellationToken cancellationToken = default, int from = 0, int size = 10) where T : IElasticsearchModal;

        Task<IReadOnlyCollection<T>> WildcardQueryAsync<T>(Expression<Func<T, object>> field, string queryKeyword, string indexName = ElasticsearchIndexes.DefaultIndex, CancellationToken cancellationToken = default, int from = 0, int size = 10) where T : IElasticsearchModal;

        Task<IReadOnlyCollection<T>> BoolQueryAsync<T>(Expression<Func<T, object>> matchField, string matchQueryKeyword, Expression<Func<T, object>> fuzzyField, string fuzzyQueryKeyword, Expression<Func<T, object>> wildcardField, string wildcardQueryKeyword, string indexName = ElasticsearchIndexes.DefaultIndex, CancellationToken cancellationToken = default, int from = 0, int size = 10) where T : IElasticsearchModal;

        Task<IReadOnlyCollection<T>> TermQueryAsync<T>(Expression<Func<T, object>> field, string queryKeyword, string indexName = ElasticsearchIndexes.DefaultIndex, CancellationToken cancellationToken = default, int from = 0, int size = 10) where T : IElasticsearchModal;

        Task<IReadOnlyCollection<T>> ExistsQueryAsync<T>(Expression<Func<T, object>> field, string indexName = ElasticsearchIndexes.DefaultIndex, CancellationToken cancellationToken = default, int from = 0, int size = 10) where T : IElasticsearchModal;
    }

Görüldüğü üzere Elasticsearch üzerinde index oluşturmaktan tutun, document sayısını verene ya da match, fuzzy, wildcard vs. gibi sorguları gerçekleştirene kadar birçok sorumluluğu tarif eden bir arayüz tanımlamış bulunuyoruz. Tabi burada daha fazla yetenek ekleme şansımız olsa da ben içeriğimiz için bu kadarını kâfi görmekte ve bu vaziyette yola devam etmekteyim.

Şimdi burada odaklanmanızı istediğim nokta şudur ki, yukarıda tanımlanmış olan her bir imza esasında Elasticsearch’ün bizlere sunduğu servise bir abstraction sağlamakta ve bu servisi kullanacak developer açısından oldukça kolaylaştırıcı bir arayüz sunmaktadır. Şöyle ki… Şimdi bizler bu interface’i aşağıdaki sınıfta kullandığımızı varsayarsak;

    public class ElasticsearchService : IElasticsearchService
    {
        readonly ElasticsearchClient _elasticsearchClient;
        public ElasticsearchService(IConfiguration configuration)
        {
            ElasticsearchClientSettings elasticsearchClientSettings = new(new Uri(configuration["ElasticsearchSettings:Uri"]));
            elasticsearchClientSettings.DefaultIndex(ElasticsearchIndexes.DefaultIndex);
            _elasticsearchClient = new(elasticsearchClientSettings);
        }

        .
        .
        .
    }

Görüldüğü gibi öncelikle Elasticsearch client’a bir bağlantı oluşturmamız gerekecektir. Bunu da Elastic.Clients.Elasticsearch kütüphanesinin bizlere sağlamış olduğu sınıflarla yukarıdaki gibi rahatlıkla gerçekleştirebilmekteyiz. Şimdi, oluşturduğumuz IElasticsearchService interface’inin implementasyonuna odaklanırsak eğer aşağıdaki gibi bir çalışma yapmamız gerekecektir.

    public class ElasticsearchService : IElasticsearchService
    {
        readonly ElasticsearchClient _elasticsearchClient;
        public ElasticsearchService(IConfiguration configuration)
        {
            ElasticsearchClientSettings elasticsearchClientSettings = new(new Uri(configuration["ElasticsearchSettings:Uri"]));
            elasticsearchClientSettings.DefaultIndex(ElasticsearchIndexes.DefaultIndex);
            _elasticsearchClient = new(elasticsearchClientSettings);
        }

        public async Task<bool> CreateIndexAsync(string indexName = ElasticsearchIndexes.DefaultIndex, CancellationToken cancellationToken = default)
        {
            IndexResponse indexResponse = await _elasticsearchClient.IndexAsync(indexName, cancellationToken);

            return indexResponse.IsSuccess();
        }

        public async Task<bool> CreateDocumentAsync<T>(T document, string indexName = ElasticsearchIndexes.DefaultIndex, CancellationToken cancellationToken = default) where T : IElasticsearchModal
        {
            CreateRequest<T> createRequest = new(document, indexName, document.Id);
            CreateResponse createResponse = await _elasticsearchClient.CreateAsync(createRequest, cancellationToken);

            return createResponse.IsSuccess();
        }

        public async Task<bool> UpdateDocumentAsync<T>(string documentId, object partialDocument, string indexName = ElasticsearchIndexes.DefaultIndex, CancellationToken cancellationToken = default)
        {
            UpdateRequest<T, object> updateRequest = new(indexName, documentId)
            {
                Doc = partialDocument
            };
            UpdateResponse<T> updateResponse = await _elasticsearchClient.UpdateAsync<T, object>(updateRequest, cancellationToken);

            return updateResponse.IsSuccess();
        }

        public async Task<bool> DeleteDocumentAsync<T>(string documentId, string indexName = ElasticsearchIndexes.DefaultIndex, CancellationToken cancellationToken = default)
        {
            DeleteResponse deleteResponse = await _elasticsearchClient.DeleteAsync<T>(indexName, documentId, cancellationToken);

            return deleteResponse.IsSuccess();
        }

        public async Task<long> CountDocumentsAsync<T>(string indexName = ElasticsearchIndexes.DefaultIndex, CancellationToken cancellationToken = default) where T : IElasticsearchModal
        {
            CountResponse countResponse = await _elasticsearchClient.CountAsync<T>(indexName, cancellationToken);

            return countResponse.Count;
        }

        public async Task<IReadOnlyCollection<T>> SearchAsync<T>(Action<SearchRequestDescriptor<T>> searchRequestDescriptor, CancellationToken cancellationToken = default) where T : IElasticsearchModal
        {
            SearchResponse<T> searchResponse = await _elasticsearchClient.SearchAsync<T>(searchRequestDescriptor, cancellationToken);

            return searchResponse.Documents;
        }

        public async Task<IReadOnlyCollection<T>> GetDocumentsAsync<T>(string indexName = ElasticsearchIndexes.DefaultIndex, CancellationToken cancellationToken = default) where T : IElasticsearchModal
        {
            SearchResponse<T> searchResponse = await _elasticsearchClient.SearchAsync<T>(indexName, cancellationToken);

            return searchResponse.Documents;
        }

        public async Task<T> GetDocumentAsync<T>(string documentId, string indexName = ElasticsearchIndexes.DefaultIndex, CancellationToken cancellationToken = default) where T : IElasticsearchModal
        {
            GetResponse<T> getResponse = await _elasticsearchClient.GetAsync<T>(documentId, index => index
                                                                                                        .Index(indexName), cancellationToken);

            return getResponse.Source;
        }

        public async Task<IReadOnlyCollection<T>> MatchQueryAsync<T>(Expression<Func<T, object>> field, string queryKeyword, string indexName = ElasticsearchIndexes.DefaultIndex, CancellationToken cancellationToken = default, int from = 0, int size = 10) where T : IElasticsearchModal
        {
            SearchResponse<T> searchResponse = await _elasticsearchClient.SearchAsync<T>(index => index
                                                                                                    .Index(indexName)
                                                                                                    .Query(query => query
                                                                                                        .Match(t => t.Field(field)
                                                                                                                     .Query(queryKeyword)))
                                                                                                    .From(from)
                                                                                                    .Size(size), cancellationToken);

            return searchResponse.Documents;
        }

        public async Task<IReadOnlyCollection<T>> FuzzyQueryAsync<T>(Expression<Func<T, object>> field, string queryKeyword, string indexName = ElasticsearchIndexes.DefaultIndex, CancellationToken cancellationToken = default, int from = 0, int size = 10) where T : IElasticsearchModal
        {
            SearchResponse<T> searchResponse = await _elasticsearchClient.SearchAsync<T>(index => index
                                                                                                    .Index(indexName)
                                                                                                    .Query(query => query
                                                                                                        .Fuzzy(t => t.Field(field)
                                                                                                                     .Value(queryKeyword)))
                                                                                                    .From(from)
                                                                                                    .Size(size), cancellationToken);

            return searchResponse.Documents;
        }

        public async Task<IReadOnlyCollection<T>> WildcardQueryAsync<T>(Expression<Func<T, object>> field, string queryKeyword, string indexName = ElasticsearchIndexes.DefaultIndex, CancellationToken cancellationToken = default, int from = 0, int size = 10) where T : IElasticsearchModal
        {
            SearchResponse<T> searchResponse = await _elasticsearchClient.SearchAsync<T>(index => index
                                                                                                    .Index(indexName)
                                                                                                    .Query(query => query
                                                                                                        .Wildcard(t => t.Field(field)
                                                                                                                        .Value(queryKeyword)))
                                                                                                    .From(from)
                                                                                                    .Size(size), cancellationToken);

            return searchResponse.Documents;
        }

        public async Task<IReadOnlyCollection<T>> BoolQueryAsync<T>(Expression<Func<T, object>> matchField, string matchQueryKeyword, Expression<Func<T, object>> fuzzyField, string fuzzyQueryKeyword, Expression<Func<T, object>> wildcardField, string wildcardQueryKeyword, string indexName = ElasticsearchIndexes.DefaultIndex, CancellationToken cancellationToken = default, int from = 0, int size = 10) where T : IElasticsearchModal
        {
            SearchResponse<T> searchResponse = await _elasticsearchClient.SearchAsync<T>(index => index
                                                                                                    .Index(indexName)
                                                                                                    .Query(query => query
                                                                                                        .Bool(t => t.Should(
                                                                                                            match => match.Match(t => t.Field(matchField).Query(matchQueryKeyword)),
                                                                                                            fuzzy => fuzzy.Fuzzy(p => p.Field(fuzzyField).Value(fuzzyQueryKeyword)),
                                                                                                            wildcard => wildcard.Wildcard(p => p.Field(wildcardField).Value(wildcardQueryKeyword))
                                                                                                            )))
                                                                                                    .From(from)
                                                                                                    .Size(size), cancellationToken);

            return searchResponse.Documents;
        }

        public async Task<IReadOnlyCollection<T>> TermQueryAsync<T>(Expression<Func<T, object>> field, string queryKeyword, string indexName = ElasticsearchIndexes.DefaultIndex, CancellationToken cancellationToken = default, int from = 0, int size = 10) where T : IElasticsearchModal
        {
            SearchResponse<T> searchResponse = await _elasticsearchClient.SearchAsync<T>(index => index
                                                                                            .Index(indexName)
                                                                                            .Query(query => query
                                                                                                .Term(t => t.Field(field)
                                                                                                            .Value(queryKeyword)))
                                                                                            .From(from)
                                                                                            .Size(size), cancellationToken);

            return searchResponse.Documents;
        }

        public async Task<IReadOnlyCollection<T>> ExistsQueryAsync<T>(Expression<Func<T, object>> field, string indexName = ElasticsearchIndexes.DefaultIndex, CancellationToken cancellationToken = default, int from = 0, int size = 10) where T : IElasticsearchModal
        {
            SearchResponse<T> searchResponse = await _elasticsearchClient.SearchAsync<T>(index => index
                                                                                            .Index(indexName)
                                                                                            .Query(query => query
                                                                                                .Exists(t => t.Field(field)))
                                                                                            .From(from)
                                                                                            .Size(size), cancellationToken);

            return searchResponse.Documents;
        }
    }

Evet, yukarıda görüleceği üzere ElasticsearchClient üzerinden yapılan sorgulamalar mahiyetlerine bağlı olarak belirli kullanım komplekslikleri barındırmakta ve bu, her ihtiyaç olduğu durumlarda bizlere zorluk yaratabilecek derecelerde olabilmektedir. Dolayısıyla bizler oluşturduğumuz interface sayesinde her bir metoda uygun davranış ve değerleri developer’dan parametre olarak almakta ve buradaki kullanım zorluğunun maliyetini bir kereye mahsus bu concrete servis nesnesi üzerinde ödeyerek, bundan sonrası için kullanımı kolaylaştırmaktayız.

Bunu daha net bir şekilde izah edebilmek için mesela term sorgusunu ele alabiliriz. Şöyle ki, ElasticsearchClient üzerinden bir term sorgusunu yapabilmek için aşağıdaki çalışmaya benzer bir kod geliştirmemiz gerekecektir;

            await _elasticsearchClient.SearchAsync<Product>(index => index
                                                    .Index("products")
                                                    .Query(query => query
                                                        .Term(t => t.Field("name")
                                                                    .Value("şuayip")))
                                                    .From(0)
                                                    .Size(10));

Şimdi soruyorum, her ihtiyaç olduğunda bu sorguyu tekrar tekrar yazacak babayiğit var mı ulan aranızda? Ne, nerde, nasıl çağrılıyor belli değil 🙂 Ee haliyle yazılımcıysak eğer bi tek tüketici değil bazen de düzeltici olmamız ve bizim bunu daha da kolay kullanılabilir hale getirmemiz gerekiyor değil mi? İşte bu durumda aşağıdaki gibi bir metodu inşa ediyoruz;

        public async Task<IReadOnlyCollection<T>> TermQueryAsync<T>(Expression<Func<T, object>> field, string queryKeyword, string indexName = ElasticsearchIndexes.DefaultIndex, CancellationToken cancellationToken = default, int from = 0, int size = 10) where T : IElasticsearchModal
        {
            SearchResponse<T> searchResponse = await _elasticsearchClient.SearchAsync<T>(index => index
                                                                                            .Index(indexName)
                                                                                            .Query(query => query
                                                                                                .Term(t => t.Field(field)
                                                                                                            .Value(queryKeyword)))
                                                                                            .From(from)
                                                                                            .Size(size), cancellationToken);

            return searchResponse.Documents;
        }

Ve bu metot sayesinde artık term sorgusunun ihtiyacını duyduğumuz her noktada aşağıdaki şekilde çağrım yapmamız yeterli olmaktadır.

    var datas = await elasticsearchService.TermQueryAsync<Product>(p => p.Name, queryKeyword: "şuayip", indexName: "products", from: 0, size: 10);

Evet, itiraf edin ki bu daha insancıl bir kod 🙂 İşte bu mantık doğrultusunda interface içerisindeki diğer imzalara karşın davranışlarda artık hangi metoda göre özelleştirilecekse onun fıtratına uygun bir şekilde yapılandırılmış vaziyettedir.

Velhasıl, son olarak inşa ettiğimiz bu yapıyı aşağıdaki gibi minimal api’ler de kullanabilir ve Elasticsearch çalışmasını .NET ortamında başarıyla gerçekleştirebiliriz;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSwaggerGen();
builder.Services.AddEndpointsApiExplorer();

builder.Services.AddSingleton<IElasticsearchService, ElasticsearchService>();

var app = builder.Build();

app.UseSwagger();
app.UseSwaggerUI();


app.MapGet("/products", async (IElasticsearchService elasticsearchService, CancellationToken cancellationToken) =>
{
    var datas = await elasticsearchService.GetDocumentsAsync<Product>(ElasticsearchIndexes.Products, cancellationToken);

    return Results.Ok(datas);
});

app.MapGet("/products/{id}", async (string id, IElasticsearchService elasticsearchService, CancellationToken cancellationToken) =>
{
    var data = await elasticsearchService.GetDocumentAsync<Product>(id, ElasticsearchIndexes.Products, cancellationToken);

    return Results.Ok(data);
});

app.MapGet("/products-details", async (IElasticsearchService elasticsearchService, CancellationToken cancellationToken) =>
{
    var datas = await elasticsearchService.SearchAsync<Product>(product =>
                  product.Index(ElasticsearchIndexes.Products)
                         .From(0)
                         .Size(10), cancellationToken);

    return Results.Ok(datas);
});

app.MapGet("/products-match/{query_keyword}", async (string query_keyword, IElasticsearchService elasticsearchService, CancellationToken cancellationToken) =>
{
    var datas = await elasticsearchService.MatchQueryAsync<Product>(p => p.Name, query_keyword, ElasticsearchIndexes.Products, cancellationToken);

    return Results.Ok(datas);
});

app.MapGet("/products-fuzzy/{query_keyword}", async (string query_keyword, IElasticsearchService elasticsearchService, CancellationToken cancellationToken) =>
{
    var datas = await elasticsearchService.FuzzyQueryAsync<Product>(p => p.Name, query_keyword, ElasticsearchIndexes.Products, cancellationToken);

    return Results.Ok(datas);
});

app.MapGet("/products-wildcard/{query_keyword}", async (string query_keyword, IElasticsearchService elasticsearchService, CancellationToken cancellationToken) =>
{
    var datas = await elasticsearchService.WildcardQueryAsync<Product>(p => p.Name, query_keyword, ElasticsearchIndexes.Products, cancellationToken);

    return Results.Ok(datas);
});

app.MapGet("/products-exists", async (IElasticsearchService elasticsearchService, CancellationToken cancellationToken) =>
{
    var datas = await elasticsearchService.ExistsQueryAsync<Product>(p => p.Name, ElasticsearchIndexes.Products, cancellationToken);

    return Results.Ok(datas);
});

app.MapGet("/products-bool", async (IElasticsearchService elasticsearchService, CancellationToken cancellationToken) =>
{
    var datas = await elasticsearchService.BoolQueryAsync<Product>(p => p.Name, "Mouse", p => p.Name, "erasr", p => p.Name, "*se*", ElasticsearchIndexes.Products, cancellationToken);

    return Results.Ok(datas);
});

app.MapGet("/products-term/{query_keyword}", async (string query_keyword, IElasticsearchService elasticsearchService, CancellationToken cancellationToken) =>
{
    var datas = await elasticsearchService.TermQueryAsync<Product>(p => p.Name, query_keyword, ElasticsearchIndexes.Products, cancellationToken);

    return Results.Ok(datas);
});

app.MapGet("/products-count", async (IElasticsearchService elasticsearchService, CancellationToken cancellationToken) =>
{
    var count = await elasticsearchService.CountDocumentsAsync<Product>(ElasticsearchIndexes.Products, cancellationToken);

    return Results.Ok(count);
});

app.MapPost("/products", async (CreateProductVM createProductVM, IElasticsearchService elasticsearchService, CancellationToken cancellationToken) =>
{
    Product product = new()
    {
        Name = createProductVM.Name,
        Price = createProductVM.Price,
        Quantity = createProductVM.Quantity
    };
    bool result = await elasticsearchService.CreateDocumentAsync(product, ElasticsearchIndexes.Products, cancellationToken);

    return Results.Ok(result);
});

app.MapPut("/products", async (UpdateProductVM updateProductVM, IElasticsearchService elasticsearchService, CancellationToken cancellationToken) =>
{
    bool result = await elasticsearchService.UpdateDocumentAsync<Product>(updateProductVM.Id, updateProductVM, ElasticsearchIndexes.Products, cancellationToken);

    return Results.Ok(result);
});

app.MapDelete("/products/{id}", async (string id, IElasticsearchService elasticsearchService, CancellationToken cancellationToken) =>
{
    bool result = await elasticsearchService.DeleteDocumentAsync<Product>(id, ElasticsearchIndexes.Products, cancellationToken);

    return Results.Ok(result);
});

app.Run();

Evet, böylece Elasticsearch’ün nasıl kullanıldığına Kibana ve .NET ortamı eşliğinde göz atmış bulunuyor ve Elastic.Clients.Elasticsearch kütüphanesinin getirdiği yeteneklere kendimizce özelleştirilmiş abstraction uygulayarak daha kullanışlı ve özgün bir yapıyı ortaya koymuş oluyoruz. Ve böylece bu uzun ve meşakkatli içeriği taktirlerinize sunarak burada noktalıyorum 🙂

İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…

Not : Örnek çalışmaya aşağıdaki github adresinden erişebilirsiniz.
https://github.com/gncyyldz/Elasticsearch.Example

Mobil sürümden çık