.Net Core ve Go ile Mikroservis Mimarisi

Selamlar,

Mars Üzerinde Rover İzleri.

Bu makalede, .Net Core bir WebApi projesinde bir şirket yaratılacak ve bu şirket Go ile yazılmış bir Microservices tarafından işlenmek üzere bir kuyruğa alınacaktır. Peki neden Microservice ? Çünkü yapılacak işlemler, vakit alan uzun süren işlemlerdir. Ayrıca fazlaca sistem tüketen işlerdir. Bu nedenle ilgili işlemleri, harici bir makinada ve asenkron olarak işletmek için Microservicesler tercih edilmiştir.

.Net Core WebApi:

Gelin ilk önce .Net Core bir WebApi projesini, aşağıdaki gibi yaratalım.

.Net Versiyonu olarak aşağıda görüldüğü gibi .Net 5.0 seçilmiştir.

Nuget Package Manager’dan, proje için gerekli olan aşağıdaki dosyalar indirilir:

Yaratılacak olan Company Model, aşağıdaki gibidir.

Bugünün sorunları dünün çözümlerinden kaynaklanır. ―Peter M. Senge

Startup.cs(ConfigureServices()): .Net Core Web Api projesinde Swagger entegrasyonu için Startup.cs, aşağıdaki gibi değiştirilir.

Startup.cs(Configure()): Swagger için ilgili “.json”, aşağıdaki gibi tanımlanır.

CompanyController: Aşağıda görüldüğü gibi, InsertCompany() methodunda Company modeli, parametre olarak beklenmektedir.

  • SwaggerOperation: İlgili methodun, swagger üzerindeki görünen açıklamasıdır. Bunun için “Swashbuckle.AspNetCore.Annotations” kütüphanesinin, indirilmesi gerekmektedir.
  • “var factory = new ConnectionFactory()”: RabbitMQ’ya bağlanılacak configurasyon tanımlanır.
  • “channel.QueueDeclare(queue: “company”: RabbitMQ’ya atılacak  Company channel, burada tanımlanır.
  • “var companyData = JsonConvert.SerializeObject(companyModel)”: Gelen CompanyModel, Serialize edilip daha sonra byte[] dizisine çevrilir.
  • “channel.BasicPublish(exchange: “”, routingKey: “company”, basicProperties: null, body: body)”: Gönderilen model, “Company” channel’ına konur. Burada amaç, sonuçlanması uzun sürecek işleri client’ı bekletmeden arkada yapmak ve sunucu üzerindeki yükü başka makinalara aktarmaktır.

Go Microservice:

Sıra geldi, bu channel’ı dinleyen bir Go Microservices’i yazmaya. İlgili CompanyModel channel’a düştüğünde, Remotedaki bir SQLDB’ye ilgili Company kaydı yazılıp, bunun haricinde “borakasmer.com” sayfası pars edilip son 10 makalesi, console’a örnek amaçlı yazdırılacaktır. Aşağıda, Go projesinde kullanılan sayfalar, görevlerine göre ayrılmıştır.

Aklınıza neden Go diye bir soru gelir ise, hem mikroservices projerinde tüketilen kaynağın azlığı, hem çalışma performansı hem de kodlama kolaylığından dolayı tercih edilmiştir.

shared.go: Proje içinde kullanılacak tüm tanımlamaların yapıldığı, package’dır. Aşağıda dikkat ederseniz, Channel’a atılan CompanyModel, uzak bağlantı kurulacak SQLDB’nin ve RabbitMQ’nun connection stringleri tanımlanmıştır.

İnsanların yanılgısı, karşılaştıkları sorunları tam olarak açıklamadan önce, çözümleri istemeleridir. ―Jorge Angel Livraga

sql.go: Gelen CompanyModel’in Remote’daki bir SQLDB kaydedilmesi ve var olan Company kayıtların daha en başta listelenmesi için kullanılan package’dır.

  • “func GetSqlContent(db *sql.DB)”: Kayıtlı company listesinin çekildiği functiondır.
  • “CompanyName []string CompanyCode []float64 CompanyUrl []string ApiUrl []string” : SqlDB’den çekilecek olan kolonlar, dizi haline bu değişkenlere atanacaktır.
  • “ctx”: Go’da Sql Query çekildiği zaman, time out süresinin tanımlanması için kullanılmıştır. Aksi durumda, Sql Query’de bir sorun olması durumunda query sonlandırılmaz.
  • “rows, err := db.QueryContext(ctx, “select CompanyName,CompanyCode,CompanyUrl,ApiUrl from [dbo].[Companies] where IsDeleted = 0″)”: Companies tablosundan, ilgili kolonlar çekilmiş ve rows değişkenine atanmıştır.
  • “for rows.Next() {” : Herbir kayıt, tek tek gezilir.
  • “err := rows.Scan(&_companyName, &_companyCode, &_companyUrl, &_apiUrl)”: Her satıra ait kolonlar, tanımlı ilgili değişkenlere atanır.
  • “CompanyName = append(CompanyName, _companyName)”: Herbir değişken, tanımlı kolonlara ait dizilere atanır.
  • func InsertSqlContent(db *sql.DB, company *shared.AddCompany)”: Yeni bir Company kaydının, DB’ye atılması için kullanılan functiondır.
  • “stmt, err := db.Prepare(“INSERT INTO Companies(CompanyName,CompanyCode,CompanyUrl,ApiUrl) VALUES (@p1, @p2,@p3,@p4); select ID = convert(bigint, SCOPE_IDENTITY())”)”: Companies tablosuna kaydedilen Insert query’sidir. Geriye insert edilen kaydın ID değeri, “Scope_Identity” ile alınmaktadır.
  • “ctx, cancel := context.WithTimeout(context.Background(), time.Minute)”: Insert işlemi için DB’ye, default 1 dakkalık connection timeout süresi atanmaktadır.
  • “defer stmt.Close()”: Enson işlem bitince, Sql connectionın kapanması için çağrılan functiondır.
  • “rows := stmt.QueryRowContext(ctx, company.CompanyName, company.CompanyCode, company.CompanyUrl, company.ApiUrl)”: İlgili insert işlemi için yukarıda tanımlanan (@p1, @p2,@p3,@p4) parametrelerine Gönderilen Company’nin kolonları atanır ve ilgili Insert Query çalıştırılır.
  • “var _id int64 rows.Scan(&_id) return _id, nil”: Insert sonucu dönen identity ID değeri, “_id” değişkenine atanarak geri dönülür.

borakasmer.com’da ilgili maklelerin başlık ve urllerinin bulunması için “entery-title” css’i, filitre olarak kullanılmıştır.

parser.go: “borakasmer.com”‘un son 10 makalesinin, parse edilip çekildiği package’dır. Bu işlem için Go’da “PuerkitoBio/goquery” paketi kullanılmıştır.

  • “var articleList map[string]string”: Bir çeşit dictionary olan map ile, çekilen makalelerin başlık ve urlleri bu articleList’e doldurulur.
  • “client := &http.Client{ Timeout: 30 * time.Second, }”: Request çekilecek site için, 30sn’lik request timeout süresi tanımlanmıştır.
  • “res, err := client.Get(“https://www.borakasmer.com”)”: İlgili sitete request çekilir.
  • “defer res.Body.Close()”: Tüm işlemler bittikten sonra, connection’ın kapanması için ilgili function çağrılmıştır.
  • “doc, err := goquery.NewDocumentFromReader(res.Body)”: Request’den dönen sonuc, yani sayfanın body’si doc değişkenine atanmıştır.
  • “data := doc.Find(“.entry-title”)”: İlgili sayfa içinde “entry-title” ile başlayan tüm HtmlElementler filitrelenmiştir. Bu şekilde, ilgili makalelerin tümü bulunmuş olmaktadır.
  • “articleList = make(map[string]string, data.Length())”: Bulunan makale sayısı kadar articleList’e, boyut atanarak “make()” komutu ile yaratılmıştır.
  • “data.Each(func(i int, s *goquery.Selection) {“: Herbir filitrelenen data, tek tek gezilmektedir.
  • “title := s.Find(“a”).Text() url, _ := s.Find(“a”).Attr(“href”) articleList[title] = url”: Herbir makalenin url ve title’ı bulunarak, articleList’e eklenmiştir.
  • “return articleList”: En son da doldurulan liste, geri dönülmektedir.

consumer.go: Amaç RabbitMQ’da “company” channel’ı dinlenip kayıt geldiği zaman birden fazla işin tetiklenerek çalıştırılmasıdır. İşler, ilgili kaydın SQLDB’ye kaydedilmesi, tüm kaydın listelenmesi, “borakasmer.com”‘un parse edilip son 10 makalesinin ekrana basılmasıdır.

  • “conn, err := amqp.Dial(shared.Config.AMQPURL)”: RabbitMQ’ya ait connection, shared package’dan çekilir.
  • “amqpChannel, err := conn.Channel()”: İlgili connection’a ait channel, amqpChannel’a atanır.
  • “defer amqpChannel.Close()”: İlgili channel func’ın sonunda, tüm işlemler tamamlanınca kapanır.
  • “queue, err := amqpChannel.QueueDeclare(“company”, false, false, false, false, nil)”: “Company” channel’ını dinleyen Queue, tanımlanır.
  • “messageChannel, err := amqpChannel.Consume( queue.Name …” : İlgili queue dinlenir ve bir kayıt channel’a düştüğünde, bu messageChannel’a atanır.
  • “stopChan := make(chan bool)” İlgili Company Channel’ın conccurent olarak dinlenebilmesi için gerekli olan channeldır.
  • “go func() {.. for d := range messageChannel {” : go komutu ile messagChannel, arkada concurrent olarak dinlenir. Go’nun en büyük gücü, Threadler’de minimum kaynak tüketimidir.
  • “addCompany := &shared.AddCompany{}”: Queue’den çekilecek Company model, boş olarak shared kütüphanesinden yaratılır.
  • “err := json.Unmarshal(d.Body, addCompany)”: .Net Core WebApi ile Queue’ya atılan Company model, deserialize (Unmarshal) edilip addCompany model’e atanmıştır.
  • “res, err2 := sql2.InsertSqlContent(db, addCompany)”: Yukarıda tanımlanan sql package’daki Insert function’ı, çağrılıp kuyruğa alınan yeni Company, DB’ye kaydedilir.
  • “companyName, companyCode, companyUrl, apiUrl, err := sql2.GetSqlContent(db)”: Son kaydedilen Company kaydı dahil, tüm kayıtlar sql package’daki GetSqlContent() function’ı çağrılarak listelenir.
  • “articleList := parser.ParseWeb()” : Son olarak, yukarıda tanımlanan parser package’ındaki ParserWeb() function’ı çağrılarak, “borakasmer.com” portalı parse edilir. Blogdaki son 10 makale alınarak, articleList’e atanır.
  • “for title,url :=range articleList{ fmt.Println(“Blog:”, title, “=>”, “Url:”, url) }”: Çekilen tüm makalelerin Title ve Url’leri ekrana listelenir.
  • “<-stopChan”: İlgili channel’dan data okunarak, sonlandırılır.

Kendi sorununa kendin bizzat sahip çıkmazsan o sorun gün geldiğinde kendiliğinden çözülse bile sen o çözümü hak etmemiş olacaksın. ―Mehmet Murat İldan

main.go: Projenin başlangıç sayfasıdır. İlk proje ayağa kaldırıldığında, main() function’ı çağrılmaktadır.

  • “companyNames, companyCodes, companyUrls, apiUrls, err := sql.GetSqlContent(db)”: Öncelikle SQLDB’deki kayıtlı tüm company datası, çekilir.
  • “for i := range companyNames {“: Herbir kayıt, tek tek gezilerek ekrana basılır.
  • “rabbitMQ.Consumer(db)”: RabbitMQ’daki company channel’ı, dinlenmeye başlanır.

Geldik bir makalenin daha sonuna. Bu makalede, farklı teknolojilerin birbirleri ile nasıl uyumlu çalışabildiklerini hep birlikte inceledik. Bana göre .Net Core WebApi projelerinde hem çok güçlü hem de çok performanslı. Go da Microservices çözümlerinde, Concurrent yapılardaki performansı, minimum kaynak tüketimi ile, maximum iş yapması seçimlerimde büyük rol oynamaktadır. Birden fazla işlemin yapıldığı concurrent yapılarda, Go tam bir performans ve hız canavarıdır. Ayrıca .Net Core katmanlı mimaride ve Repository Pattern’in kullanılmında, kendine özgü bir pratikliğe ve kolaylığa sahiptir.

Kısacası bir teknoloji üzerinde fanatiklik yapmaktansa, farklı teknolojilerin hangi ihtiyaçlara göre yaratıldığına ve bunlardan hangisi veya hangilerinin var olan sorunlarımıza çözüm olabileceğine karar verip, bu şekilde ilerlenmelidir.

Yeni bir makalede görüşmek üzere, hepinize hoşçakalın.

Soruce Code (GO): https://github.com/borakasmer/GoParser/tree/main/Test

Soruce Code (.Net Core): https://github.com/borakasmer/CompanyService/tree/main/CompanyService/CompanyService

Source: github.com/masnun/gopher-and-rabbitjenicaandpatrick.combsilverstrim.blogspot.comgithub.com/PuerkitoBio/goquery

Herkes Görsün:

Bunlar da hoşunuza gidebilir...

2 Cevaplar

  1. Fehmi dedi ki:

    Elinize sağlık

Bir cevap yazın

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