Varlık Yöntemleri Çağrılarında DDD Enjeksiyon Hizmetleri


11

Kısa soru biçimi

Varlık yöntemi çağrılarına hizmet enjekte etmek DDD ve OOP'un en iyi uygulamaları dahilinde mi?

Uzun format örneği

Diyelim ki Toplama Kökü olarak da çalışan, Sipariş adı verilen bir Etki Alanı Varlığına sahip olduğumuz DDD'de klasik Order-LineItems davamız var ve Varlık sadece Değer Nesnelerinden değil, aynı zamanda bir Satır Öğesi koleksiyonundan oluşuyor Varlıkları.

Uygulamamızda akıcı bir sözdizimi istediğimizi varsayalım, böylece böyle bir şey yapabiliriz ( getLineItemsyöntemi çağırdığımız 2. satırdaki sözdizimini not ederek ):

$order = $orderService->getOrderByID($orderID);
foreach($order->getLineItems($orderService) as $lineItem) {
  ...
}

OrderEntity'ye herhangi bir LineItemRepository enjekte etmek istemiyoruz, çünkü bu aklıma gelen birkaç prensibin ihlalidir. Ancak, sözdiziminin akıcılığı gerçekten istediğimiz bir şeydir, çünkü test etmenin yanı sıra okunması ve bakımı kolaydır.

Yöntemi belirterek, şu kodu göz önünde bulundurun getLineItemsiçinde OrderEntity:

interface IOrderService {
    public function getOrderByID($orderID) : OrderEntity;
    public function getLineItems(OrderEntity $orderEntity) : LineItemCollection;
}

class OrderService implements IOrderService {
    private $orderRepository;
    private $lineItemRepository;

    public function __construct(IOrderRepository $orderRepository, ILineItemRepository $lineItemRepository) {
        $this->orderRepository = $orderRepository;
        $this->lineItemRepository = $lineItemRepository;
    }

    public function getOrderByID($orderID) : OrderEntity {
        return $this->orderRepository->getByID($orderID);
    }

    public function getLineItems(OrderEntity $orderEntity) : LineItemCollection {
        return $this->lineItemRepository->getLineItemsByOrderID($orderEntity->ID());
    }
}

class OrderEntity {
    private $ID;
    private $lineItems;

    public function getLineItems(IOrderServiceInternal $orderService) {
        if(!is_null($this->lineItems)) {
            $this->lineItems = $orderService->getLineItems($this);
        }
        return $this->lineItems;
    }
}

DDD ve OOP'un temel ilkelerini ihlal etmeden, Akıcı sözdizimini Uygulamanın kabul edilen yolu bu mudur? Bana göre iyi görünüyor, çünkü yalnızca hizmet katmanını açığa çıkardık, altyapı katmanını (hizmetin içinde iç içe) değil

Yanıtlar:


9

Bu var tamamen iyi bir varlık çağrısında Alan hizmetini geçmek. Diyelim ki, müşteri türüne bağlı olabilecek karmaşık bir algoritma ile fatura toplamını hesaplamamız gerekiyor. İşte böyle görünebilir:

class Invoice
{
    private $currency;
    private $customerId;

    public function __construct()
    {
    }

    public function sum(InvoiceCalculator $calculator)
    {
        $sum =
            new SumRecord(
                $calculator->calculate($this)
            )
        ;

        if ($sum->isZero()) {
            $this->events->add(new ZeroSumCalculated());
        }

        return $sum;
    }
}

Başka bir yaklaşım da etki alanı hizmetinde bulunan bir iş mantığını etki alanı olayları aracılığıyla ayırmaktır . Bu yaklaşımın yalnızca farklı uygulama hizmetlerini, ancak aynı veritabanı işlem kapsamını kapsadığını unutmayın.

Üçüncü yaklaşım, lehte olduğum yaklaşımdır: kendimi bir etki alanı hizmeti kullanarak bulursam, muhtemelen bazı etki alanı kavramlarını kaçırdığım anlamına gelir, çünkü kavramlarımı fiillerle değil , isimlerle modellenirim . İdeal olarak, bir alan adı hizmetine hiç ihtiyacım yok ve tüm iş mantığımın iyi bir kısmı dekoratörlerdedir .


6

Burada bazı cevapları okumak için şok oldum.

Bazı iş hesaplamalarını devretmek için alan adı hizmetlerini DDD'deki varlık yöntemlerine geçirmek mükemmel şekilde geçerlidir. Örnek olarak, bazı iş mantığı yapmak ve bir etkinlik oluşturmak için toplam kökünüzün (varlık) http üzerinden harici bir kaynağa erişmesi gerektiğini düşünün. Hizmeti kuruluşun iş yöntemiyle enjekte etmezseniz, başka nasıl yaparsınız? Varlığınızın içinde bir http istemcisi başlatır mısınız? Kulağa korkunç bir fikir gibi geliyor.

Yanlış olan, yapıcıları aracılığıyla toplu olarak hizmet enjekte etmektir. Ama bir iş yöntemiyle sorun yok ve tamamen normal.


1
Verdiğiniz dava neden bir Alan Adı Hizmetinin sorumluluğunda değil?
e_i_pi

1
bir Alan Adı Hizmetidir, ancak işletme yöntemine eklenir. Uygulama katmanı sadece bir orkestratör,
diegosasw

DDD'de deneyimli değilim ancak Uygulama Hizmeti'nden Etki Alanı Hizmeti çağrılmamalıdır ve Etki Alanı Hizmeti doğrulaması bu Uygulama Hizmeti aracılığıyla Varlık yöntemlerini çağırmaya devam ettikten sonra? Etki alanı hizmeti depo üzerinden veritabanı çağrısı çalıştırdığından, projemde aynı sorunla karşı karşıyayım ... Bunun iyi olup olmadığını bilmiyorum.
Muflix

Etki alanı hizmeti düzenlemelidir, daha sonra uygulamadan çağırırsanız, bir şekilde yanıtı işlediğiniz ve ardından onunla bir şeyler yaptığınız anlamına gelir. Belki de iş mantığı gibi gelebilir. Öyleyse, Etki Alanı katmanına aittir ve uygulama daha sonra bağımlılığı çözer ve bunu toplu olarak enjekte eder. Etki alanı hizmeti, uygulama isabet veritabanının altyapı katmanına (arabirim / sözleşme değil, yalnızca uygulama) ait olması gereken bir havuz enjekte etmiş olabilir. Her yerde kullandığınız dili açıklıyorsa, etki alanına aittir.
diegosasw

5

Varlık yöntemi çağrılarına hizmet enjekte etmek DDD ve OOP'un en iyi uygulamaları dahilinde mi?

Hayır, alan adı katmanınıza hiçbir şey enjekte etmemelisiniz (buna varlıklar, değer nesneleri, fabrikalar ve alan hizmetleri dahildir). Bu katman herhangi bir çerçeveden, 3. taraf kitaplıklardan veya teknolojiden bağımsız olmalı ve herhangi bir IO çağrısı yapmamalıdır.

$order->getLineItems($orderService)

Agrega'nın sipariş kalemlerini iade etmek için kendisinden başka bir şeye ihtiyacı olmaması gerektiği için bu yanlıştır. Tüm Agrega zaten yöntem çağrısından önce yüklenmelidir. Bunun tembel yüklenmesi gerektiğini düşünüyorsanız, iki olasılık vardır:

  1. Toplu sınırlarınız yanlış, çok büyük.

  2. Bu kullanıcı tabanında Toplama'yı yalnızca okumak için kullanırsınız. En iyi çözüm yazma modelini okuma modelinden ayırmaktır (yani CQRS kullanın ). Bu temiz mimaride Toplama'yı değil, okuma modelini sorgulamanıza izin verilir.


Doğrulama için veritabanı çağrısı gerekiyorsa, ben uygulama hizmetinde aramak ve etki alanı hizmetine veya doğrudan birleştirilmiş kök içine bir sonucu yerine etki alanı hizmetine enjekte gerekir?
Muflix

1
@Muflix evet, doğru
Constantin Galbenu

3

DDD taktik kalıplarında ana fikir: Uygulama, bir toplu kök üzerinde hareket ederek uygulamadaki tüm verilere erişir. Bu, yalnızca etki alanı modelinin dışından erişilebilen varlıkların toplu kökler olduğu anlamına gelir.

Sipariş toplama kökü asla, koleksiyonunu değiştirmenize izin verecek olan lineitem koleksiyonuna bir referans vermez ve onu değiştirmenize izin verecek herhangi bir satır öğesine bir referans koleksiyonu vermez. Sipariş toplamını değiştirmek isterseniz, hollywood prensibi geçerlidir: "Söyle, sorma".

Tekrar değerleri agrega içinden değerleri doğal değişmez olduğu için, ince; kopyanızı değiştirerek verilerimi değiştiremezsiniz.

Bir etki alanı hizmetini bağımsız değişken olarak kullanmak, toplamanın doğru değerleri sağlamasına yardımcı olmak için yapılması oldukça makul bir şeydir.

Topluluğun içindeki verilere erişim sağlamak için normalde bir etki alanı hizmeti kullanmazsınız çünkü toplamanın buna önceden erişimi olması gerekir.

$order = $orderService->getOrderByID($orderID);
foreach($order->getLineItems($orderService) as $lineItem) {
  ...
}

Bu siparişin satır öğesi değerleri koleksiyonuna erişmeye çalışıyorsak, bu yazım gariptir. Daha doğal yazım

$order = $orderService->getOrderByID($orderID);
foreach($order->getLineItems() as $lineItem) {
  ...
}

Elbette, bu, satır öğelerinin önceden yüklendiğini varsayar.

Genel model, agreganın yükünün, belirli kullanım durumu için gereken tüm durumu içermesidir. Başka bir deyişle, aynı toplamayı yüklemenin birkaç farklı yolu olabilir ; depo yöntemleriniz amaca uygundur .

Bu yaklaşım, orijinal Evans'ta bulacağınız bir şey değildir, burada bir toplamanın kendisiyle ilişkili tek bir veri modeline sahip olacağını varsaymıştır. CQRS'den daha doğal olarak düşer.


Bunun için teşekkürler. Şimdi "kırmızı kitabın" yarısını okudum ve altyapı katmanında Hollywood Prensibini doğru bir şekilde uygulamaktan ilk zevkimi aldım. Tüm bu cevapları tekrar okuduktan sonra, hepsi iyi puanlar veriyor, ancak bence lineItems()sizde Agrega Kökünün ilk alınışının kapsamı ve önyüklemesi ile ilgili bazı önemli noktalar var .
e_i_pi

3

Genel olarak, agregaya ait değerli nesnelerin kendi başlarına depoları yoktur. Onları doldurmak toplu köklerin sorumluluğundadır. Sizin durumunuzda, Order varlık ve OrderLine değerleri nesnelerini doldurmak OrderRepository'nin sorumluluğundadır.

İlgili olarak, OrderRepository'nin altyapı uygulaması, ORM durumunda, bire-çok ilişkidir ve OrderLine'ı hevesle veya tembel olarak yüklemeyi seçebilirsiniz.

Hizmetlerinizin tam olarak ne anlama geldiğinden emin değilim. Oldukça "Uygulama Servisi" yakın. Böyle bir durumda, hizmetleri toplu kök / varlık / değer nesnesine enjekte etmek iyi bir fikir değildir. Uygulama Hizmeti, Toplu kök / Varlık / Değer Nesnesi ve Etki Alanı Hizmeti'nin istemcisi olmalıdır. Hizmetlerinizle ilgili başka bir şey de Uygulama Hizmetinde değer nesnelerini ortaya çıkarmak iyi bir fikir değildir. Bunlara toplu kök tarafından erişilmelidir.


2

Cevap: kesinlikle HAYIR, varlık yöntemlerinde hizmet vermekten kaçının.

Çözüm basit: Sipariş deposunun Sipariş'i tüm LineItems'leriyle döndürmesine izin verin. Sizin durumunuzda toplama Order + LineItems olduğundan, depo tam bir toplama döndürmezse işini yapmaz.

Daha geniş olan ilke: fonksiyonel bitleri (ör. Domain logic) fonksiyonel olmayan bitlerden (örn. Kalıcılık) ayrı tutmak.

Bir şey daha: Yapabiliyorsanız, bunu yapmaktan kaçının:

$order = $orderService->getOrderByID($orderID);
foreach($order->getLineItems() as $lineItem) {
  ...
}

Bunun yerine bunu yap

$order = $orderService->getOrderByID($orderID);
$order->doSomethingSignificant();

Nesneye yönelik tasarımda, bir nesne verisinde balık avından kaçınmaya çalışırız. Nesneden istediğimizi yapmasını istemeyi tercih ediyoruz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.