paint-brush
Etki Alanı Odaklı Tasarıma Pratik Girişile@tobi
1,576 okumalar
1,576 okumalar

Etki Alanı Odaklı Tasarıma Pratik Giriş

ile Piotr Tobiasz15m2023/07/22
Read on Terminal Reader
Read this story w/o Javascript

Çok uzun; Okumak

Başlamak için DDD terminolojisi, yapı taşları ve diğer kavramlar.
featured image - Etki Alanı Odaklı Tasarıma Pratik Giriş
Piotr Tobiasz HackerNoon profile picture
0-item
1-item

Yazılım yazmanın yalnızca kod yazmakla ilgili olmadığını, belirli bir sorunu çözmekle ilgili olduğunu söylemek yenilikçi olmasa gerek. Sonunda çözümü uygulayanlar geliştiriciler olsa da, sorunun ne olduğunu ilk etapta tanımlayanlar geliştiriciler değildir. Bu görev, sorunun ne olduğunu, neden var olduğunu ve nasıl ele alınması gerektiğini açıklamak için süreçleri, riskleri ve sonuçları dikkate alan çeşitli iş adamları tarafından gerçekleştirilir. Etki alanı odaklı bir bağlamda bu iş adamlarına Etki Alanı Uzmanları adı verilir.


Mühendislik açısından bakıldığında, Etki Alanı Uzmanlarının değerli bir varlığa sahip olduğu görülmektedir: etki alanı hakkındaki bilgileri. Ancak bu bilgi nadiren ham haliyle paylaşılır. Bunun yerine genellikle geliştiricilerin anlayabilmesi ve uygulayabilmesi için gereksinimlere dönüştürülür. Bu yaklaşımın sorunu, iş adamlarının ve geliştiricilerin alan bilgilerinin farklılık gösterebilmesidir. Bu, sorunu tanımlayanlar ile sorunu çözmek için çalışanların bakış açılarının aynı hizada olmayabileceği ve yanlış anlamalara ve çatışmalara yol açabileceği anlamına gelir.


Peki çıkış yolu nedir? İş ve teknik kişilerin aynı dili ve terminolojiyi kullandığından emin olun.

Etki Alanına Dayalı Tasarım Nedir?

Etki Alanına Dayalı Tasarım (DDD), alan uzmanları ve teknik paydaşlar arasında ortak bir anlayış oluşturmanın ve yazılım çözümünü temel iş gereksinimleriyle uyumlu hale getirmenin önemini vurgulayan bir metodolojidir . Bu üst düzey, teknik olmayan bir tanım gibi görünüyor, ancak aynı zamanda daha geliştirici dostu bir şeye de ayrılabilir:


DDD, iş alanını modelleyerek oluşturulan, her yerde bulunan dili geliştirip kullanarak koddaki gerçek dünya kavramlarını ve yapısını temsil ediyor.


Hala tanıtılması gereken bazı terminolojiler var, dolayısıyla şu anda %100 net olmayabilir. En önemlisi, DDD'nin iş vizyonuyla uyumlu kod yazmaya ve yapılandırmaya olanak tanıyan araç ve etkinlikler sağlamasıdır. O zaman mesele sadece iletişim değil aynı zamanda ortak dili şekillendiren tasarım kararlarının alınmasıdır.

Terminoloji

DDD dünyasındaki en önemli terimin Etki Alanı olması sürpriz olmamalıdır. "Etki Alanına Dayalı Tasarım Öğrenme" kitabının yazarı Vlad Khononov bunu şöyle tanımlıyor:


İş alanı, bir şirketin ana faaliyet alanını tanımlar.


Bu, iş alanının şu şekilde de değerlendirilebileceği anlamına gelir:


  • Bir şirketin gelirinin ana kaynağı,
  • Şirketin en iyi bilinen özelliği,
  • Şirketin rakiplerinden daha iyi yaptığı her şey.


Alan ayrıca alt alanlara (daha spesifik faaliyet aralıkları) bölünebilir. Üç farklı alt alan adı türü olsa da en önemlisi çekirdektir . Şirketin iş avantajını nasıl elde ettiğini açıklar. Diğer ikisi kimlik doğrulama sistemleri veya dahili yönetici panelleri gibi daha yaygın, genel sorunlarla ilgilidir.


Bir şirketin iş alanına ilişkin derinlemesine bir anlayışa sahip olmak, Etki Alanı Odaklı Tasarımın avantajlarından tam olarak yararlanmak için kesinlikle çok önemlidir. Bu anlayışın en iyi kaynağı Domain Uzmanlarından başkası değildir. Bunlar, sorunları yazılımla çözülen kişilerdir; paydaşlar, çeşitli iş adamları ve hatta kullanıcılar. Bu, mühendislerin üzerinde çalıştıkları alan hakkında bilgisiz oldukları anlamına gelmez; daha ziyade Uzmanların, alan bilgisinin doğruluğunun kaynağı olduğu anlamına gelir. Geliştiriciler, Alan Adı Uzmanları ile birlikte çalışarak alan adı modellerinin doğru ve güncel kalmasını sağlayabilirler.


Bu, başka bir kritik ancak potansiyel olarak belirsiz terime yol açar: model . Eric Evans, DDD ile ilgili kitabında modeli şu şekilde anlatmaktadır:


Eldeki sorunun çözümüyle ilgili yönleri soyutlayan ve konu dışı ayrıntıları göz ardı eden, gerçekliğin bir yorumudur.


Vlad Khononov bu fikri daha bağdaştırılabilir terimlerle açıklamaya devam ediyor:


Model, gerçek dünyanın bir kopyası değil, gerçek dünya sistemlerini anlamamıza yardımcı olan bir insan yapısıdır.


Sonuç olarak model, alanın altında yatan karmaşıklığın anlaşılmasını ve iletilmesini kolaylaştıran bir iş kavramının veya sürecinin bir temsilidir.


Vlad, etki alanı modeli kavramını etkili bir şekilde göstermek için bir harita kullandı. Haritalar, topoğrafya, yollar veya sınırlar gibi yalnızca harita türüyle ilgili bilgilerin nasıl görüntülendiğinin mükemmel bir örneğidir. Tüm ayrıntıları aynı anda gösteren bir harita, bunaltıcı ve neredeyse işe yaramaz olacaktır. Etki alanı modelleri aşağıdaki gibi başka biçimlerde de bulunabilir:


  • Arka planda gerçekleşen tüm süreçlerin basitleştirilmiş halini temsil eden müşteri siparişleri,
  • Hazırlama sürecindeki her malzemeyi ve adımı listelemek yerine, menüde listelenen öğelerin nihai ürünler olduğu restoran menüleri,
  • Seyahat, otel vb. planlamaya çok daha fazla şey harcanmasına rağmen, rezervasyonu yapılan seyahatin yalnızca en kritik ayrıntıları vurguladığı seyahat rezervasyonları.


Etki Alanı Odaklı Tasarım (DDD) terminoloji bulmacasının son parçası Her Yerde Bulunan Dil'dir. Bir projede hem teknik hem de iş paydaşları tarafından kullanılan ortak dili ifade eder. Etki alanı modelinden türetilen iş alanını tanımlamak için ortak bir dile sahip olmak DDD'de çok önemlidir. Tüm ekip üyelerinin problem alanını, kavramlarını ve aralarındaki ilişkileri net bir şekilde anlamalarını sağlamaya yardımcı olur. Bu daha iyi hizalamaya yol açar ve yanlış anlama riskini azaltır. Yazılım çözümü, Ubiquitous Language'ı kullanarak temel iş gereksinimlerini doğru bir şekilde yansıtabilir ve bu da onu DDD'nin kritik bir bileşeni haline getirir.


Terminolojinin çoğu ele alındığında, alan odaklı tasarımın ne olduğunu anlamak daha kolay olacaktır. Şimdi gerçek nasıl yapıldığına, yani DDD'nin yapı taşlarına dalmanın zamanı geldi.

Yapı taşları

DDD yapı taşları, etkili ve verimli bir Etki Alanı Modeli oluşturmanın temelini oluşturur. Vlad Khononov Etki Alanı Modelini şu şekilde tanımlıyor:


Etki alanı modeli, etki alanının hem davranışı hem de verileri içeren bir nesne modelidir.


Etki Alanı Modeli çeşitli yapı taşlarından ve yapılardan oluşur. En önemlileri şunlardır:


  • Değer nesneleri,
  • Varlıklar,
  • Agregalar,
  • Etki alanı etkinlikleri,
  • Depolar,
  • Etki Alanı Hizmetleri.

Değer Nesneleri

Değer Nesneleri mevcut en temel yapı taşlarıdır. Bunlar bir dizi nitelik ve değerle tanımlanan nesnelerdir. Benzersiz bir tanımlayıcıları yoktur; değerleri kimliklerini tanımlar. Farklı değerlerin zaten farklı bir değer nesnesini temsil etmesi anlamında değişmezler. Değer Nesnelerinin örnekleri şunları içerir:


  • Parasal tutar,
  • Tarih aralığı,
  • Posta adresi.


Python'da basit bir Değer Nesnesinin nasıl uygulanabileceği aşağıda açıklanmıştır:


 from pydantic import BaseModel class Address(BaseModel):  """Customer address."""  country: str  city: str  street: str  house_number: str  class Config:    frozen = True


Bu, iki adresi karşılaştırmak için eşitlik operatörünü ( == ) kullanmanın, yalnızca her iki nesneye de tam olarak aynı değerler atanmışsa True döndüreceği anlamına gelir.

Varlıklar

Varlıklar bir sonraki yapı taşı türüdür. Varlıklar, etki alanındaki bir kişi veya sipariş gibi farklı bir kimliğe sahip bireysel nesneleri temsil eder. Verileri depolamaları açısından Değer Nesnelerine benzerler, ancak nitelikleri değişebilir ve değişmesi beklenir ve bu nedenle benzersiz bir tanımlayıcıya ihtiyaç duyarlar. Siparişler ve kişisel bilgiler Varlıkların yalnızca iki basit örneğidir:


 import uuid from pydantic import BaseModel, Field from practical_ddd.building_blocks.value_objects import Address class Person(BaseModel):  """Personal data."""  id: uuid.UUID = Field(default_factory=uuid.uuid4)  first_name: str  last_name: str  address: Address class Order(BaseModel):  """Customer order."""  id: uuid.UUID = Field(default_factory=uuid.uuid4)  description: str  value: float


Örneklerin değerleri her iki durumda da değiştirilebilir olduğundan, UUID olabilecek bir kimliğe ihtiyaçları vardır. Daha da önemlisi, çoğu durumda varlıkların doğrudan değil, bir bütün olarak yönetilmesi amaçlanmaktadır.

Agrega

Bir Toplama, değiştirilebilir olduğundan ve benzersiz bir tanımlayıcı gerektirdiğinden bir varlık türüdür. Bununla birlikte, birincil sorumluluğu verileri depolamak değil, bir dizi ilgili nesneyi (Varlıklar ve Değer Nesneleri) tek bir tutarlılık birimi olarak gruplandırmaktır. Toplama, iç durumunu kapsayan ve tüm grubun tutarlılığını sağlamak için değişmezleri zorlayan iyi tanımlanmış bir sınıra sahip kök nesnedir. Toplamalar, nesnelerin kendisi yerine nesneler arasındaki ilişkilere odaklanarak alan hakkında daha doğal ve sezgisel bir şekilde akıl yürütmeye olanak tanır.


Önceki örneklerden hareketle, bir toplam müşteri olarak temsil edilebilir:


 import uuid from pydantic import BaseModel, Field from practical_ddd.building_blocks.entities import Person, Order from practical_ddd.building_blocks.value_objects import Address class Customer(BaseModel):  """Customer aggregate.  Manages personal information as well as orders.  """  id: uuid.UUID = Field(default_factory=uuid.uuid4)  person: Person  orders: list[Order] = Field(default_factory=list)  def change_address(self, new_address: Address) -> None:    self.person.address = new_address  def add_order(self, order: Order) -> None:    if self.total_value + order.value > 10000:      raise ValueError("Order cannot have value higher than 10000")    self.orders.append(order)  def remove_order(self, order_id: uuid.UUID) -> None:    order = next((order for order in self.orders if order.id == order_id), None)    if order is None:      raise IndexError("Order not found")    self.orders.remove(order)  @property  def total_value(self) -> float:    return sum(order.value for order in self.orders)


Müşterinin kişisel verileri doğrudan bağlantılıdır ve tüm siparişleri saklar. Bunun da ötesinde, toplu, kişinin adresinin yönetilmesinin yanı sıra siparişlerin eklenmesi ve kaldırılması için bir arayüz ortaya çıkarır. Bunun nedeni, toplamın durumunun yalnızca ilgili yöntemlerin çalıştırılmasıyla değiştirilebilmesidir.


Önceki örnek nispeten basit olsa da, yalnızca bir kısıtlamayla (sipariş değeri 10000'den büyük olamaz), DDD yapı taşlarının kullanımını ve bunların ilişkilerini göstermelidir. Gerçek sistemlerde kümeler genellikle daha karmaşıktır; daha fazla kısıtlama, sınır ve muhtemelen daha fazla ilişki içerir. Sonuçta onların varlığı bu karmaşıklığı yönetmekten ibarettir. Ek olarak, gerçek dünyada, toplamalar genellikle veritabanı gibi bir veri deposunda kalıcı hale getirilir. Depo modelinin devreye girdiği yer burasıdır.

Depo

Toplamın durum değişikliklerinin tümü, tek bir atomik işlemde işlemsel olarak gerçekleştirilmelidir. Ancak "kendini sürdürmek" topluluğun sorumluluğunda değildir. Depo modeli , veri depolama ve alma ayrıntılarını soyutlamaya ve bunun yerine daha yüksek bir soyutlama düzeyinde toplamalarla çalışmaya olanak tanır. Basitçe ifade etmek gerekirse, bir depo, toplama ile veri depolama arasında bir katman olarak düşünülebilir. Bir JSON dosyası, oldukça basit bir dosyadır. böyle bir mağazanın basit bir örneği: Müşteri topluluğu, JSON dosyaları üzerinde çalışan bir depoya sahip olabilir:


 import json import uuid from practical_ddd.building_blocks.aggregates import Customer class CustomerJSONRepository:  """Customer repository operating on JSON files."""  def __init__(self, path: str) -> None:    self.path = path  def get(self, customer_id: uuid.UUID) -> Customer:    with open(self.path, "r") as file:      database = json.load(file)      customer = database["customers"].get(str(customer_id))      if customer is None:        raise IndexError("Customer not found")      person = database["persons"][str(customer["person"])]      orders = [database["orders"][order_id] for order_id in customer["orders"]]    return Customer(      id=customer["id"],      person=person,      orders=orders,    )  def save(self, customer: Customer) -> None:    with open(self.path, "r+") as file:      database = json.load(file)      # Save customer      database["customers"][str(customer.id)] = {        "id": customer.id,        "person": customer.person.id,        "orders": [o.id for o in customer.orders],      }      # Save person      database["persons"][str(customer.person.id)] = customer.person.dict()      # Save orders      for order in customer.orders:        database["orders"][str(order.id)] = order.dict()      file.seek(0)      json.dump(database, file, indent=4, default=str)


Elbette bu sınıf çok daha fazlasını yapabilir (ve belki de yapmalıdır), ancak mükemmel, çok işlevli bir ORM olması amaçlanmamıştır. Bu durumda müşteri kümesinin JSON dosyasında depolanması ve alınması olan depo sorumlulukları hakkında bir fikir vermelidir. Ayrıca havuzun toplamla ilişkili varlıkları nasıl ele aldığına dikkat etmek önemlidir. Kişisel veriler ve siparişler müşteri yaşam döngüsüyle sıkı bir şekilde bağlantılı olduğundan, bunların toplam işlenirken tam olarak yönetilmesi gerekir.

Alan Adı Hizmeti

Göz önünde bulundurulması gereken diğer bir durum ise, topluluğa veya onun varlıklarından veya değer nesnelerinden herhangi birine uymayan bir iş mantığının mevcut olmasıdır. Birden fazla toplama veya veri deposunun durumuna bağlı bir mantık olabilir. Bu gibi durumlarda Domain Service olarak bilinen bir yapı işinize yarayabilir. Etki Alanı Hizmeti, örneğin depoyu kullanarak toplamaları yönetebilmeli ve ardından toplama ait olmayan etki alanı mantığını depolayabilmelidir. Örneğin, bir müşteri çok fazla sipariş kaybetmemek için mantığa ihtiyaç duyabilir:


 import uuid from typing import Protocol from practical_ddd.building_blocks.aggregates import Customer class CustomerRepository(Protocol):  """Customer repository interface."""  def get(self, customer_id: uuid.UUID) -> Customer:    ...  def save(self, customer: Customer) -> None:    ... class CustomerService:  """Customer service."""  def __init__(self, repository: CustomerRepository) -> None:    self.repository = repository  def get_customer(self, customer_id: uuid.UUID) -> Customer | None:    try:      return self.repository.get(customer_id)    except IndexError:      return None  def save_customer(self, customer: Customer) -> None:    existing_customer = self.get_customer(customer.id)    # If customer is already in the database and has more than 2 orders,    # he cannot end up with half of them after a single save.    if (      existing_customer is not None      and len(existing_customer.orders) > 2      and len(customer.orders) < (len(existing_customer.orders) / 2)    ):      raise ValueError(        "Customer cannot lose more than half of his orders upon single save!"      )    self.repository.save(customer)


Aggregate, JSON dosyası hakkında hiçbir bilgiye sahip olmadığından, durumunun JSON dosyasındaki durumdan ne kadar farklı olduğundan emin olamaz. Bu nedenle karşılaştırma mantığının Etki Alanı Hizmetine dahil edilmesi gerekir. Etki Alanı Hizmetinin depo soyutlaması ile çalışması gerektiğine dikkat etmek de önemlidir. Bu, bağımlılık enjeksiyonunu kullanarak somut uygulamayı alternatif bir uygulamayla değiştirmeyi kolaylaştırır.

Hepsini bir araya koy

Artık tüm parçalar ele alındığından, artık bir çalışma programı olarak görülebilirler:


 import uuid from practical_ddd.building_blocks import aggregates, entities, value_objects from practical_ddd.database.repository import CustomerJSONRepository from practical_ddd.service import CustomerService # Initialize domain service with json repository srv = CustomerService(repository=CustomerJSONRepository("test.json")) # Create a new customer customer = aggregates.Customer(  person=entities.Person(    first_name="Peter",    last_name="Tobias",    address=value_objects.Address(      country="Germany",      city="Berlin",      street="Postdamer Platz",      house_number="2/3",    ),  ), ) srv.save_customer(customer) # Add orders to existing customer customer = srv.get_customer(uuid.UUID("a32dd73a-6c1b-4581-b1d3-2a1247320938")) assert customer is not None customer.add_order(entities.Order(description="Order 1", value=10)) customer.add_order(entities.Order(description="Order 2", value=210)) customer.add_order(entities.Order(description="Order 3", value=3210)) srv.save_customer(customer) # Remove orders from existing customer # If there are only 3 orders, it's gonna fail customer = srv.get_customer(uuid.UUID("a32dd73a-6c1b-4581-b1d3-2a1247320938")) assert customer is not None customer.remove_order(uuid.UUID("0f3c0a7f-67fd-4309-8ca2-d007ac003b69")) customer.remove_order(uuid.UUID("a4fd7648-4ea3-414a-a344-56082e00d2f9")) srv.save_customer(customer)


Her şeyin sorumlulukları ve sınırları vardır. Aggregate, varlıklarını ve değer nesnelerini yönetmekten ve kısıtlamalarını uygulamaktan sorumludur. Etki Alanı Hizmeti, verileri JSON dosyasında kalıcı hale getirmek ve ek etki alanı sınırlarını zorunlu kılmak için eklenen JSON deposunu kullanır. Sonuçta her bileşenin belirlenen alan içerisinde ayrı bir işlevi ve önemi vardır.

Özet

Etki Alanı Odaklı Tasarım hiç şüphesiz anlaşılması karmaşık bir fikirdir. İş alanına güçlü bir vurgu yaparak yazılım ekiplerinin en zorlu iş sorunlarının üstesinden gelmesine yardımcı olacak uygulamalar, modeller ve araçlar sağlar. Ancak DDD, bir dizi yapı taşından daha fazlasıdır. Teknik ve iş paydaşları arasında işbirliği ve iletişim gerektiren bir zihniyettir. Her yerde bulunan dil aracılığıyla ifade edilen etki alanına ilişkin ortak bir anlayış, bir DDD projesinin başarısı için kritik öneme sahiptir. İyi uygulandığında DDD, işin ihtiyaçlarına daha iyi uyum sağlayan ve karmaşık sorunları çözmede daha etkili bir yazılıma yol açabilir.

Son Söz ve Sonraki Adımlar

Bu makalenin asla "DDD: Sıfırdan Kahramana" gibi bir amacı yoktu, daha ziyade DDD evrenine bir giriş niteliğindeydi. Etki Alanına Dayalı Tasarımın en önemli kavramlarını çok basit ve pratik bir şekilde göstermek istedim. Etki Alanına Dayalı Tasarım öğrenmenin programlama uzmanlığını artırmanın mükemmel bir yolu olduğuna inanıyorum. Ancak bunu çok sık duymazsınız - en azından "11 ÇILGIN JavaScript ipucu ve püf noktası - bir başlık 🧵" kadar.


Her halükarda, bunlardan herhangi birini ilginç bulduysanız, bu makaleyi yazmam için bana ilham veren kitap ve makaleleri bulmak için kaynaklar bölümüne bakabilirsiniz. Bu girişin kapsamı dışında olduklarını düşündüğüm için ele almadığım bazı kavramlar var, ancak araştırmaya değer:


  • Sınırlı Bağlamlar
  • Etki Alanı Etkinlikleri
  • Etkinlik Fırtınası


Bunları şüphesiz aşağıda listelenen kaynaklarda bulacaksınız.

Kaynaklar

Makalede kullanılan kod örneklerini burada bulabilirsiniz: link .


Vlad Khononov'dan Etki Alanı Odaklı Tasarımı Öğrenmek . Benim için büyük bir ilham kaynağı olan muhteşem bir kitap. Bu makalede tartışılan tüm kavramları daha derinlemesine açıklamaktadır.


Python'da Mimari Desenler , Harry Percival ve Bob Gregory. Kitabı neredeyse iki yıl önce okudum ve bir geliştirici olarak üzerimde önemli bir etki yarattı. Bu makaleyi yazarken ona geri döndüm ve bana bir kez daha yardımcı oldu.


Przemysław Górecki tarafından Python'da DDD . Bu blogu makaleyi yazmamın sonlarına doğru keşfettim, ancak son derece profesyonel olması nedeniyle ilgimi çekti. Eğlenceli gerçek: Przemysław ile aynı şirkette çalışıyordum ve bundan tamamen habersizdim.


Burada da yayınlandı.