Problem: hər şeyin hər şeyi dəyişə bildiyi model
Order entity-sinin public setter-ləri və OrderLine obyektləri siyahısı var. Qiymət servisi sətir əlavə edir, endirim job-u cəmləri redaktə edir, dəstək aləti statusu dəyişir. Bir ay sonra cəmi sətirlərə uyğun gəlməyən sifarişlər, həm göndərilmiş, həm ləğv edilmiş sifariş və mənfi miqdar meydana çıxır. Qaydaları tək bir yer tətbiq etmir, ona görə hər çağıran onları yadda saxlamalıdır və nəhayət biri unudur.
Domain-driven design (DDD) buna lüğət və struktur verir: ortaq dil, aydın sərhədli kontekstlər və consistency-ni qoruyan aggregate-lər. Bu məqalə taktiki nüvəyə, aggregate-ə fokuslanır və onu strateji mənzərənin içində yerləşdirir.
Ubiquitous language və bounded context
Ubiquitous language developer-lərin və domen mütəxəssislərinin ortaq lüğətidir və kodda görünür: biznes "sifariş təsdiqləndi" deyirsə, kodda setStatus(2) yox, order.confirm() olur. Fərqli insanlar üçün fərqli məna daşıyan terminlər xəbərdarlıq siqnalıdır.
Bounded context bir modelin və bir dilin ardıcıl tətbiq olunduğu sahədir. Katalog kontekstində "məhsul" təsvir və şəkillərə malikdir; inventar kontekstində miqdarı olan SKU-dur; billing-də vergi kodlu sətirdir. Bunlar təsadüfən eyni sözü bölüşən ayrı modellərdir. Hər yerdə bir Product sinfi məcbur etmək hər komandanın dəyişməyə qorxduğu şişmiş tip yaradır. Kontekstlər sərhədlərində açıq tərcümə ilə danışır (API, event, anti-corruption layer). Bounded context-lər servis sərhədləri üçün təbii namizədlərdir; bu, microservices boundaries məqaləsində müzakirə olunub.
Aggregate nədir
Aggregate dəyişiklik və consistency baxımından vahid kimi qəbul edilən obyektlər qrupudur. Onun:
- aggregate root-u var: yeganə giriş nöqtəsi; kənar kod yalnız root-a istinad saxlayır;
- invariant-ları var: sərhəd daxilində həmişə doğru olmalı qaydalar (məsələn, "sifariş cəmi sətirlərin cəminə bərabərdir" və "təsdiqlənmiş sifariş boş ola bilməz");
- transaction sərhədi var: bir aggregate bir transaction-da yüklənir, dəyişir və saxlanır.
Order (root)
├── OrderLine (qty > 0, price >= 0)
├── OrderLine
└── ShippingAddress (value object)
invariant-lar: total = sum(lines); təsdiq üçün ≥ 1 sətir;
SHIPPED-dən sonra dəyişiklik yoxdurInvariant-ları kodda qorumaq
Root setter yox, davranış açır və yanlış keçidləri rədd edir:
public class Order { private final OrderId id; private final List<OrderLine> lines = new ArrayList<>(); private OrderStatus status = OrderStatus.DRAFT; public void addLine(ProductId product, int qty, Money price) { requireDraft(); if (qty <= 0) throw new DomainException("qty must be positive"); lines.add(new OrderLine(product, qty, price)); } public void confirm() { requireDraft(); if (lines.isEmpty()) throw new DomainException("empty order"); status = OrderStatus.CONFIRMED; events.add(new OrderConfirmed(id, total())); } public Money total() { return lines.stream().map(OrderLine::subtotal) .reduce(Money.ZERO, Money::plus); } public List<OrderLine> lines() { return List.copyOf(lines); }}Cəm sətirlərdən törədilir, ona görə yayına bilməz. Kolleksiya yalnız oxumaq üçün açılır. Money kimi value object-lər (konstruktorunda validasiya olan Java record) yanlış vəziyyətləri ifadə etməyi çətinləşdirir.
Aggregate-ləri kiçik saxlayın, başqalarına id ilə istinad edin
Böyük aggregate-lər rəqabət və yavaş yükləmə yaradır: Customer indiyə qədər verilmiş hər Order-i saxlayırsa, müştərini yükləmək tarixçəni də yükləyir və iki paralel sifariş eyni obyektdə toqquşur. Dizayn qaydası: aggregate-ə yalnız bir transaction daxilində birlikdə ardıcıl olmalı olanı daxil edin. Qalan hər şey ayrı aggregate-dir.
Aggregate-lər bir-birinə obyekt istinadı ilə yox, identifikator ilə müraciət edir:
public class Order { private final CustomerId customerId; // Customer obyekti yox private final List<OrderLine> lines; // sərhəd daxilində}Bu, sərhədləri açıq saxlayır, təsadüfi naviqasiya və lazy-loading sürprizlərinin qarşısını alır və aggregate-ləri sonradan fərqli servislərdə və ya saxlancılarda yerləşdirməyi mümkün edir.
Aggregate-lər arası consistency: transaction-a bir dənə
Transaction bir aggregate-i dəyişir. Biznes qaydası bir neçəsini əhatə edəndə (sifarişi təsdiqləmək stoku rezerv etməlidir) aggregate-i və ya transaction-ı böyütməyin. Birinci aggregate domain event qeyd etsin, digər hissələr isə eventual consistency ilə reaksiya versin.
@Transactionalpublic void confirm(OrderId id) { Order order = orders.findById(id).orElseThrow(); order.confirm(); // OrderConfirmed qeyd edir orders.save(order); order.pullEvents().forEach(publisher::publish);}Event-lər keçmiş zamanda adlanır (OrderConfirmed) və handler-lərə lazım olanı daşıyır. Bir servis daxilində proses daxili handler ikinci transaction-da stoku rezerv edə bilər; servislər arasında isə event-i delivery semantics məqaləsində təsvir olunan transactional outbox ilə etibarlı yayımlayın. İkinci aggregate-in qaydaları dəyişikliyi rədd edə bilər, ona görə kompensasiya düşünün, məsələn, sifarişi səbəblə ləğv etmək.
Bir aggregate üzərində paralellik
Aggregate consistency vahidi olduğuna görə eyni zamanda kilidləmə vahididir. Eyni sifarişi redaktə edən iki istifadəçi bir-birinin işini səssizcə üstələməməlidir. @Version sütunlu optimistic locking istifadə edin: ikinci save uğursuz olur və yenidən cəhd edilə və ya bildirilə bilər. Aggregate-lər kiçikdirsə, aggregate səviyyəsində pessimistic kilid nadir hallarda lazım olur. Altda verilənlər bazasının nə təmin etdiyini isolation levels məqaləsində görün.
Tez-tez edilən səhvlər
- Anemik model: getter və setter-li entity-lər, qaydalar service-lərə səpələnib.
- "Hər şey əlaqəlidir" deyə bir nəhəng aggregate.
- Aggregate-lər arasında obyekt istinadı; böyük qrafların təsadüfən yüklənməsi.
- Vərdiş kimi bir transaction-da bir neçə aggregate dəyişmək, çatışmayan sərhədi gizlədir.
- Daxili dəyişən kolleksiyaları açıq vermək.
- Persistence məsələlərinin qaydalara sızıb-sızmadığını yoxlamadan verilənlər bazası entity-sini aggregate kimi istifadə etmək.
- Aggregate-ləri invariant və use case-lərdən yox, cədvəl diaqramından seçmək.
- DDD-ni modelləşdirmə intizamı yox, məcburi qovluq strukturu saymaq.
Kompromislər və nə vaxt lazım deyil
Aggregate-lər qaydaların zəngin olduğu və dəyişdiyi yerdə özünü doğruldur: qiymətləndirmə, rezervasiya, siyasət, state machine. Az invariant-lı sadə data idarəsi üçün validasiyalı sadə CRUD modeli daha asandır və tamamilə dürüstdür. Taktiki DDD domen mütəxəssisləri ilə vaxt da tələb edir; onsuz lüğət təxmin olunur. Onu əsas domen-ə tətbiq edin, istifadəçi profili saxlanması kimi generic hissələri sadə saxlayın.
Xərc domain və persistence modelləri arasında mapping-də, ayrıca read model tələb edən aggregate-lər arası sorğularda və komandanın öyrədilməsində görünür. Fayda qaydaların bir yerdə yaşaması və infrastruktursuz test oluna bilməsidir.
Repository və factory
Repository aggregate-i bütöv şəkildə yükləyir və saxlayır; hər aggregate tipi üçün bir repository kifayətdir, daxili obyektlər üçün ayrıca repository yoxdur. İnterfeys domain dilində olur (findById, save), sorğu mürəkkəbliyi isə read model-lərə verilir. Yaradılma məntiqi mürəkkəbdirsə, factory metodu (Order.create(...)) başlanğıc invariant-ların ilk andan doğru olmasını təmin edir, çünki yarımçıq obyekt heç vaxt mövcud olmur.
Kiçik, lakin vacib qeyd
Aggregate sərhədini bir dəfə çəkib unutmayın. Yeni tələb gələndə soruşun: bu qayda hansı invariant-ı qoruyur və onu qorumaq üçün hansı obyektlərin eyni transaction-da olması şərtdir? Cavab dəyişirsə, sərhəd də dəyişə bilər.
Yoxlama siyahısı
- Koddakı terminlər biznesin işlətdiyi terminlərlə üst-üstə düşür.
- Hər bounded context-in öz modeli var, sərhədlərdə açıq tərcümə ilə.
- Invariant-lar yazılıb və aggregate root tərəfindən tətbiq olunur.
- Qaydaları keçən public setter yoxdur.
- Aggregate-lər kiçikdir və bir-birinə id ilə istinad edir.
- Transaction-da bir aggregate dəyişir; qalanı event-lərlə.
- Optimistic locking paralel dəyişiklikləri qoruyur.
- Domain event-lər keçmiş zamanda adlanır və etibarlı yayımlanır.
Növbəti addım
- Clean architecture, praqmatik yanaşma: domain-i framework-dən müstəqil saxlamaq.
- Microservices boundaries: bounded context-dən servislərə.
- Solution architect roadmap: sistem miqyasında domen təfəkkürü.