ABP Framework ile Ad?m Ad?m Yaz?l?m Geli?tirme ? B?l?m 4: Domain Layer Geli?tirme
Domain Layer (Domain Katman?), uygulaman?z?n ?ekirdek i? mant???n? ve kurallar?n? bar?nd?ran en ?nemli katman?d?r. Bu katman, veritaban? veya UI gibi d?? detaylardan ba??ms?zd?r ve uygulaman?n i?leyi?inin kalbidir. Bu b?l?mde, domain katman?n?z? zenginle?tirecek anahtar kavramlar? inceleyece?iz.
Domain Service’ler Olu?turma
Domain Service (Domain Servisi), tek bir entity’ye ait olmayan, ancak birden fazla entity’yi veya aggregate root’u i?eren i? mant??? i?in kullan?l?r. Genellikle bir fiili veya bir i?lemi temsil eder.
?rnek: ?ki kitab? birle?tiren veya bir kitab? ba?ka bir kategoriye ta??yan bir domain servisi.
C#
using System;
using System.Threading.Tasks;
using Volo.Abp.Domain.Services;
using Volo.Abp.Domain.Repositories;
namespace MyProject.Books
{
public class BookManager : DomainService
{
private readonly IRepository<Book, Guid> _bookRepository;
public BookManager(IRepository<Book, Guid> bookRepository)
{
_bookRepository = bookRepository;
}
public async Task ChangeBookTypeAsync(Book book, BookType newType)
{
if (book.Type == newType)
{
return; // T?r zaten ayn?ysa i?lem yapma
}
// ?? kural?: Belirli bir t?rden di?erine ge?i? k?s?tlamas? olabilir
if (book.Type == BookType.Horror && newType == BookType.Adventure)
{
throw new BusinessException(MyProjectDomainErrorCodes.HorrorToAdventureNotAllowed);
}
book.Type = newType;
await _bookRepository.UpdateAsync(book);
}
// Ba?ka domain i?lemleri...
}
}
DomainServices?n?f?ndan t?reyerek servislerinizdeki ba??ml?l?k enjeksiyonunu kolayla?t?r?rs?n?z.- Domain servisleri genellikle
IRepositoryaray?zleri arac?l???yla entity’lere eri?ir.
Business Logic Implementasyonu
Domain katman?nda i? mant???n? implemente ederken, entity’lerin kendi i? tutarl?l?klar?n? korumalar?n? sa?lamak ?nemlidir. ?? kurallar? m?mk?n oldu?unca entity’lerin kendi metotlar? i?ine yerle?tirilmelidir. E?er i? kural? birden fazla entity’yi ilgilendiriyorsa, o zaman bir domain servisi kullan?labilir.
?rnek: Book entity’si i?inde bir i? kural?:
C#
public class Book : AggregateRoot<Guid>
{
public string Name { get; private set; } // Private setter ile kontroll? de?i?im
public BookType Type { get; set; }
public DateTime PublishDate { get; set; }
public float Price { get; set; }
// Yap?c? metod
private Book() { /* EF Core i?in bo? yap?c? metod */ }
public Book(Guid id, string name, BookType type, DateTime publishDate, float price)
: base(id)
{
SetName(name); // ?? kural? uygulama
Type = type;
PublishDate = publishDate;
Price = price;
}
public void SetName(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentException("Book name cannot be empty.", nameof(name));
}
if (name.Length > 128)
{
throw new ArgumentException("Book name cannot exceed 128 characters.", nameof(name));
}
Name = name;
}
}
Domain Event’ler
Domain Event (Domain Olay?), domain i?indeki bir ?eyin oldu?unu ve di?er domain bile?enlerinin ilgilenebilece?i bir olay? temsil eder. Domain olaylar?, farkl? aggregate root’lar veya mod?ller aras?nda gev?ek ba?l? ileti?imi sa?lamak i?in kullan?l?r.
ABP Framework, Event Bus (Bkz. B?l?m 11) mekanizmas? ile domain olaylar?n? kolayca yay?nlaman?za ve dinlemenize olanak tan?r.
- Domain Event Tan?mlama:
Volo.Abp.EventBus.Distributed.IDistributedEventveyaVolo.Abp.EventBus.Local.ILocalEventaray?zlerini implemente eden bir DTO s?n?f?. C#public class BookCreatedEto // ETO: Event Transfer Object { public Guid BookId { get; set; } public string BookName { get; set; } // Di?er ilgili veriler } - Olay Yay?nlama: Bir domain servisi veya entity i?erisinde
IDomainEventBusservisi arac?l???yla olay yay?nlan?r. C#// BookManager i?inde public async Task CreateBookAsync(string name, BookType type, ...) { // Kitap olu?turma mant???... var book = new Book(...); await _bookRepository.InsertAsync(book); // Olay? yay?nla await _eventBus.PublishAsync(new BookCreatedEto { BookId = book.Id, BookName = book.Name }); } - Olay Dinleme (Handler):
IEventHandler<TEvent>aray?z?n? implemente eden bir s?n?f. C#public class BookCreatedEventHandler : IEventHandler<BookCreatedEto>, ITransientDependency { public async Task HandleEventAsync(BookCreatedEto eventData) { // Olay meydana geldi?inde yap?lacak i?lemler Console.WriteLine($"Yeni kitap olu?turuldu: {eventData.BookName} (ID: {eventData.BookId})"); // ?rne?in, bir loglama, bildirim g?nderme veya ba?ka bir i?lemi tetikleme await Task.CompletedTask; } }
Validation Kurallar?
Domain katman?nda i? kurallar?n? (business rules) ve validasyonlar? uygulamak, uygulaman?z?n veri b?t?nl???n? sa?lar. ABP, FluentValidation gibi pop?ler k?t?phanelerle entegre edilebilir veya basit if bloklar? ve BusinessException ile kendi validasyonlar?n?z? uygulayabilirsiniz.
C#
using Volo.Abp; // BusinessException i?in
public class Book : AggregateRoot<Guid>
{
public void SetPrice(float price)
{
if (price <= 0)
{
throw new BusinessException(MyProjectDomainErrorCodes.BookPriceMustBePositive);
}
Price = price;
}
}
Value Objects Kullan?m?
Value Object (De?er Nesnesi), kimli?i olmayan, sadece de?erleri ile tan?mlanan bir nesne t?r?d?r. ?ki de?er nesnesi, de?erleri ayn?ysa e?ittir. ?rnek olarak bir adres, para birimi veya renk gibi kavramlar de?er nesneleri olabilir.
ABP’de de?er nesneleri i?in ?zel bir taban s?n?f yoktur, ancak .NET record tipleri veya ?zel bir s?n?f ile implemente edilebilirler.
C#
// ?rnek bir Value Object
public class Address
{
public string Street { get; private set; }
public string City { get; private set; }
public string ZipCode { get; private set; }
public Address(string street, string city, string zipCode)
{
Street = street;
City = city;
ZipCode = zipCode;
}
// De?er kar??la?t?rmas? i?in e?itlik (Equality) override edilmeli
public override bool Equals(object obj)
{
if (obj == null || GetType() != obj.GetType())
{
return false;
}
var other = (Address)obj;
return Street == other.Street && City == other.City && ZipCode == other.ZipCode;
}
public override int GetHashCode()
{
return HashCode.Combine(Street, City, ZipCode);
}
}
Bu de?er nesnesini bir entity i?inde kullanabilirsiniz:
C#
public class Author : AggregateRoot<Guid>
{
public string Name { get; set; }
public Address HomeAddress { get; set; } // Adres bir de?er nesnesi
}
Specification Pattern Uygulamas?
Specification Pattern (?artname Deseni), i? kurallar?n? sorgu mant???ndan ay?rmak i?in kullan?l?r. Belirli bir i? kural?n? kar??layan entity’leri filtrelemek i?in esnek ve yeniden kullan?labilir yollar sa?lar.
ABP, do?rudan bir “Specification” yap?s? sa?lamaz, ancak Expression kullanarak veya ?zel IQueryable uzant?lar? yazarak bu deseni uygulayabilirsiniz.
C#
using System;
using System.Linq.Expressions;
using Volo.Abp.Domain.Entities;
// ?rnek bir spesifikasyon (bir s?n?f i?inde de tutulabilir)
public static class BookSpecifications
{
public static Expression<Func<Book, bool>> IsScienceFiction()
{
return book => book.Type == BookType.ScienceFiction;
}
public static Expression<Func<Book, bool>> PublishedBefore(DateTime date)
{
return book => book.PublishDate < date;
}
}
Bu spesifikasyonlar repository sorgular?nda kullan?labilir:
C#
var scienceFictionBooks = await _bookRepository.GetListAsync(
BookSpecifications.IsScienceFiction()
);
Repository Pattern ve IRepository Interface’i
Repository Pattern (Depo Deseni), veri eri?im mant???n? domain katman?ndan soyutlar. Entity’lerin veri depolama mekanizmas?ndan habersiz olmas?n? sa?lar. ABP Framework, varsay?lan olarak EF Core tabanl? bir repository implementasyonu sunar.
ABP’de IRepository<TEntity, TKey> aray?z? temel repository operasyonlar?n? (CRUD, sorgulama) sa?lar:
C#
using Volo.Abp.Domain.Repositories;
using MyProject.Books;
namespace MyProject.Domain.Repositories
{
public interface IBookRepository : IRepository<Book, Guid>
{
// ?zel sorgular buraya eklenebilir
Task<Book> FindByNameAsync(string name);
Task<List<Book>> GetBooksByTypeAsync(BookType type);
}
}
ABP, IRepository aray?z?n? otomatik olarak implemente eder, bu nedenle ?o?u durumda ?zel bir implementasyon yazman?za gerek kalmaz. Ancak, daha karma??k veya ?zel sorgulara ihtiyac?n?z varsa, kendi repository implementasyonunuzu yazabilirsiniz.
C#
// BookRepository implementasyonu (TodoApp.EntityFrameworkCore projesinde)
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
using MyProject.Books;
namespace MyProject.EntityFrameworkCore.Repositories
{
public class EfCoreBookRepository : EfCoreRepository<MyProjectDbContext, Book, Guid>, IBookRepository
{
public EfCoreBookRepository(IDbContextProvider<MyProjectDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
public async Task<Book> FindByNameAsync(string name)
{
return await GetQueryable()
.Where(b => b.Name == name)
.FirstOrDefaultAsync();
}
public async Task<List<Book>> GetBooksByTypeAsync(BookType type)
{
return await GetQueryable()
.Where(b => b.Type == type)
.ToListAsync();
}
}
}
Bu b?l?m, domain katman?n?z? g??l? ve s?rd?r?lebilir bir ?ekilde in?a etmeniz i?in gerekli temel yap? ta?lar?n? sunar.