Depolardan Etki Alanından Erişme


14

Bir görev günlüğü sistemimiz olduğunu varsayalım, bir görev günlüğe kaydedildiğinde, kullanıcı bir kategori belirtir ve görev varsayılan olarak 'Üstün' durumuna geçer. Bu durumda Kategori ve Durum'un varlık olarak uygulanması gerektiğini varsayın. Normalde bunu yaparım:

Uygulama katmanı:

public class TaskService
{
    //...

    public void Add(Guid categoryId, string description)
    {
        var category = _categoryRepository.GetById(categoryId);
        var status = _statusRepository.GetById(Constants.Status.OutstandingId);
        var task = Task.Create(category, status, description);
        _taskRepository.Save(task);
    }
}

Varlık:

public class Task
{
    //...

    public static void Create(Category category, Status status, string description)
    {
        return new Task
        {
            Category = category,
            Status = status,
            Description = descrtiption
        };
    }
}

Bunu böyle yapıyorum çünkü sürekli olarak varlıkların depolara erişmemesi gerektiği söylendi, ancak bunu yapsaydım çok daha mantıklı olurdu:

Varlık:

public class Task
{
    //...

    public static void Create(Category category, string description)
    {
        return new Task
        {
            Category = category,
            Status = _statusRepository.GetById(Constants.Status.OutstandingId),
            Description = descrtiption
        };
    }
}

Durum deposu yine de bağımlı olarak enjekte edilir, bu yüzden gerçek bir bağımlılık yoktur ve bu bana göre bir görev varsayılan olarak varsayılan olarak karar veren etki alanıdır. Önceki sürüm, bu kararı veren uygulama katmanı gibi geliyor. Herhangi bir olasılık olmasaydı, neden alandaki depo sözleşmeleri sık sık oluyor?

İşte daha uç bir örnek, burada etki alanı aciliyete karar veriyor:

Varlık:

public class Task
{
    //...

    public static void Create(Category category, string description)
    {
        var task = new Task
        {
            Category = category,
            Status = _statusRepository.GetById(Constants.Status.OutstandingId),
            Description = descrtiption
        };

        if(someCondition)
        {
            if(someValue > anotherValue)
            {
                task.Urgency = _urgencyRepository.GetById
                    (Constants.Urgency.UrgentId);
            }
            else
            {
                task.Urgency = _urgencyRepository.GetById
                    (Constants.Urgency.SemiUrgentId);
            }
        }
        else
        {
            task.Urgency = _urgencyRepository.GetById
                (Constants.Urgency.NotId);
        }

        return task;
    }
}

Aciliyetin tüm olası sürümlerini iletmek istemenin hiçbir yolu yoktur ve bu iş mantığını uygulama katmanında hesaplamak istemezsiniz, bu yüzden bu kesinlikle en uygun yol olur mu?

Bu, etki alanından depolara erişmek için geçerli bir neden mi?

EDIT: Bu statik olmayan yöntemlerde de olabilir:

public class Task
{
    //...

    public void Update(Category category, string description)
    {
        Category = category,
        Status = _statusRepository.GetById(Constants.Status.OutstandingId),
        Description = descrtiption

        if(someCondition)
        {
            if(someValue > anotherValue)
            {
                Urgency = _urgencyRepository.GetById
                    (Constants.Urgency.UrgentId);
            }
            else
            {
                Urgency = _urgencyRepository.GetById
                    (Constants.Urgency.SemiUrgentId);
            }
        }
        else
        {
            Urgency = _urgencyRepository.GetById
                (Constants.Urgency.NotId);
        }

        return task;
    }
}

Yanıtlar:


8

Karıştırıyorsunuz

kuruluşlar depolara erişmemelidir

(bu iyi bir öneri)

ve

etki alanı katmanı depolara erişmemelidir

(depolarınız uygulama katmanının değil, alan katmanının bir parçası olduğu sürece bu kötü bir öneri olabilir). Aslında, örnekleriniz , herhangi bir varlığa ait olmayan statik yöntemler kullandığınızdan, bir varlığın bir depoya eriştiği hiçbir durumu göstermez .

Bu oluşturma mantığını varlık sınıfının statik bir yöntemine koymak istemiyorsanız, ayrı fabrika sınıfları (etki alanı katmanının bir parçası olarak!) Ekleyebilir ve oluşturma mantığını buraya koyabilirsiniz.

DÜZENLEME: Updateörneğinize göre: _urgencyRepositoryve bir tür arabirim olarak tanımlanan statusRepository sınıfın üyeleri olduğu için Task, şimdi Taskkullanabilmeniz Updateiçin önce bunları herhangi bir varlığa enjekte etmeniz gerekir (örneğin, Görev yapıcısında). Veya bunları statik üyeler olarak tanımlarsınız, ancak çok iş parçacığı sorunlarına veya aynı anda farklı Görev varlıkları için farklı depolara ihtiyacınız olduğunda sorunlara neden olabileceğine dikkat edin.

Bu tasarım, Taskvarlıkları yalıtılmış olarak oluşturmayı biraz zorlaştırır , bu nedenle Taskvarlıklar için birim testleri yazmayı zorlaştırır, Görev varlıklarına bağlı olarak otomatik testler yazmayı zorlaştırır ve her Görev varlığının artık depolara iki atıfta bulunun. Tabii ki, bu sizin durumunuzda tolere edilebilir. Öte yandan, TaskUpdaterdoğru depolara yapılan referansları koruyan ayrı bir yardımcı sınıf oluşturmak genellikle veya en azından bazen daha iyi bir çözüm olabilir.

Önemli olan: TaskUpdaterAlan adı katmanının bir parçası olmaya devam edecek! Bu güncelleştirmeyi veya oluşturma kodunu ayrı bir sınıfa yerleştirmeniz, başka bir katmana geçmeniz gerektiği anlamına gelmez.


Bunun statik olmayan yöntemler için olduğu kadar statik olanlar için de geçerli olduğunu göstermek için düzenledim. Fabrika yönteminin bir varlığın parçası olmadığını hiç düşünmemiştim.
Paul T Davies

@PaulTDavies: benim düzenlememi görmek
Doc Brown

Burada söylediklerinize katılıyorum, ancak Status = _statusRepository.GetById(Constants.Status.OutstandingId)bir iş kuralı olan noktayı çizen kısa bir parça eklerdim, "İş, tüm görevlerin başlangıç ​​durumunu belirler" diyebilir ve bu yüzden bu kod satırı, yalnızca endişeleri CRUD işlemleri aracılığıyla veri yönetimi olan bir havuzun içine ait değildir.
Jimmy Hoffa

@JimmyHoffa: hm, burada hiç kimse bu tür bir satırı depo sınıflarından birine koymayı önermiyordu, ne OP ne de ben - ne demek istiyorsun?
Doc Brown

Görevli bir hizmet olarak TaskUpdater fikrini oldukça seviyorum. Her nasılsa sadece DDD ilkelerini korumak için biraz şekerleme gibi görünüyor, ancak Görev'i her kullandığımda depoyu enjekte etmekten kaçınabileceğim anlamına geliyor.
Paul T Davies

6

Durum örneğinizin gerçek kod mu yoksa sadece gösteri uğruna mı olduğunu bilmiyorum, ancak kimliği sabit olarak tanımlandığında Varlık Olarak Durum (Toplam Kökten bahsetmemek) uygulamanız garip görünüyor kod içinde Constants.Status.OutstandingId. Bu, veritabanında istediğiniz sayıda ekleyebileceğiniz "dinamik" durumların amacını bozmaz mı?

Ben senin durumunda eklemek istiyorum, bir inşaat Task(eğer gerekirse StatusRepository sağ durumunu alma işi dahil) hak olabilir TaskFactoryziyade kalan daha Taskçok nesneler önemsiz olmayan bir topluluğu olduğundan, kendisi.

Fakat :

Sürekli olarak varlıkların depolara erişmemesi gerektiği söylendi

Bu ifade en iyi ihtimalle kesin değildir ve aşırı basit, yanıltıcı ve en kötü ihtimalle tehlikelidir.

Alan adı güdümlü mimarilerde, bir kurumun kendini nasıl saklayacağını bilmemesi oldukça yaygın bir şekilde kabul edilmektedir - bu, kalıcı cehalet ilkesidir. Bu yüzden deposuna kendisini depoya eklemek için çağrı yapılmaz. Diğer varlıkları nasıl ve ne zaman depolayacağını bilmeli mi? Yine, bu sorumluluk başka bir nesneye ait gibi görünmektedir - belki Uygulama katmanı hizmeti gibi yürütme bağlamının ve mevcut kullanım durumunun genel ilerlemesinin farkında olan bir nesne.

Bir işletme başka bir varlığı almak için bir havuz kullanabilir mi? İhtiyaç duymayan varlıklar genellikle kendi toplamı kapsamında olduğundan veya diğer nesnelerin çaprazlanmasıyla elde edilebildiğinden, zamanın% 90'ı gerekmez. Ama olmadıkları zamanlar var. Örneğin, hiyerarşik bir yapı alırsanız, varlıkların genellikle içsel davranışlarının bir parçası olarak tüm atalarına, belirli bir toruna vb. Erişmeleri gerekir. Bu uzak akrabalara doğrudan bir referansları yok. Bu akrabaları operasyonun parametresi olarak etraflarına aktarmak uygun değildir. Öyleyse, neden toplu kökler olmaları koşuluyla onları almak için bir Depo kullanmıyorsunuz?

Birkaç örnek daha var. Mesele şu ki, bazen bir Alan hizmetine yerleştiremeyeceğiniz bir davranış var, çünkü mevcut bir varlığa mükemmel şekilde uyuyor gibi görünüyor. Ve yine de, bu varlığın bir kökü veya kendisine aktarılamayan bir kök koleksiyonunu nemlendirmek için bir Depoya erişmesi gerekir.

Bu nedenle, bir Kuruluştan bir Depoya erişmek kendi başına kötü değildir , felaketten kabul edilebilire kadar çeşitli tasarım kararlarından kaynaklanan farklı formlar alabilir .


Bir varlığın zaten bir ilişkisi olduğu bir varlığa erişmek için bir havuz kullanması gerektiğini kabul etmiyorum - o varlığa erişmek için nesne grafiğini geçebilmeniz gerekir. Depoyu bu şekilde kullanmak mutlak bir hayır hayırdır. Burada tartıştığım şey, varlığın henüz bir referansı olmadığı, ancak bazı iş koşullarında bir tane oluşturması gerektiğidir.
Paul T Davies

Beni iyi okuduysan, buna tamamen katılıyoruz ...
guillaume31

2

Etki alanımda Numaralılar veya saf arama tabloları kullanmamamın bir nedeni budur. Aciliyet ve Durum her iki Devlettir ve doğrudan devlete ait bir durumla ilişkili bir mantık vardır (örneğin, mevcut durumum verildiğinde hangi eyaletlere geçebilirim). Ayrıca, bir durumu saf bir değer olarak kaydederek, görevin belirli bir durumda ne kadar sürdüğü gibi bilgileri kaybedersiniz. Durumları bir sınıf hiyerarşisi olarak temsil ediyorum. (C # ile)

public class Interval
{
  public Interval(DateTime start, DateTime? end)
  {
    Start=start;
    End=end;
  }

  //To be called by internal framework
  protected Interval()
  {
  }

  public void End(DateTime? when=null)
  {
    if(when==null)
      when=DateTime.Now;
    End=when;
  }

  public DateTime Start{get;protected set;}

  public DateTime? End{get; protected set;}
}

public class TaskStatus
{
  protected TaskStatus()
  {
  }
  public Long Id {get;protected set;}

  public string Name {get; protected set;}

  public string Description {get; protected set;}

  public Interval Duration {get; protected set;}

  public virtual TNewStatus TransitionTo<TNewStatus>()
    where TNewStatus:TaskStatus
  {
    throw new NotImplementedException();
  }
}

public class OutStandingTaskStatus:TaskStatus
{
  protected OutStandingTaskStatus()
  {
  }

  public OutStandingTaskStatus(bool initialize)
  {
    Name="Oustanding";
    Description="For tasks that need to be addressed";
    Duration=new Interval(DateTime.Now,null);
  }

  public override TNewStatus TransitionTo<TNewStatus>()
  {
    if(typeof(TNewStatus)==typeof(CompletedTaskStatus))
    {
      var transitionDate=DateTime.Now();
      Duration.End(transitionDate);
      return new CompletedTaskStatus(true);
    }
    return base.TransitionTo<TNewStatus>();
  }
}

CompletedTaskStatus uygulaması neredeyse aynı olacaktır.

Burada dikkat edilmesi gereken birkaç nokta var:

  1. Varsayılan kurucuları korumalı hale getiriyorum. Bu, çerçevenin bir nesneyi kalıcılıktan çekerken buna çağırabilir (hem EntityFramework Code-first hem de NHibernate, sihirlerini yapmak için etki alanı nesnelerinizden türetilen proxy'leri kullanır).

  2. Mülkiyet belirleyicilerinin çoğu aynı nedenden dolayı korunmaktadır. Bir Aralığın bitiş tarihini değiştirmek istersem, Interval.End () işlevini çağırmak zorundayım (bu, Anemic Domain Objects yerine anlamlı işlemler sağlayan, Domain Driven Design'ın bir parçasıdır.

  3. Burada göstermiyorum ama Görev de mevcut durumunu nasıl sakladığının ayrıntılarını gizleyecekti. Genelde, ilgileniyorlarsa halkın sorgulamasına izin verdiğim, korunan bir HistoricalStates listesi var. Aksi takdirde geçerli durumu HistoricalStates.Single (state.Duration.End == null) sorgulayan bir alıcı olarak maruz.

  4. TransitionTo işlevi önemlidir, çünkü geçiş için hangi durumların geçerli olduğu hakkında mantık içerebilir. Sadece bir enumunuz varsa, bu mantık başka bir yerde uzanmalıdır.

Umarım, bu DDD yaklaşımını biraz daha iyi anlamanıza yardımcı olur.


1
Farklı durumlar, devlet modeli örneğinizde olduğu gibi farklı davranışlara sahipse ve bu da tartışılan sorunu kesinlikle çözerse, kesinlikle doğru bir yaklaşım olacaktır. Ancak, farklı davranışları değil, farklı değerleri olsaydı, her durum için bir sınıfı haklı çıkarmak zor olurdu.
Paul T Davies

1

Aynı sorunu bir süredir çözmeye çalışıyorum, Task.UpdateTask () 'i çağırabilmeye karar verdim. (...) sadece CRUD değil, bir eylemi belirtmek için.

her neyse, sorununuzu denedim ve bu ile geldi ... benim pasta var ve çok yemek. Fikir, tüm bağımlılıkların enjeksiyonu olmadan işletme üzerinde eylemlerin gerçekleşmesidir. Bunun yerine, varlığın durumuna erişebilmeleri için statik yöntemlerle çalışma yapılır. Fabrika hepsini bir araya getirir ve normalde işletmenin yapması gereken işi yapmak için ihtiyaç duyduğu her şeye sahip olur. Müşteri kodu artık temiz ve açık görünüyor ve kuruluşunuz herhangi bir depo enjeksiyonuna bağımlı değil.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace UnitTestProject2
{
    public class ClientCode
    {
        public void Main()
        {
            TaskFactory factory = new TaskFactory();
            Task task = factory.Create();
            task.UpdateTask(new Category(), "some value");
        }

    }
    public class Category
    {
    }

    public class Task
    {
        public Action<Category, String> UpdateTask { get; set; }

        public static void UpdateTaskAction(Task task, Category category, string description)
        {
            // do the logic here, static can access private if needed
        }
    }

    public class TaskFactory
    {      
        public Task Create()
        {
            Task task = new Task();
            task.UpdateTask = (category, description) =>
                {
                    Task.UpdateTaskAction(task, category, description);
                };

            return task;
        }

    }
}
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.