DTO'lar için kompozisyon ve kalıtım kullanın


13

Tek Sayfa Uygulamamız için bir REST API'si sağlayan bir ASP.NET Web API'miz var. Bu API üzerinden veri aktarmak için DTO'lar / POCO'lar kullanıyoruz.

Sorun şu ki, bu DTO'lar zamanla büyüyor, bu yüzden şimdi DTO'ları yeniden düzenlemek istiyoruz.

Ben nasıl bir DTO tasarlamak için "en iyi uygulamalar" arıyorum: Şu anda sadece değer türü alanlardan oluşan küçük DTO'lar var, örneğin:

public class UserDto
{
    public int Id { get; set; }

    public string Name { get; set; }
}

Diğer DTO'lar bu UserDto'yu bileşime göre kullanır, örneğin:

public class TaskDto
{
    public int Id { get; set; }

    public UserDto AssignedTo { get; set; }
}

Ayrıca, başkalarından devralınarak tanımlanan bazı genişletilmiş DTO'lar vardır, örneğin:

public class TaskDetailDto : TaskDto
{
    // some more fields
}

Bazı DTO'lar birkaç uç nokta / yöntem (örneğin GET ve PUT) için kullanıldığından, zaman içinde bazı alanlar tarafından aşamalı olarak genişletilmiştir. Ve kalıtım ve kompozisyon nedeniyle diğer DTO'lar da büyüdü.

Benim sorum şimdi miras ve kompozisyon iyi uygulamalar değil mi? Ancak onları tekrar kullanmadığımızda, aynı kodu birden çok kez yazmak gibi geliyor. Birden fazla uç nokta / yöntem için bir DTO kullanmak kötü bir uygulamadır mı, yoksa sadece bazı nüanslarda farklılık gösteren farklı DTO'lar olmalı mı?


6
Size ne yapmanız gerektiğini söyleyemem, ancak DTO, kalıtım ve kompozisyonla çalışma deneyimime daha sonradan kötü bir kahve gibi avlanırsınız. DTO'yu bir daha asla kullanmayacağım. Hiç. Ayrıca benzer DTO'ların KURU bir ihlal olduğunu düşünmüyorum. Aynı temsili döndüren iki uç nokta, aynı DTO'ları yeniden kullanabilir. Benzer temsiller döndüren iki uç nokta aynı DTO'ları döndürmüyor , bu yüzden her biri için belirli DTO'lar yapıyorum . Eğer seçmek zorunda kalırsam, kompozisyon uzun vadede daha az sorunludur.
LAIV

@Laiv bu soruya doğru cevap, sadece yapma. Neden bir yorum olarak koymak emin değilim
TheCatWhisperer

2
@Laiv: Bunun yerine ne kullanıyorsunuz? Benim tecrübelerime göre, bununla mücadele eden insanlar sadece onu düşünüyorlar. DTO sadece veri için bir kaptır ve hepsi bu kadardır.
Robert Harvey

TheCatWhisperer çünkü argümanlarım çoğunlukla fikir temelli olacaktı. Hala projelerimde bu tür sorunları ele almaya çalışıyorum. @RobertHarvey doğru, neden gerçekte olduklarından daha zor görmeye eğilimliyim bilmiyorum. Hala çözüm üzerinde çalışıyorum. HAL'ın bu sorunları çözmek için model olduğu konusunda oldukça ikna olmuştum, ancak cevabınızı okurken DTO'larımı da çok ayrıntılı yaptığımı fark ettim. Bu yüzden önce yaklaşımınızı uygulamaya koyacağım. Değişiklikler tamamen HATEOAS'a geçmekten daha az dramatik olacak.
LAIV

Size yardımcı olabilecek iyi bir tartışma için bunu deneyin: stackoverflow.com/questions/6297322/…
johnny

Yanıtlar:


10

En iyi uygulama olarak DTO'larınızı olabildiğince özlü hale getirmeyi deneyin. Sadece geri dönmek için gerekeni döndürün. Yalnızca kullanmanız gerekenleri kullanın. Bu birkaç ekstra DTO demekse, öyle olsun.

Örneğinizde, bir görev bir kullanıcı içerir. Muhtemelen orada tam kullanıcı nesnesine ihtiyaç duyulmaz, belki sadece göreve atanan kullanıcının adı. Kullanıcı özelliklerinin geri kalanına ihtiyacımız yok.

Bir görevi farklı bir kullanıcıya yeniden atamak istediğinizi varsayalım, yeniden atama yayını sırasında bir tam kullanıcı nesnesini ve bir tam görev nesnesini geçirmeye cazip gelebilir. Ancak, gerçekten gerekli olan sadece görev kimliği ve kullanıcı kimliği. Bunlar, bir görevi yeniden atamak için gereken iki bilgi parçasıdır, bu nedenle DTO'yu bu şekilde modelleyin. Bu, yalın bir DTO modeli için uğraşıyorsanız, her dinlenme çağrısı için genellikle ayrı bir DTO olduğu anlamına gelir.

Ayrıca, bazen miras / kompozisyona ihtiyaç duyulabilir. Diyelim ki birinin bir işi var. Bir işin birden fazla görevi vardır. Bu durumda, bir iş bulmak, işin görev listesini de döndürebilir. Yani, kompozisyona karşı bir kural yok. Neyin modelleneceğine bağlıdır.


Mümkün olduğunca kısa ve öz, DTO'ları sadece bazı detaylarda farklılık gösterirlerse yeniden oluşturmam gerektiği anlamına gelir - değil mi?
subay

1
@officer - Genel olarak, evet.
Jon Raynor

2

Sisteminiz kesinlikle CRUD işlemlerine dayanmadıkça, DTO'larınız çok ayrıntılıdır. İş süreçlerini veya yapay nesneleri içeren uç noktalar oluşturmayı deneyin. Bu yaklaşım, bir iş mantığı katmanı ve Eric Evans'ın "alan güdümlü tasarımı" ile uyumludur.

Örneğin, son kullanıcıya bir ekranda veya formda görüntülenebilmesi için bir faturanın verilerini döndüren bir bitiş noktanız olduğunu varsayalım. Bir CRUD modelinde, gerekli bilgileri birleştirmek için uç noktalarınıza birkaç çağrı yapmanız gerekir: ad, fatura adresi, gönderim adresi, satır öğeleri. Bir ticari işlem bağlamında, tek bir uç noktadan gelen tek bir DTO, tüm bu bilgileri bir kerede geri verebilir.


Robert, burada Agrega Kökü mü demek istiyorsun?
johnny

Şu anda DTO'larımız bir form için tüm verileri sağlamak üzere tasarlanmıştır, örneğin bir yapılacaklar listesi görüntülenirken TodoListDTO bir Görev Listesi Listesi'ne sahiptir. Ancak TaskDTO'yu yeniden kullandığımız için, her birinde bir UserDTO da var. Yani sorun arka uçta sorgulanan ve tel üzerinden gönderilen büyük miktarda veri.
subay

Tüm veriler gerekli mi?
Robert Harvey

1

DTO gibi "esnek" veya soyut bir şey için en iyi uygulamaları oluşturmak zordur. Esasen, DTO'lar yalnızca veri aktarımı için nesnelerdir, ancak hedefe veya aktarımın nedenine bağlı olarak, farklı "en iyi uygulamaları" uygulamak isteyebilirsiniz.

Martin Fowler'ın Kurumsal Uygulama Mimarisi Kalıplarını okumanızı tavsiye ederim . DTO'ların gerçekten ayrıntılı bir bölüm aldığı desenlere adanmış bir bölüm var.

Başlangıçta, pahalı mantıklı aramalarda kullanılmak üzere "tasarlandılar", burada mantığınızın farklı kısımlarından çok fazla veriye ihtiyacınız olacaktır; DTO'lar veri aktarımını tek bir çağrıda yapacaktır.

Yazara göre, DTO'ların yerel ortamlarda kullanılması amaçlanmamıştı, ancak bazı insanlar onlar için bir kullanım buldu. Genellikle farklı POCO'lardan GUI'ler, API'lar veya farklı katmanlar için tek bir varlıkta bilgi toplamak için kullanılırlar.

Şimdi, miras ile, kodun yeniden kullanımı asıl amacından ziyade mirasın bir yan etkisi gibidir; kompozisyon ise temel amaç olarak kodun yeniden kullanımı ile uygulanmaktadır.

Bazı insanlar , her ikisinin de güçlü yönlerini kullanarak ve zayıflıklarını azaltmaya çalışarak, kompozisyon ve mirasın birlikte kullanılmasını önerir. Aşağıdakiler, yeni DTO'lar veya bu konu için herhangi bir yeni sınıf / nesne seçerken veya oluştururken zihinsel sürecimin bir parçası:

  • Aynı katman veya bağlam içinde DTO'lar ile kalıtım kullanıyorum. Bir DTO asla bir POCO'dan miras alınmaz, bir BLL DTO asla bir DAL DTO'dan miras kalmaz.
  • Kendimi bir alanı bir DTO'dan gizlemeye çalışırken bulursam, yeniden düzenleme yapacağım ve belki kompozisyonu kullanacağım.
  • Tek bir DTO tabanından çok az farklı alan tek ihtiyacım olursa, bunları evrensel bir DTO'ya koyacağım. Evrensel DTO'lar yalnızca dahili olarak kullanılır.
  • Bir temel POCO / DTO neredeyse hiçbir zaman mantık için kullanılmayacaktır, bu şekilde temel sadece çocuklarının ihtiyaçlarına cevap verir. Tabanı kullanmam gerekirse, çocuklarının asla kullanmayacağı yeni bir alan eklemekten kaçınırım.

Bazıları belki de "en iyi" uygulamalar olmayabilir, üzerinde çalıştığım projeler için gayet iyi çalışıyorlar ama hiçbir boyutun herkese uymadığını hatırlamanız gerekiyor. Evrensel DTO durumunda dikkatli olmalısınız, yöntem imzalarım şöyle görünüyor:

public void DoSomething(BaseDTO base) {
    //Some code 
}

Yöntemlerden herhangi birinin kendi DTO'suna ihtiyacı varsa ve genellikle miras yapmam gereken tek değişiklik parametredir, ancak bazen belirli durumlar için daha derine inmem gerekir.

Yorumlarınızdan iç içe DTO'lar kullandığınızı görüyorum. Yuvalanmış DTO'larınız sadece diğer DTO'ların bir listesinden oluşuyorsa, yapılacak en iyi şeyin listeyi açmak olduğunu düşünüyorum.

Görüntülemeniz veya üzerinde çalışmanız gereken veri miktarına bağlı olarak, verileri sınırlandıran yeni DTO'lar oluşturmak iyi bir fikir olabilir; Örneğin, UserDTO'nuzda çok fazla alan varsa ve yalnızca 1 veya 2'ye ihtiyacınız varsa, yalnızca bu alanlara sahip bir DTO'ya sahip olmak daha iyi olabilir. Bir DTO'nun katmanını, içeriğini, kullanımını ve faydasını tanımlamak, onu tasarlarken çok yardımcı olacaktır.


Cevabımda 2'den fazla bağlantı ekleyemedim, işte yerel DTO'lar hakkında daha fazla bilgi . Çok eski bilgilerinin farkındayım ama bazılarının hala geçerli olduğunu düşünüyorum.
IvanGrasp

1

DTO'larda kompozisyon kullanmak mükemmel bir uygulamadır.

Somut DTO türleri arasında kalıtım kötü bir uygulamadır.

Bir kere, C # gibi bir dilde, otomatik olarak uygulanan bir özelliğin çok az sürdürülebilirlik yükü vardır, bu yüzden onları çoğaltmak (gerçekten yinelemekten nefret ediyorum) çoğu zaman olduğu kadar zararlı değildir.

Bunun bir nedeni değil DTOs arasında somut miras kullanmayı belirli araçlar mutlu yanlış türlerine görüntülemektedirler olmasıdır.

Örneğin, class name -> table nameçıkarım yapan Dapper gibi bir veritabanı yardımcı programı kullanırsanız (bunu tavsiye etmiyorum ama popülerdir) , türetilmiş bir türü hiyerarşinin herhangi bir yerinde somut bir temel türü olarak kolayca kaydedebilir ve böylece verileri kaybedebilir veya daha da kötüsü yapabilirsiniz. .

A nedenin değil DTOs arasında kullanım miras bu bariz bir ilişki "bir olan" yok türleri arasında pay uygulamaları için kullanılmamalıdır gerektiğidir. Aklımda, TaskDetailbir alt türü gibi görünmüyor Task. Kolayca bir mülk Taskolabilir ya da daha da kötüsü, bir süpertip olabilir Task.

Şimdi, endişelenebileceğiniz bir şey, ilgili DTO'ların özelliklerinin adları ve türleri arasında tutarlılığı korumaktır .

Somut türlerin kalıtımı, sonuç olarak, bu tür bir tutarlılığın sağlanmasına yardımcı olurken, bu tür bir tutarlılığı korumak için arayüzleri (veya C ++'daki saf sanal temel sınıfları) kullanmak çok daha iyidir.

Aşağıdaki DTO'ları düşünün

interface IIdentity
{
    int Id { get; set; }
}

interface INamed
{
    string Name { get; set; }
}

public class UserDto: IIdentity, INamed
{
    public int Id { get; set; }

    public string Name { get; set; }

    // User specific properties
}

public class TaskDto: IIdentity
{
    public int Id { get; set; }

    // Task specific properties
}
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.