Kurumsal projelerde, özellikle CRM, ERP, mikroservis mimarileri ve entegrasyon katmanlarında ölçeklenebilir, sürdürülebilir ve test edilebilir kod yazmak kritik önem taşır. Ayrıca, SOLID prensipleri bunu sağlayan yazılım tasarımının temel taşlarını oluşturur. Bu nedenle, bu yazıda SOLID prensiplerini gerçek CRM ve mikroservis senaryoları üzerinden, hatalı kod → doğru kod örnekleri ile açıklıyorum.
Neden Kurumsal CRM Projelerinde SOLID Zorunludur?
Kurumsal CRM projelerinde kullanıcı sayısı, entegrasyon hacmi ve iş kuralları sürekli büyür. Bu nedenle SOLID prensipleri sadece “iyi uygulama” değildir; değişen iş kurallarına hızlı adapte olabilmek için zorunlu bir mimari disiplindir. Özellikle öğrenci, mezun, bursiyer ve okul gibi hiyerarşik yapılar SOLID uygulanmadığında hızla yönetilemez hâle gelir.

1) Single Responsibility Principle (SRP)
Bir sınıfın değişmesi için tek bir sebebi olmalı.
Gerçek Senaryo:
Dynamics 365’ten öğrenci bilgisi çekip CRM’e kaydediyoruz. Ama aynı sınıf içinde hem mapping yapılıyor hem de veri kaydediliyor hem de log atılıyor.
❌ Yanlış Tasarım
public class StudentService
{
public void SyncStudent(D365Student d365)
{
// Mapping
var student = new Student
{
Name = d365.FullName,
School = d365.SchoolName,
ScholarshipType = d365.ScholarshipType
};
// Save
Database.Save(student);
// Log
File.AppendAllText("log.txt", $"{student.Name} synced");
}
}C#3 ayrı sorumluluk → değiştiğinde sınıf patlar.
✅ Doğru Tasarım (SRP uygulanmış)
public class StudentMapper
{
public Student Map(D365Student d365) =>
new Student
{
Name = d365.FullName,
School = d365.SchoolName,
ScholarshipType = d365.ScholarshipType
};
}
public class StudentRepository
{
public void Save(Student student)
{
Database.Save(student);
}
}
public class Logger
{
public void Log(string message)
{
File.AppendAllText("log.txt", message);
}
}
public class StudentSyncService
{
private readonly StudentMapper _mapper;
private readonly StudentRepository _repo;
private readonly Logger _logger;
public StudentSyncService(StudentMapper mapper, StudentRepository repo, Logger logger)
{
_mapper = mapper;
_repo = repo;
_logger = logger;
}
public void Sync(D365Student d365)
{
var student = _mapper.Map(d365);
_repo.Save(student);
_logger.Log($"{student.Name} synced");
}
}
C#2) Open/Closed Principle (OCP)
“Bir sınıf geliştirmeye açık, değişikliğe kapalı olmalı.”
Gerçek Senaryo:
Okul → fakülte → bölüm gibi hiyerarşik yapılarda farklı kaynaklardan veri geliyor.
Yeni kaynak eklenince servisi değiştirmemek istiyoruz.
❌ Yanlış Tasarım
public class FacultyImporter
{
public List<Faculty> Import(string sourceType)
{
if (sourceType == "D365")
{
// Dynamics 365 mapping
}
else if (sourceType == "SAP")
{
// SAP mapping
}
else if (sourceType == "CSV")
{
// CSV mapping
}
return new List<Faculty>();
}
}C#Her yeni kaynakta else if ekleniyor → OCP ihlali.
✅ Doğru Tasarım: Strategy Pattern ile OCP
public interface IFacultySource
{
List<Faculty> Import();
}
public class D365FacultySource : IFacultySource
{
public List<Faculty> Import()
{
// Mapping logic
return new();
}
}
public class CsvFacultySource : IFacultySource
{
public List<Faculty> Import()
{
// CSV parsing logic
return new();
}
}
public class FacultyImporter
{
private readonly IFacultySource _source;
public FacultyImporter(IFacultySource source)
{
_source = source;
}
public List<Faculty> Import() => _source.Import();
}C#Yeni kaynak → Yeni sınıf ekleyip IFacultySource implement etmek yeterlidir.
Mevcut kod değişmez.
3) Liskov Substitution Principle (LSP)
“Türetilmiş sınıflar, temel sınıfın yerine kullanılabilmelidir.”
Gerçek Senaryo:
CRM’de Kullanıcı Türü hesaplayan bir servisimiz var:
- Lise Öğrencisi
- Üniversite Öğrencisi
- Mezun
Yanlış kalıtım → alt sınıf davranışı bozuyor.
❌ Yanlış Tasarım
public class UserTypeCalculator
{
public virtual string CalculateType(User user)
=> "GeneralUser";
}
public class GraduateUserTypeCalculator : UserTypeCalculator
{
public override string CalculateType(User user)
{
throw new Exception("Graduate user cannot be processed here");
}
}C#Ana sınıfın beklediği davranış alt sınıfta bozulmuş.
✅ Doğru Tasarım
public interface IUserTypeCalculator
{
string CalculateType(User user);
}
public class GraduateUserTypeCalculator : IUserTypeCalculator
{
public string CalculateType(User user)
=> "Graduate";
}
public class StudentUserTypeCalculator : IUserTypeCalculator
{
public string CalculateType(User user)
=> "Student";
}C#Artık tüm sınıflar aynı kontratı düzgün şekilde uyguluyor.
4) Interface Segregation Principle (ISP)
“Kullanmadığın metodu içeren interface’i implemente etme.”
Gerçek Senaryo:
CRM’de kullanıcıya SMS, mail veya push bildirim gönderen bir servis var. Ancak bazı entegre sistemler SMS desteklemiyor, bu yüzden tek bir arayüz tüm ihtiyaçları karşılamıyor.
❌ Yanlış Tasarım
public interface INotificationService
{
void SendEmail();
void SendSms();
void SendPush();
}
public class EmailService : INotificationService
{
public void SendEmail() { }
public void SendSms() => throw new NotImplementedException();
public void SendPush() => throw new NotImplementedException();
}C#Kullanmadığımız metotları implement etmek zorunda kalıyoruz → ISP ihlali.
✅ Doğru Tasarım
public interface IEmailNotification
{
void SendEmail();
}
public interface ISmsNotification
{
void SendSms();
}
public interface IPushNotification
{
void SendPush();
}
public class EmailService : IEmailNotification
{
public void SendEmail() { }
}
public class SmsService : ISmsNotification
{
public void SendSms() { }
}C#Her servis sadece ihtiyacı olan interface’i implement eder.
5) Dependency Inversion Principle (DIP)
“Üst seviye sınıflar, alt seviye sınıflara değil soyutlamalara bağlı olmalı.”
Gerçek Senaryo:
CRM’e veri kaydeden bir servis var; ancak repository sınıfı doğrudan concrete bir sınıfa bağımlı olduğu için test yazılamıyor. Bu nedenle, yapı hem esnekliğini kaybediyor hem de bağımlılıklar artıyor.
❌ Yanlış Tasarım (Test edilemez yapı)
public class UserService
{
private readonly UserRepository _repo = new UserRepository();
public void Save(User user)
{
_repo.Save(user);
}
}C#Unit test yazılamaz, çünkü yapı doğrudan gerçek database’e bağlıdır; bu yüzden test ortamında izole edilemez.
✅ Doğru Tasarım (Mocklanabilir ve test edilebilir)
public interface IUserRepository
{
void Save(User user);
}
public class UserRepository : IUserRepository
{
public void Save(User user)
{
// DB logic
}
}
public class UserService
{
private readonly IUserRepository _repo;
public UserService(IUserRepository repo)
{
_repo = repo;
}
public void Save(User user)
{
_repo.Save(user);
}
}C#Artık test yazabiliriz:
[Fact]
public void Save_Should_Call_Repository()
{
var repo = new Mock<IUserRepository>();
var service = new UserService(repo.Object);
service.Save(new User());
repo.Verify(x => x.Save(It.IsAny<User>()), Times.Once);
}C#SOLID Uygulamasının En Büyük Kazancı
SOLID uygulandığında kod tabanı öngörülebilir hale gelir. Böylece ekip değiştiğinde veya yeni bir modül eklendiğinde mevcut kodu kırma riski minimuma düşer. Entegrasyon projelerinde bu esneklik, değişiklik maliyetini ciddi şekilde azaltır.
Sonuç
SOLID prensipleri çoğu zaman teorik başlıklar gibi görünse de CRM yapıları, mikroservisler, entegrasyonlar ve kurumsal projelerde gerçek sorunları çözen pratik kurallardır:
- SRP → Kod karmaşasını azaltır
- OCP → Yeni özellik eklemeyi güvenli hâle getirir
- LSP → Kalıtım kaynaklı hataları engeller
- ISP → Sınıfları gereksiz bağımlılıklardan kurtarır
- DIP → Test edilebilir, izole edilebilir bir yapı sağlar
Bu örneklerle proje kodlarınızı gözden geçirip daha sürdürülebilir bir mimari oluşturabilirsiniz.
Mini Check-List: Projen SOLID mi?
Bu örnekleri değerlendirirken kendi projenize şu soruları sorabilirsiniz: Bir sınıf tek bir işi mi yapıyor? Yeni bir kaynağı desteklemek için mevcut kodu değiştirmek zorunda kalıyor musunuz? Türeyen sınıflar beklenen davranışı bozuyor mu? Servisler gereksiz metodlara mı bağımlı? Birimi test etmek istediğinizde mock kullanabiliyor musunuz? Bu sorulara “hayır” cevabı verebildiğiniz her alan daha sürdürülebilir bir mimariye yaklaştığınız anlamına gelir.

Yorum Yap