Jenkins, Continuous Integration araçlarının en popüleri ve DevOps‘un en önemli aşamasıdır diyebiliriz. Bu popülerliğe makaleyi okuduktan sonra, siz de hak vereceksiniz.
Jenkins Nedir?
Jenkins Continuous Integration – CI (Sürekli Entegrasyon) için yazılmış açık kaynak kodlu bir otomatik entegrasyon aracıdır. Java dili ile geliştirilmiştir.
Geliştiricilerin değişikliklerini kolayca entegre edebilmesi.
Commit edilen projeyi otomatik olarak derlemesi.
Testlerin otomatik olarak çalıştırması.
Kod ile ilgili veya testlere uymayan bir kod hatası alınırsa size bilgi verebilmesi ve mail atabilmesi.
Yazılım teslim sürecini hızlandırması.
Yazılımın kod kalitesini görüntüleyebilmesi.
Büyük topluluk desteğine sahip açık kaynak kodlu olması.
Çalışmanızı kolaylaştırmak çalışan geliştirici topluluğuna ve 1000’den fazla eklentiye sahip olması.
Sürekli Teslimat (Continuous Integration– CI) Kodun her an kurulumunun yapılabilir halde olmasını sağlamak diye tarif edebiliriz.
Kavramlar
Node : Jenkins sunucusunun üzerinde çalıştığı makineyi ifade eder.
Pipeline : İşlerin ardışık bir sıra ile yapılabilmesini sağlar.
Stage : Pipeline içerisindeki her fazı ifade eder. Örneğin : build > Test > deploy
Yaklaşık bir yıldır preview sürümü yayınlanmakta olan .Net Core 3.0 ‘ı release veryisonu2019-09-23 tarihinde yayınlandı. Bu linkteki makalede genel olarak 3.o’ın getireceği yenilikler hakkında bilgi paylaştım. Bu makalede ise .Net Core 3.0’ın getirdiği yeniliklerden biri olan System.Text.Json ‘ı detaylı bir şekilde inceledik.
“Ah Newtonsoft, vah Newtonsoft, by by Newtonsoft”
JSON Nedir ?
JSON (JavaScript Object Notation) veri depolamak ve taşımak için çevik bir formattır. Bütün diller tarafından desteklenmektedir.
Redis NoSQL memory cached veritabanı sistemlerinden biridir. Redis ilk başlarda Linux için yazılmış bir memory cached sistemi olsada Windows işletim sistelerini destekleyecek şekilde güncellenmiştir.
Avantajları
Senkron çalıştığı için son derece hızlıdır.
Birçok veri türünü destekler.
Veriyi hem RAM üzerine hem de ayarladığınız konfigürasyona göre disk üzerine kaydedebilir.
Disk üzerine kayıt yaptığı için restart sonrasında aynı verilerle çalışmaya devam eder.
Son derece aktif bir kullanıcı kitlesine sahiptir.
Sharding, cluster, sentinel,replication gibi birçok enterprise özelliklere sahiptir
Dezavantajları
Veri boyutunuza göre RAM’e ihtiyacınız olur.
Komplex sorgular için sorgu yapacaksanız, Redis yapısını düzgün kurgulamalısınız.
Bir transaction hata alırsa geri dönüşü yoktur.
Redis Kurulumunu bu linktenindirebilirsiniz. redis-server.exe tıklayıp kurulumu yapın. aşağıdaki ekran geldiği zaman kurulum tamamlanmıştır.
Project.json dosyası ile kurulum “Swashbuckle”: “6.0.0-beta902”
Middleware yapılandırması
public void ConfigureServices(IServiceCollection services)
{
//Framework servislerini ekleyiniz.
services.AddMvc();
//Loglama servislerini ekleyiniz.
services.AddLogging();
// Add our repository type
services.AddSingleton<IEFRepository,EFRepository>();
//Servislere swaggeri ekleyiniz. Kullanmka istediginizde ISwaggerProvider servisini inject ederek kullanınız.
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new Info { Title = "Baslık", Version = "v1" });
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", " Servis baslık");
});
}
Örneklere başlamadan önce karşılaştığım bir hata hakkında bilgi vermek istiyorum. Projenizde area(admin) alanı var ise orada API değilde normal kontroller kullanıyorsanız swagger oluşturulurken şema hatası verecektir.Bu hatanını çözümünü o kontrolleri ignore ederek çözebilirsiniz. Yapmanız gereken aşağıdaki kod satırınız kontroller sayfanızın başına ekleyiniz.
[ApiExplorerSettings(IgnoreApi = true)]
Swagger Örnek Kullanımı
Middleware yapılandırılmasından sonra bir tane örnek yapacağım.Web API ile bir tablodaki verileri listeleme ve parametre ile getbyId methodlarını kullanacagım.Örnek kod bloğu aşağıdadır.
[Route("api/[controller]")]
[ApiController]
public class SwaggerController : Controller
{
private readonly IAppLoggerService _appLoggerService;
public SwaggerController(IAppLoggerService appLoggerService)
{
_appLoggerService = appLoggerService;
}
[HttpGet("[action]")]
public IList<AppLogger> GellAllLogs() {
return _appLoggerService.GetAll();
}
[HttpGet("[action]")]
public AppLogger GetById(int Id)
{
return _appLoggerService.GetById(Id);
}
}
JWT (JSON Web Tokens). WebApi ile mobil, IOT veya single page web projelerinde kullanıcı doğrulama, kullanıcı tanıma, veri bütünlüğünü ve bilgi güvenliğini koruma gibi noktalarda kullanılmakta.
Neden JWT ?
JSON kullanması
URL üzerinde taşınabilmesi
Web çerezleri kullanma zorunluluğu olmaması
CSRF ataklarına karşı daha kapalı olması
Hızlı doğrulama yapılabilmesi
Kolay ölçeklenebilir olması
Web uygulamaları açısından HTTP session gerekmemesi, stateless kullanıma uygun olması
Veri bütünlüğünü sağlaması
JWT Neden Güvenli ?
JWT, Base64 olarak şifrelenir
HASH256 kripto algoritması kullanılır
3 Bölümden Oluşur
Header: bölümünde; hangi token türünün ve şifreleme algoritmasının kullanıldığı bilgisi yer alır.
Payload; uygulama bazlı bilgilerin yer aldığı(claim,userId vs.) yani uygulamaya özel bölümdür.
Signature ise adından da anlaşıldığı gibi server tarafından üretilen signature’ın bulunduğu bölümdür.
[AllowAnonymous]
[HttpPost]
[Route("token")]
public IActionResult Post([FromBody]LoginRequest request)
{
if (ModelState.IsValid)
{
var user = _userService.Get(request.UserName, request.Password);
if (user == null)
{
return Unauthorized();
}
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, request.Username),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var token = new JwtSecurityToken
(
issuer: _configuration["Issuer"], //appsettings.json içerisinde bulunan issuer değeri
audience: _configuration["Audience"],//appsettings.json içerisinde bulunan audince değeri
claims: claims,
expires: DateTime.UtcNow.AddDays(30), // 30 gün geçerli olacak
notBefore: DateTime.UtcNow,
signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["SigningKey"])),//appsettings.json içerisinde bulunan signingkey değeri
SecurityAlgorithms.HmacSha256)
);
return Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token) });
}
return BadRequest();
}
public class LoginRequest
{
public string UserName {get;set;}
public string Password {get;set;}
}
ValueContoller.cs
[Authorize]
[Route("api/[controller]")]
public class ValuesController : Controller
{
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
Bir önceki bölümde projenin katmanlarını oluşturduk İncelemek için tıklayınız. 2. Bölümde konumuz mapping ve DBContext konfigrasyonu. Fluent Api ‘den de faydalanacağız bu kısımda. Core katmanına öncelikle EntityConfigratonMapper.cs adında class oluşturalım.
namespace MuratGOZCU.Core
{
public abstract partial class EntityConfigratonMapper<T> : IEntityTypeConfiguration<T> where T : class
{
public abstract void Configure(EntityTypeBuilder<T> builder);
}
}
Neden Fluent Desing Pattern ? Martin Fowler‘ın yayınladığı bir manifestodur. Amacı daha okunabilir ve geliştirilebilir architecture projeler oluşturmak. Aslında projelerimizde kullandığımız ve ismini bilmediğimiz bir pattern diyebiliriz 🙂 Başka bir yazıda fluent design pattern konusunu daha detaylı incelemek üzere serimiz çok uzun olacak konuyu çok da uzatmayalım.
Data katmanına Mapping klasörü oluşturup içerisine CategoryMap.cs oluşturuyoruz.
namespace MuratGOZCU.Data.Mapping
{
public class CategoryMap : EntityConfigratonMapper<Category>
{
public override void Configure(EntityTypeBuilder<Category> builder)
{
builder.HasKey(m => m.Id);
builder.Property(m => m.Name).HasMaxLength(250);
builder.Property(m => m.Description).HasMaxLength(255);
}
}
}
EFDbContext.cs class’ımızı yazalım ve reflection ‘ın güzelliklerinden faydalanarak Entity classlarını yani, Category classımızı ve diğer mapping yapacağımız bütün tabloları database’e migration yapmak üzere hazırlayalım. Generic bir method yazıp EntityConfigratonMapper referansını içeren entity classlarının DBSet<> işlemlerini yapmış olduk.
namespace MuratGOZCU.Data
{
public class EFDbContext : DbContext
{
public EFDbContext(DbContextOptions<EFDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
var typesToRegister = Assembly.GetExecutingAssembly().GetTypes()
.Where(type => !String.IsNullOrEmpty(type.Namespace))
.Where(type => type.BaseType != null && type.BaseType.IsGenericType &&
type.BaseType.GetGenericTypeDefinition() == typeof(MuratGOZCU.Core.EntityConfigratonMapper<>));
foreach (var type in typesToRegister)
{
dynamic configInstance = Activator.CreateInstance(type);
modelBuilder.ApplyConfiguration(configInstance);
}
}
}
}
appsettings.json dosyasına bir bağlantı dizesi ekleyeceğiz, böylece veritabanıyla etkileşime girebilelim.
Entity Framework , SOLID ve Generic Repository ‘nin avantajları vardır bu avantajlardan faydalanacağız.
Yazılımcının sırtına çok fazla yük vermemek için tasarlayıcağımız mimaride entity framework ve base işlemler kullanacağız. Ekip veya bireysel olarak hızlı kod ve anlaşılır kod yazma imkanı sunacağız, Oluşturulan projenin senaryosunu göre base metotların içeriği elbette değişebilir.
Projenin mimarisine geçmeden önce başlıkları birlikte inceleyelim.
Kullanılacak Teknolojiler ve Patternler Fluent Pattern, AutoMapper, Entity Framework, BaseClass, WebApi, BaseController, UnitOfWork Pattern, Repository ve Generic Repository, Service ve Generic Service
1.Bölüm : Projenin ve katmanlarının oluşturulması Solution Projesi açalım ve Libraries ve Presentation adında 2 tane klasör oluşturup içlerine aşağıdaki gibi projenin katmanlarını oluşturalım.
Create a new project – Yeni Proje Oluşturuyoruz
Öncelikle BaseClass oluşturuyoruz ve sonrasında oluşturacağımız DbClasslarımıza inheritance olarak vereceğiz.
namespace MuratGOZCU.Core
{
public class BaseEntity
{
public int Id { get; set; }
}
}
Core katmanına Domain adında bir klasör açıp içerisine DBClasslarımızı oluşturuyoruz.İlk Classımız Category classını oluşturalım.
namespace MuratGOZCU.Core.Domain
{
public class Category:BaseEntity
{
public string Name { get; set; }
public string Description { get; set; }
}
}
Senaryomuzu kısaca anlatayım. Bir üst tablomuz Product ve alt tablomuz ProductPrice. “Güncelle” butonuna bastığımızda gönderilen model’de product modelimiz update olacaktır. Alt tablomuz olan productPrice tablomuzda update olmayacak. aynı anda client bazlı insert, update ve delete yaptığımızda aşağıdaki sorguda üç işlemi tek seferde yapacaktır.
Örnek Json Model
// var olan productPrice elemanlarınının state durumlarını delete yapıyoruz.
// Henüz silmedik
context.productPrice.Where(w => w.ProductId== myObject.Id).ToList()
.ForEach(item => context.productPrice.Delete(item));
// İkinci aşamada model'den Idsi 0 olmayanlara state durumlarını update olarak tanımlıyoruz.
// Henüz Update etmedik
Model.ProductPrice
.Where(x => x.Id != 0).ToList()
.ForEach(order => context.productPrice.Update(order));
context.productPrice.Attach(myObject);
context.productPrice.Update(myObject);
await _unitOfWork.SaveChangesAsync();
Burada Put işlemi içerisine 3 işlem birden yaptırdık. Uzun uzun sorgular yazmak yerine daha kısa ve düzenlene bilir kod yazmış olduk.
Tenant, saas veya store mantığı ile çalışan bir web uygulamamız var diyelim ve yazılan bütün metotlar için appId yi sorgunun sonuna eklemek gerekiyor.Ama biz bu işlemi otomatik hale getireceğiz. Url bizim appId’mizi verecektir. Amacımız : kod temizliği, güvenlik ve esneklik katacaktır uygulamamıza.
Url’den Gelen Tenant ‘ı cache alıyoruz.
protected override Task<TenantContext<AppTenant>> ResolveAsync(HttpContext context)
{
TenantContext<AppTenant> tenantContext = null;
var hostName = context.Request.Host.Value.ToLower();
var tenant = _tenantService.GetTenantResultByHostName(hostName);
var appTenant = new AppTenant()
{
Id = tenant.tenant.tenantId,
Host = tenant.tenant.host,
Name = tenant.tenant.name,
RequireSSL = tenant.tenant.RequireSSL,
LanguageId = tenant.tenant.LanguageId
};
if (tenant != null)
{
tenantContext = new TenantContext<AppTenant>(appTenant);
}
return Task.FromResult(tenantContext);
}
Örneklerle biraz daha açalım konuyu.
1. adımda appId’yi manuel olarak yazacağım ve bir Get işlemi deneyeceğim.
var userList= _userRepository.Table.where(x=>x.AppId == _appId);
2. adımda ise appId’yi middleware de otomatik eklemiş örneği görelim.
var userList= _userRepository.Table;
Aradaki fark şahane dimi.
Bu senaryoda de her tenant’ın kendi domaini var. Proje ayağa kalkerken ayarlar bir defa yüklenecek. Önce context classımıı açıp içerisine asembly’den faydalanarak bütün veri tabanı entity classlarına appId ekleyip oluşturduğumuz expression’ı HasQueryFilter ile gönderiyoruz.
Artık .net core ile Desktop uygulamları yazılabilecek. WPF, Windows formları ve WinUI artık açık kaynaklı olacak.
Newtonsoft Geliyor
Anladığım kadarı ile Microsoft kendi Json reader’ının yavaş olduğunu fark etti. Kendisinden daha performanslı olduğunu kanıtlayan Newtonsoft u default olarak kullanacak.
Index Sınıfı
Index tipi eklendi. Artık int kullanmak zorunda değiliz. Index tipinin amacı tuttuğumuz değere dizinin neresinden başlayacağını atayabiliriz.
Index i1 = 2; // 2. index den başla
Index i2 = ^3; // 3. adım ilerle
int[] a = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
Console.WriteLine($"{a[i1]}, {a[i2]}"); // "2, 5"
Range Sınıfı
Typescript’ten aşina olduğumuz bir kullanım şekli aslında. Range ise iki Index kullanarak dizinin belirli bir kısmını ifade edebileceğimiz bir yapı. a dizisi içerisinde i1 ve i2 Index leri arasını range ile alabiliyoruz.