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 (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.
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:
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:
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.
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 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:
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 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.
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.
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.
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.
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.
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.
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:
Bunları şüphesiz aşağıda listelenen kaynaklarda bulacaksınız.
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ı.