SOLID Prensipleri — Part 1

Batuhan güngör
10 min readJan 13, 2024

--

Herkese selamlar,
Solid prensipleri hakkında OOP kitapları, Bilgisayar Mühendisliği dersleri ve eğitim serileri başta olmak üzere internette de bulunabilecek pek çok kaynak ve aktarım mevcut. Ancak ben bu yazımda konuyu biraz daha sektörün içinden direk olarak kullanım alanları ile ele almak istiyorum.

Her aktarımın çok kıymetli olduğuna inancım tam ancak kendi penceremden ve benim için ne ifade ettiğine de değinerek bu önemli kavramı ve içerdiği prensipleri sizlerle paylaşmak istedim.

S — Single Responsibility Principle
O — Open-Closed Principle
L — Liskow Substitution Principle
I — Interface Segregation Principle
D — Dependency Inversion Principle

Bu yazımda sizlerle SOLID prensiplerinin ilk 2 kavramını gerçek projelerden kullanım örnekleri ile paylaşmak istiyorum.
Genellikle teorik örnekler ile açıklandığını düşündüğüm bu kavramları, gerçek projelerdeki bazı uygulamaları ile anlatmaya çalışacağım. Aktarımlarımı sadece OOP üzerinden değil yaklaşımsal olarak da ele alıp “kural” tanımlamasından çıkarmayı dolayısıyla da gerçekten prensip olarak değerlendirmeyi amaçlıyorum.

Single Responsibility

Tanım olarak baktığımızda tam da ingilizce çevirisinden elde edeceğimiz çıktı gibi “Bir kod bloğu sadece bir sorumluluğu yerine getirmelidir” olarak görürüz. Ancak bu malesef “bu iş süreci, bu class’ın sorumluluğunda değil” den biraz ötesini ifade ediyor benim için. Şimdi benim tanımımı yapmak ve sonrasında da örnekler ile ne demek istediğimi açıklamak istiyorum.

Ben bu prensibi “herhangi bir kod bloğu, kendi üzerine 1'den fazla ‘Bağımlı’ sorumluluğu almamalıdır” olarak tanımlıyorum. Peki bu aslında ne demek? İki örnek ile farklı anlamlarını ele alalım.

1-) Kod blokları üzerinden “Single Responsibility” ihlallerine değinelim.

Diyelim ki bir bankacılık sisteminde çalışıyoruz. Bu sistem üzerinde bir kullanıcı yaratmak istiyoruz ve bu kullanıcı yaratıldıktan hemen sonrasında da ilk banka hesabının da oluşturulması gerekiyor. Bu senaryo üzerinden geliştirmemizi tasarlarken kullanıcı oluşturma ve bu kullanıcı için ilk hesabın oluşturulması sürecini birer sorumluluk olarak düşünmeli ve bu kapsamda yaklaşmalıyız. Aksi takdirde oluşacak kod bloğu şu şekilde olacaktır.

UserController :

using BankAPI.Services;
using Microsoft.AspNetCore.Mvc;

namespace BankAPI.Controllers
{
[ApiController]
[Route("api/users")]
public class UserController : Controller
{
private readonly UserManager _userManager;
public UserController(UserManager userManager)
{
_userManager = userManager;
}
[HttpPost]
public IActionResult Create()
{
var user = _userManager.CreateUser();
return Ok(user);
}
}
}

UserManager :

using BankAPI.Models;

namespace BankAPI.Services
{
public class UserManager
{
public User CreateUser()
{
var user = new User()
{
Id = Guid.NewGuid(),
Name = "Batuhan Güngör"
};

user.Account = CreateUserAccount();
return user;
}

private Account CreateUserAccount()
{
var account = new Account()
{
Name = "Bank Account 1",
Balance = 100
};
return account;
}
}
}

Ancak bu yaklaşımla, sistemde user işlemlerini ve hesap işlemlerini aynı anda ve aynı kod bloğu içerisinde yapıp bağımlılık oluşturmuş ve pek çok sorumluluğu tek kod bloğuna vermiş oluyoruz. Bu durum bize 2 sorun olarak dönebilir. İlk olarak hesap oluşturma işleminde yapılacak değişiklik ve eklemeler hiç alakası olmamasına rağmen UserManager Class’ını etkileyecek ve kodun sürekli bir dönüşüme uğramasına sebep olacaktır. İkinci sorun ise, sorumlulukların bölünmemesi sebebiyle, eğer hesap oluşturma işleminde bir sorun ile karşılaşılırsa tüm kullanıcı oluşturma sürecimiz bloklanacaktır.

Bu sorunu çözmek için öncelikle kod seviyesindeki bağımlılığı yok etmeliyiz. Kodumuz aşağıdaki gibi değişmelidir.

UserController :

using BankAPI.Services;
using Microsoft.AspNetCore.Mvc;

namespace BankAPI.Controllers
{
[ApiController]
[Route("api/users")]
public class UserController : Controller
{
private readonly UserManager _userManager;
private readonly AccountManager _accountManager;
public UserController(UserManager userManager, AccountManager accountManager)
{
_userManager = userManager;
_accountManager = accountManager;
}
[HttpPost]
public IActionResult Create()
{
var user = _userManager.CreateUser();
user.Account = _accountManager.CreateAccount();
return Ok(user);
}
}
}

UserManager :

using BankAPI.Models;

namespace BankAPI.Services
{
public class UserManager
{
public User CreateUser()
{
var user = new User()
{
Id = Guid.NewGuid(),
Name = "Batuhan Güngör"
};
return user;
}
}
}

AccountManager:

using BankAPI.Models;

namespace BankAPI.Services
{
public class AccountManager
{
public Account CreateAccount()
{
var account = new Account()
{
Name = "Bank Account 1",
Balance = 100
};
return account;
}
}
}

Bu durumda kullanıcı oluşturma ve hesap oluşturma sorumluluklarını ayırmış olduk. Ancak yine de hesap oluşturma sürecine bağımlı olduğumuzu unutmamalıyız. Teorik eğitimler tam olarak da bu noktada eksik kalıyor kanısındayım. Bağımlılıklar kod seviyesinde ayrılsa da, uygulama seviyesinde halen oluşması muhtemel sorunlara sebebiyet verecek bir yapıya sahibiz. Benim tavsiyem hesap oluşturma sürecini bir “Event” olarak publish edip tamamen asenkron hale getirmek ve bağımlılığın tamamen ortadan kaldırılması olacaktır. Bu durumda kullanıcımız oluşturulduktan sonra arayüze’e başarılı bilgi verip hesap oluşturma sürecini arka planda asenkron yapabilir, oluşan sorunlarda tekrar deneme gibi yapısal süreçleri tasarlayabiliriz.

Kısacası sadece kod seviyesindeki Single Responsibility uygulamalarını ya da teorik olarak anlatılan “kodlarımızı sorumluluk bazında ayırmalıyız” yaklaşımını yeterli bulmuyorum. Bu kavramları birer “Prensip” olarak benimsemek ve projelerimizi de bu mantalite ile kodlamalıyız düşüncesindeyim.

2-) Proje yapıları üzerinden “Single Responsibility” ihlallerine değinelim.

Diyelim ki bir Sadakat programı oluşturuyoruz Bu kapsamda .Net Web API uygulamamız var. Bu uygulama kendi içerisinde dış servislere HTTP request atıp veri topluyor ve bu veriler üzerinden işlem yapıyor. Kullanıcı bilgisini ve kullanıcıya ait sipariş bilgisini dışarıdan çektiğimizi düşünelim. Bu projemizin bir de zamanlanmış görevleri yöneten katmanı olsun. Bu katmanda da, Kullancıya uygun kampanyalara ait bildirimleri belli süreler zarfında iletsin (Örneğin “Bir kampanya başlayacak, Kaçırma!” bildirimi)

Proje katmanlarımız:
API -> Rest Servisler ile UI’a veri besleyecek
Application -> İş mantıkları(Business Logic) süreçlerini burada kodlayacağız
Scheduler -> Zamanlanmış görevleri yönetecek

Bu kapsamda Program.cs dosyaları üzerinden DI yönetimi yapacağız. Kod tekrarını önlemek için dış servis HttpClient DI yönetimini Application katmanında yapalım. Ortaya çıkan görsel şu şekilde olacaktır.

API Katmanı Program.cs:

using Application.Infrastructure;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddApplicationLayer(builder.Configuration);

var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI();
app.MapControllers();

app.Run();

Scheduler Katmanı Program.cs:

using Application.Infrastructure;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddApplicationLayer(builder.Configuration);

var app = builder.Build();

app.Run();

Application Katmanı ConfigurationSetup :

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace Application.Infrastructure
{
public static class ConfigurationSetup
{
public static void AddApplicationLayer(this IServiceCollection services, IConfigurationRoot configuration)
{
var externalServices = new ExternalServicesAppSettings();
configuration.Bind("ExternalServices", externalServices);

var userServiceUri = new Uri(externalServices.UserService);
services.AddHttpClient<UserService>(conf =>
{
conf.BaseAddress = userServiceUri;
});

var orderServiceUri = new Uri(externalServices.OrderService);
services.AddHttpClient<OrderService>(conf =>
{
conf.BaseAddress = orderServiceUri;
});

var campaignServiceUri = new Uri(externalServices.CampaignService);
services.AddHttpClient<CampaingService>(conf =>
{
conf.BaseAddress = campaignServiceUri;
});
}
}
}

API Katmanı Appsettings :

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"ExternalServices": {
"UserService": "http://userservice.com",
"OrderService": "http://orderservice.com"
}
}

Bu örnekten de görülebileceği gibi Application katmanındaki DI setup metodunda (AddApplicationLayer) Tüm sistemde kullanılan HttpClient bağımlılıkları aynı anda implemente edildiği için, API katmanındaki appsettings içerisinde tanımlanmamış olan “CampaignService” url bilgisi exception fırlatacak ve uygulamanın ayağa kalkmasını engelleyecektir.

Bu hatayı gidermek için API katmanındaki appsettings dosyasına hiç ihtiyacı olmamasına rağmen CampaignService Url bilgisi eklenmek zorundadır. Bu da aslında ilgili katmandaki bağımlılık enjeksiyonunda bulunan Single Responsibility ihlalinin tasarımsal örneklerinden biridir. Eğer bir uygulama kendine özgü bir bağımlılığı kullanmak istiyor ise, bu bağımlılığın enjeksiyonunu da kendisi gerçekleştirmeli ve farklı bir uygulamanın ayağa kalkmasını engellememelidir. Sorunu gidermek için düzenleme yapalım.

API Katmanı Program.cs:

using Application;
using Application.Infrastructure;
using API.Infrastructure;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
//builder.Services.AddApplicationLayer(builder.Configuration);
builder.Services.AddExternalServices(builder.Configuration);

var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI();
app.MapControllers();

app.Run();

Scheduler Katmanı Program.cs:

using Application.Infrastructure;
using Scheduler.Infrastructure;

var builder = WebApplication.CreateBuilder(args);
//builder.Services.AddApplicationLayer(builder.Configuration);
builder.Services.AddExternalServices(builder.Configuration);

var app = builder.Build();

app.Run();

Düzenleme sonrası her uygulama kendi bağımlılıklarını kendisi inject ettiği için, çalıştırdığımızda herhangi bir sorunla karşılaşmıyoruz. Bu da bizim için proje yapılarındaki Single Responsibily’e bir örnek olmalı düşüncesindeyim.

Open-Closed

Open closed prensibi terimsel anlam olarak, yazdığımız kodun sürekli olarak gelişime açık, ancak temelinde değişikliğe kapalı olması gerektiğini ifade eder. Bu prensip ile yazılımın hayat döngüsü boyunca karşılaşacağı geliştirme isteklerine ve bakım taleplerine daha hızlı adapte olabilmesi ve karmaşıklığın azaltılması amaçlanır.

Örnek vermek gerekirse, kod içerisindeki proje isterlerinde pek çok koşul bazlı işlem farklılıkları bulunacaktır. Ancak bu farklılıklar zaman içinde artacak ve kodun hem işlem kompleksitesi hem de bakım ve geliştirme maliyeti yükselecektir. Ayrıca projeye yeni katılacak olan developerların süreçlerin tamamına hakim olması zorunlu hale gelecektir. Çünkü kodun tamamı tek bir scope içerisinde değerlendirilmiş, tüm iş mantıkları bu scope altına yazılmış olacaktır. Bu karmaşayı aşmak ve kodumuzu okunaklı, temiz ve bakım yapılabilir tutmak için sorumlulukların ayrılması gerekir. Bunu single responsibilityden bağımsız düşünemeyiz ancak sadece kod sorumluluğunu ayırmak yetmeyecek ve mimari yapılarla kodun davranışsal olarak kendini şekillendirmesi ve küçük iş mantıklarının bağımsızlaştırılması gerekir. Terimlerle anlatıldığında karmaşıklaştığının farkındayım bu sebeple gerçek örneklere geçmek istiyorum.

Örneğimizde bir kurumsal izin yönetim sistemini temel alacağız. Bu izin sisteminde basit olarak 2 izin tipi bulunmaktadır. Bu izin tipleri Yıllık izin ve Mazeret izni olacaktır. Bu 2 izin tipinin temelde özellikleri aynı olsa da bazı farklılıklar içeriyor. Sistemden yıllık izin aldığınızda yıllık izin hakkınızdan düşecek ve maaşınıza etkisi olmayacaktır. Ancak mazeret izni aldığınızda ise, maaşınızdan düşme yapılacaktır. Ayrıca mazeret izni hakkı, yıllık izinden farklı ve daha az sayıda günü içermektedir.

Sistemi kodlarken statik bir mock data ile çalıştım ve kullanıcıları hali hazırda çalışıyormuş ve bir takım izinleri daha önce kullanmış gibi yarattım ki çeşitli kontrollerimiz için elimizde veri olsun istedim.

Proje yapısı:
Projemiz sadece api katmanından oluşuyor, karmaşıklaştırıp örneği genişletmek istemiyorum. Endpointimizden alacağımız bir izin yaratma isteği sonucu öncelikle kullanıcının izin sayıları kontrol edilecektir. Eğer izin sayılarında sorun yok ise, izni oluşturulacak ve kullanıcının bilgileri ekrana dönülecektir. Bu sayede yeni maaşı, kalan izin hakkı vb. bilgileri görüntüleyebileceğiz.

LeaveController :

using API.Business.Leave.Models;
using API.Data;
using Microsoft.AspNetCore.Mvc;

namespace API.Controllers
{
[ApiController]
[Route("api/leave")]
public class LeaveController : Controller
{
private readonly MockDataBase _database;
public LeaveController()
{
_database = new MockDataBase();
}
[HttpPost]
public IActionResult CreateLeave([FromBody] CreateLeaveModel request)
{
var user = _database.GetUser(request.PersonId);
if (user == null) return NotFound();

var userLeaveInformations = user.LeaveInformations;
switch (request.LeaveType)
{
case LeaveType.Annual:
var availableAnnualLeaveRights = userLeaveInformations.AnnualLeave.TotalLeaveRights - userLeaveInformations.AnnualLeave.UsedDays;
if (availableAnnualLeaveRights <= request.RequestedDayCount)
return StatusCode(StatusCodes.Status406NotAcceptable);

userLeaveInformations.AnnualLeave.UsedDays += request.RequestedDayCount;
break;
case LeaveType.Excused:
var availableExcusedLeaveRights = userLeaveInformations.ExcusedLeave.TotalLeaveRights - userLeaveInformations.ExcusedLeave.UsedDays;
if (availableExcusedLeaveRights <= request.RequestedDayCount)
return StatusCode(StatusCodes.Status406NotAcceptable);

userLeaveInformations.ExcusedLeave.UsedDays += request.RequestedDayCount;
user.LeaveExpenseForSalay += request.RequestedDayCount * user.DailySalary;
break;
}
return Ok(user);
}
}
}

Örneğimizde de görülebileceği gibi tüm izin tiplerimiz için farklı kontrollerimiz ve kendine özgü iş mantıklarımız bulunuyor. Şimdi bu sisteme yeni bir izin tipi eklemeyi düşünelim. Yapmamız gereken yeni bir case eklemek ve bu case’e özel iş süreçlerini de aynı kod bloğu içerisine yazmak olacaktır. Bu farklı izinlerin kaç tane olabileceğini sizin de düşünmenizi ve bu case yapısının nereye kadar uzayacağını hayal etmenizi istiyorum. Biz örneğimizi basit tutmuş olabiliriz ancak şunu düşünmenizi de ayrıca rica edeceğim. Her bir izin tipinin farklı bağımlılıkları olabilir ve tüm bu bağımlılıkları mecburen bu sınıf üzerine inject etmek zorunda kalabiliriz. Mesela yüksek lisans eğitimi için bir izin aldık ve konu arge desteği kapsamında devlet sistemlerine bildirilmesi gereken iş mantıkları içeriyor. Bu durumda pek çok dış servise request atmak için gerekli bağımlılık enjeksiyonlarını yine bu sınıfımızın içerisine eklemek zorundayız. Peki o halde ne yapabiliriz.

Bu sistemi, temelde değişmeyecek ancak sürekli gelişmeye açık hale getirmek için öncelikle Factory Design Patter’dan destek alacağım. İzinlerimizi ana bir abstract class’tan türetecek ve kendine özgü işlemler yapacak alt sınıflara böleceğim.

Öncelikle izinlere özel işlem sınıflarımı yönetmek için LeaveFactory oluşturuyorum.

using API.Business.Leave.Management.LeaveOperators;
using API.Business.Leave.Models;
using API.Data.Entities;

namespace API.Business.Leave.Management
{
public class LeaveFactory
{
public void CreateLeave(CreateLeaveModel request, User user)
{
var leaveOperator = GetOperator(request.LeaveType);
leaveOperator.Create(request, user);
}

private LeaveOpeator GetOperator(LeaveType leaveType)
{
return leaveType switch
{
LeaveType.Annual => new AnnualLeaveOperator(),
LeaveType.Excused => new ExcusedLeaveOperator(),
_ => throw new NotImplementedException(),
};
}
}
}

Sonrasında her bir işlemi yapacak abstract classlarımı yaratıyorum

LeaveOperator (Ana Abstract sınıfım)

using API.Business.Leave.Models;
using API.Data.Entities;

namespace API.Business.Leave.Management.LeaveOperators
{
public abstract class LeaveOpeator
{
public abstract void Create(CreateLeaveModel request, User user);
}
}

Şimdi yıllık izinleri yönetecek sınıfımızı yazalım

using API.Business.Leave.Models;
using API.Data.Entities;

namespace API.Business.Leave.Management.LeaveOperators
{
public class AnnualLeaveOperator : LeaveOpeator
{
public override void Create(CreateLeaveModel request, User user)
{
var availableAnnualLeaveRights =
user.LeaveInformations.AnnualLeave.TotalLeaveRights - user.LeaveInformations.AnnualLeave.UsedDays;
if (availableAnnualLeaveRights <= request.RequestedDayCount)
throw new Exception("NotAcceptable");

user.LeaveInformations.AnnualLeave.UsedDays += request.RequestedDayCount;
}
}
}

Sırada mazeret izinleri var

using API.Business.Leave.Models;
using API.Data.Entities;

namespace API.Business.Leave.Management.LeaveOperators
{
public class ExcusedLeaveOperator : LeaveOpeator
{
public override void Create(CreateLeaveModel request, User user)
{
var availableExcusedLeaveRights =
user.LeaveInformations.ExcusedLeave.TotalLeaveRights - user.LeaveInformations.ExcusedLeave.UsedDays;
if (availableExcusedLeaveRights <= request.RequestedDayCount)
throw new Exception("NotAcceptable");

user.LeaveInformations.ExcusedLeave.UsedDays += request.RequestedDayCount;
user.LeaveExpenseForSalay += request.RequestedDayCount * user.DailySalary;
}
}
}

Controller sınıfımızın son hali ise :

using API.Business.Leave.Management;
using API.Business.Leave.Models;
using API.Data;
using Microsoft.AspNetCore.Mvc;

namespace API.Controllers
{
[ApiController]
[Route("api/leave")]
public class LeaveController : Controller
{
private readonly MockDataBase _database;
public LeaveController()
{
_database = new MockDataBase();
}
[HttpPost]
public IActionResult CreateLeave([FromBody] CreateLeaveModel request)
{
var user = _database.GetUser(request.PersonId);
if (user == null) return NotFound();

var leaveFactory = new LeaveFactory();
leaveFactory.CreateLeave(request, user);
return Ok(user);
}
}
}

Gördüğünüz gibi artık tüm izin işlemlerimi tek bir factory üzerinden yürütüyorum. Şimdi sisteme yeni bir izin tipi ekleyelim ve kodumuzu geliştirelim. Ancak bu geliştirme controller sınıfımızdaki temel implementasyona kesinlikle bir değişiklik etkisi yaratmasın. Bu izin tipimiz Hastalık izni olsun. Biliyorsunuz hastalık izinleri SGK sistemine aktarılmalı. Yani bir bağımlılığımız olacak ancak bu bağımlılık kesinlikle diğer izin tiplerini etkilemeyecek. Ayrıca bizim kurgumuzda hastalık izinleri mazeret izninden düşülsün fakat maaşa etki etmesin istiyoruz.

Yeni izin tipimiz için operator sınıfımızı yaratalım.

using API.Business.Leave.Models;
using API.Data.Entities;
using System.Text;

namespace API.Business.Leave.Management.LeaveOperators
{
public class SickLeaveOperator : LeaveOpeator
{
private readonly HttpClient _httpClient;
public SickLeaveOperator()
{
_httpClient = new HttpClient();
_httpClient.BaseAddress = new Uri("http://sgksistemi.com");
}
public override void Create(CreateLeaveModel request, User user)
{
CheckLeaveRight(user.LeaveInformations.ExcusedLeave, request.RequestedDayCount);

user.LeaveInformations.ExcusedLeave.UsedDays += request.RequestedDayCount;

var stringPayload = "TcNo = 12345678910, GunSayisi = request.RequestedDayCount";
var httpContent = new StringContent(stringPayload, Encoding.UTF8, "application/json");

_httpClient.PostAsync("izingiris", httpContent);
}
}
}

Factory sınıfımıza gerekli düzenlemeyi yapalım

using API.Business.Leave.Management.LeaveOperators;
using API.Business.Leave.Models;
using API.Data.Entities;

namespace API.Business.Leave.Management
{
public class LeaveFactory
{
public void CreateLeave(CreateLeaveModel request, User user)
{
var leaveOperator = GetOperator(request.LeaveType);
leaveOperator.Create(request, user);
}

private LeaveOpeator GetOperator(LeaveType leaveType)
{
return leaveType switch
{
LeaveType.Annual => new AnnualLeaveOperator(),
LeaveType.Excused => new ExcusedLeaveOperator(),
LeaveType.Sick => new SickLeaveOperator(),
_ => throw new NotImplementedException(),
};
}
}
}

bu şekilde sistemimize yeni bir izin tipi eklemiş olduk ve bu köklü değişikliğe rağmen controller sınıfımıza herhangi bir ekleme yapmadık

using API.Business.Leave.Management;
using API.Business.Leave.Models;
using API.Data;
using Microsoft.AspNetCore.Mvc;

namespace API.Controllers
{
[ApiController]
[Route("api/leave")]
public class LeaveController : Controller
{
private readonly MockDataBase _database;
public LeaveController()
{
_database = new MockDataBase();
}
[HttpPost]
public IActionResult CreateLeave([FromBody] CreateLeaveModel request)
{
var user = _database.GetUser(request.PersonId);
if (user == null) return NotFound();

var leaveFactory = new LeaveFactory();
leaveFactory.CreateLeave(request, user);
return Ok(user);
}
}
}

Şimdi bu yöntemi kullanarak yaptığımız işlemlerle birlikte bazı kod bloklarının tekrar ettiğini görmüşsünüzdür. Hadi bu kod tekrarlarından da kurtulalım.

LeaveOperator (ana abstract sınıfımız)

using API.Business.Leave.Models;
using API.Data.Entities;

namespace API.Business.Leave.Management.LeaveOperators
{
public abstract class LeaveOpeator
{
public abstract void Create(CreateLeaveModel request, User user);

protected void CheckLeaveRight(Data.Entities.Leave leave, int requestedDayCount)
{
var availableExcusedLeaveRights =
leave.TotalLeaveRights - leave.UsedDays;
if (availableExcusedLeaveRights < requestedDayCount)
throw new Exception("NotAcceptable");
}
}
}

Yıllık izin işleyen sınıfımız

using API.Business.Leave.Models;
using API.Data.Entities;

namespace API.Business.Leave.Management.LeaveOperators
{
public class AnnualLeaveOperator : LeaveOpeator
{
public override void Create(CreateLeaveModel request, User user)
{
CheckLeaveRight(user.LeaveInformations.AnnualLeave, request.RequestedDayCount);
user.LeaveInformations.AnnualLeave.UsedDays += request.RequestedDayCount;
}
}
}

Akıllara gelecek bir diğer soru ise, controller katmanında, ilk versiyonda fırlattığımız 406NotAccepted hatsının artık sistemde olmayışı olacaktır. Bu durumu da ResultPattern ve ExceptionHandler Middleware ile çözebiliriz. Uzun uzun yazmak yerine sevgili arkadaşım Adem Olguner’in yazısına sizleri yönlendirmek isterim. Bkz : Result Pattern

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

Bu yazımda sizler ile SOLID prensiplerinden en çok dikkatimi çeken ve uygulama konusunda en hassas olduğum 2 konudan bahsettim. Umarım verimli bir aktarım olmuştur. Özellikle çokça bulunabilecek teknik anlatımlardan uzak durmak ve direk olarak sektörel örnekler ile durumu açıklamak istedim.

Konu hakkında soru ya da görüşleriniz olursa mutlaka birlikte ele almak ve gerekli ekleme ve çıkarmaları birlikte yapmak isterim. Bir sonraki yazımda diğer SOLID prensiplerinin de kurumsal uygulamalardaki örnekleri ile üzerinden geçmeyi planlıyorum.

Bu yazıya kaynak oluşturan proje kodlarını github linkinden görüntüleyebilirsiniz.

Bkz : https://github.com/batgungor/SOLID

Okumak için zaman ayırdığınız için teşekkür eder, herkese iyi çalışmalar dilerim.

--

--

Batuhan güngör

Batuhan GÜNGÖR is a computer engineer who has been working since 2011 as a Software developer / Engineering Manager