ElasticSearch Nedir ? Windows Üzerinde Kurulumu

Elasticsearch, java dilinde open-source olarak geliştirilen, dağıtık mimariye uygun, kolay ölçeklenebilir, enterprise düzeyde bir big-data arama motorudur. Sahip olduğu Http protokolü üzerinde çalışan Restful Api ile CRUD işlemlerini oldukça hızlı bir şekilde yapabilmemize olanak sağlar. 

ElasticSearch veya diğer search engine'lerin geliştirilmesine asıl sebep olan şey big-data dır. Her an her saniye milyonlarca satır veri üretiminden bahsediyoruz ve toplanmış olan bu verileri analiz etmek istediğimizde bu işlemi database seviyesinde yapıyorsak yani SQL'e bağımlıysak hız konusunda geride kalıyoruz. ElasticSearch core kısmında yer alan çeşitli algoritmalarıyla text-search işlemini oldukça kısa sürede hızlı bir şekilde yapabilmektedir.

Kurulum

Yukarıda bahsettiğimiz üzre elasticsearch Java tabanlı bir kütüphane olduğundan windows üzerinde kurulum yapmadan önce pc'nizde en az Java 8 versiyonu kurulu olmak şartıyla JRE ve JDK yüklü olmak zorundadır.

Elasticsearch elasticsearch.org adresinde ZIP and TAR.GZ gibi değişik formatlarda kurulum paketi sunmaktadır. Ancak ben temiz bir kurulum yapmanız adına MSI formatında olan paketi indirmeyi tercih edicem. İndirme işlemi bittiğinde exe'yi çalıştıralım ve bu adreste belirtildiği şekilde veya aşağıdaki görselde de olduğu gibi gerekli konfigurasyonları yaparak kurulumu tamamlayalım.

Kurulum sırasında path, memory-size gibi çeşitli konfigurasyonlar yapabilirsiniz. Eğer kurulumu Install as a service seçeneği ile yaptıysanız elasticsearch service olarak arka planda pc niz açık olduğu sürece çalışacaktır. Service'i görüntülemek için Windows Search kısmına "Services" yazdığınızda çıkan icon'a tıklayalım ve aşağıdaki gibi service listesinde elasticsearch'ü görelim.

Son olarak ES'ün çalışıp çalışmadığını browser üzerinden de kontrol edebiliriz. Browser'ın adres kısmına http://localhost:9200/ yazarak aşağıdaki gibi mevcut pc'niz de kurulu olan ES ile ilgili bilgilere ulaşabilirsiniz.

{
  "name" : "DESKTOP-GRKHT7E",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "saiTqKiQRr6m_GQ03BCH0Q",
  "version" : {
    "number" : "5.5.0",
    "build_hash" : "260387d",
    "build_date" : "2017-06-30T23:16:05.735Z",
    "build_snapshot" : false,
    "lucene_version" : "6.6.0"
  },
  "tagline" : "You Know, for Search"
}

Mimarisi

Elasticsearch'ü database üzerinden anlamaya çalışacak olursak;

SQL de bulunan Database ES'de Index'e denk gelmektedir. Tablo ise Tip yani ES'e Index yaparken vereceğimiz modellerimize denk gelmektedir. Tabloya kaydettiğimiz her bir row ise ES de Document olarak adlandırılır. Tabloda bulunan Column'lar Field yani Tip olarak verdiğimiz model de bulunan property veya field'lar dır. Schema ise Mapping olarak adlandırılır.

Aslında Code-First yaklaşımına aşina olan arkadaşlar yukarıdaki görsele baktıklarında ES'ünde bir nevi code-first mantığıyla çalıştığını görebilirler.

ES ile ilgili bu yazımızda sona geldik ancak bir sonraki yazımızda yapmış olduğumuz bu kurulum üzerinden bir .Net projesi geliştirerek ES Client'ların dan biri olan NEST'i kullanarak örnekler vermeye devam edeceğiz.

Nhibernate IPreInsertEventListener ve IPreUpdateEventListener Kullanımı

Server-side bir projede geliştirme yapıyorsanız ve db de bolca CRUD işlemleri için query'ler çalıştırmanız gerekiyorsa sizden db de kaydedilen o row için sizden insert veya update anında bazı bilgileri otomatik bir şekilde o row için kaydetmeniz istenebilir. Örnek olarak; CreatedDate veya update edilen değer için ModifiedDate gibi alanlar tutmanız muhakkak istenir istenmese dahi bu bilgileri ilgili colum'lar da tutmak muhakkak bir gün işinize yarayacaktır.

Eğer CRUD işlemlerini Ado.Net kullanarak yapıyorsanız query'nin sonuna bu değerleri ekleyebilir yada stored-procedure kullanıyorsanız da bu işlemleri sp içerisinde de yapabiliriz.

Bu yazımızda bu ve benzeri işlemleri Fluent-Nhibernate kullanarak nasıl yapabiliriz konusuna değineceğiz. 

Her defasında yeni kayıt geldi modeli initialize ederken CreatedDate alanına DateTime.Now set et, yada her update işlemi geldiğinde ModifiedDate alanına DateTime.Now alanını set et. Pek de hoş durmuyor sanki. 50'ye yakın db de tablonuz olduğunu düşünün her bir entity için gidip bu işlemleri heralde yapmak istemeyiz .

Eğer proejenizde NHibernate'i kullanıyorsanız Nhibernate bu işlemler için bizlere aşağıdaki interface'leri sunmakta.

  • IPreInsertEventListener
  • IPreUpdateEventListener

IPreInsertEventListener; adında da anlaşılacağı üzre entity'niz insert edilirken bir interceptor gibi araya girmemizi sağlayan ve insert query execution'dan OnPreInsert adındaki metoduna invoke edilerek entity'niz üzerinde işlemler yapmanızı sağlar.

IPreUpdateEventListener; ise bir update listener'ı dır ve içerisinde implement edebildiğimiz OnPreUpdate  metodu çağrılır update query'sinin execution'dan önce call edilerek yine entity üzerinde değişiklikler yapabilmemizi sağlar.

Örnek olarak bir BaseModel'miz olsun ve projemizde bulunan her bir entity için tablolarda ortak bulunan alanları bu class içerisinde tanımlayabiliriz.

    public abstract class BaseModel
    {
        public virtual Guid Id { get; set; }
        public virtual DateTime? CreatedDate { get; set; }
        public virtual DateTime? ModifiedDate { get; set; }
    }

Db de bulunan tablolarımıza karşılık gelen entity'lerimiz ise yukarıda tanımladığımız BaseModel class'ından inherit olacaklar. Örnek olarak Customer adında aşağıdaki gibi bir entity tanımlayalım.

    public class Customer : BaseModel
    {
        public virtual string FirstName { get; set; }
        public virtual string LastName { get; set; }
        public virtual string Email { get; set; }
    }

Şimdi ise CustomerMap class'ını oluşturacaz ancak öncesinde BaseModel içerisinde bulunan proeprty'ler için BaseMapping adında bir class tanımlayalım. Customer ve diğer db modellerimizde bu BaseMapping'i kullanarak map işlemlerini yapacağız. Bunu yapmamızdaki amaç her bir entity için ayrı ayrı gidip BaseModel içerisinde bulunan alanların map'ini yapmamak. 

    public class BaseMapping<T> : ClassMap<T> where T : BaseModel
    {
        public BaseMapping()
        {
            Id(x => x.Id);
            Map(x => x.CreatedDate);
            Map(x => x.ModifiedDate);
        }
    }

 

Artık BaseMapping 'i kullanarak CustomerMap class'ını oluşturabiliriz. 

    public class CustomerMap : BaseMapping<Customer>
    {
        public CustomerMap()
        {
            Map(x => x.FirstName);
            Map(x => x.LastName);
            Map(x => x.Email);
        }
    }

Sırada Listener class'ını oluşturmak var. Aşağıda NHInsertUpdateListener adında bir class tanımlayalım. Yazının başında bahsettiğimiz her tablomuzda bulunan CreatedDate ve ModifiedDate tarih alanlarını NHInsertUpdateListener içerisinde set edeceğiz.

    public class NHInsertUpdateListener : IPreInsertEventListener, IPreUpdateEventListener
    {
        public bool OnPreUpdate(PreUpdateEvent @event)
        {
            var audit = @event.Entity as BaseModel;
            if (audit == null)
                return false;

            var time = DateTime.Now;

            Set(@event.Persister, @event.State, "CreatedDate", time);

            audit.CreatedDate = time;

            return false;
        }

        public bool OnPreInsert(PreInsertEvent @event)
        {
            var audit = @event.Entity as BaseModel;
            if (audit == null)
                return false;


            var time = DateTime.Now;

            Set(@event.Persister, @event.State, "ModifiedDate", time);

            audit.ModifiedDate = time;

            return false;
        }

        private void Set(IEntityPersister persister, object[] state, string propertyName, object value)
        {
            var index = Array.IndexOf(persister.PropertyNames, propertyName);
            if (index == -1)
                return;
            state[index] = value;
        }
    }

Artık son adım olarak FluentNHibernate'i ayağa kaldırmak var. Nh configuration'ı aşağıdaki gibi tanımlayabiliriz.

Fluently.Configure()
               .Database(MsSqlConfiguration.MsSql2012.ConnectionString(c => c.FromAppSetting("dbConnectionString")).ShowSql())
               .Mappings(m => m.FluentMappings.AddFromAssemblyOf<CustomerMap>())
               .ExposeConfiguration(cfg => new SchemaUpdate(cfg).Execute(false, true))
               .ExposeConfiguration(cfg =>
               {
                   cfg.SetProperty(
                      NHibernate.Cfg.Environment.CurrentSessionContextClass,
                       "web");
                   cfg.AppendListeners(ListenerType.PreUpdate, new IPreUpdateEventListener[] { new NHInsertUpdateListener() });
               })
               .BuildSessionFactory();

 

Sırasıyla yazmak gerekirse neler yaptık;

  1. Vs da bir tane proje oluşturduk. (Console veya Api),
  2. FluentNHibernate paketini nuget'ten indirip kurduk,
  3. Bir db miz olduğunu ve connection string bilgisinin web config'de tanımlı olduğunu varsaydık,
  4. Tablolarda ortak kullanılan propert'leri BaseModel adında ki class da topladık,
  5. Daha sonra BaseMapping adında bir mapping tanımlaması yaparak entity içerisindeki property'leri map ettik,
  6. CustomerMap class'ını oluşturarak mapping işlemini tanımladık,
  7. NHInsertUpdateListener'ı oluşturduk ve CreatedDate - ModifiedDate alanları için değerleri set ettik.
  8. Fluent Nhibernate konfigurasyonunu oluşturduk.

 Listener'lar biraz az bilinen bir özellik gibi görünse de oldukça faydalıdırlar. Örnekte olduğu gibi benzer case'lerde kullanarak bizleri satırlarca tekrar eden kodlardan uzaklaştırır.