di DDD, haruskah repositori mengekspos entitas atau objek domain?

11

Seperti yang saya pahami, dalam DDD, adalah tepat untuk menggunakan pola repositori dengan root agregat. Pertanyaan saya adalah, apakah saya harus mengembalikan data sebagai entitas atau objek domain / DTO?

Mungkin beberapa kode akan menjelaskan pertanyaan saya lebih lanjut:

Kesatuan

public class Customer
{
  public Guid Id { get; set; }
  public string FirstName { get; set; }
  public string LastName { get; set; }
}

Haruskah saya melakukan sesuatu seperti ini?

public Customer GetCustomerByName(string name) { /*some code*/ }

Atau sesuatu seperti ini?

public class CustomerDTO
{
  public Guid Id { get; set; }
  public FullName { get; set; }
}

public CustomerDTO GetCustomerByName(string name) { /*some code*/ }

Pertanyaan tambahan:

  1. Dalam repositori, haruskah saya mengembalikan IQueryable atau IEnumerable?
  2. Dalam layanan atau repositori, saya harus melakukan sesuatu seperti .. GetCustomerByLastName, GetCustomerByFirstName, GetCustomerByEmail? atau hanya membuat metode seperti itu GetCustomerBy(Func<string, bool> predicate)?
ikan kode
sumber
Apa yang akan GetCustomerByName('John Smith')kembali jika Anda memiliki dua puluh John Smiths di basis data Anda? Sepertinya Anda mengasumsikan tidak ada dua orang yang memiliki nama yang sama.
bdsl

Jawaban:

8

haruskah saya mengembalikan data sebagai entitas atau objek domain / DTO

Yah itu sepenuhnya tergantung pada kasus penggunaan Anda. Satu-satunya alasan saya bisa memikirkan mengembalikan DTO daripada entitas penuh adalah jika entitas Anda sangat besar dan Anda hanya perlu mengerjakan subsetnya.

Jika ini masalahnya, maka mungkin Anda harus mempertimbangkan kembali model domain Anda dan membagi entitas besar Anda menjadi entitas yang lebih kecil terkait.

  1. Dalam repositori, haruskah saya mengembalikan IQueryable atau IEnumerable?

Aturan praktis yang baik adalah untuk selalu mengembalikan tipe yang paling sederhana (tertinggi dalam hierarki warisan). Jadi, kembali IEnumerablekecuali jika Anda ingin membiarkan konsumen repositori bekerja dengan IQueryable.

Secara pribadi, saya pikir mengembalikan sebuah IQueryableabstraksi bocor tetapi saya telah bertemu beberapa pengembang yang dengan penuh semangat berpendapat bahwa itu bukan abstraksi. Dalam pikiran saya semua logika kueri harus dimuat dan disembunyikan oleh Repositori. Jika Anda mengizinkan kode panggilan untuk menyesuaikan kueri mereka, lalu apa gunanya repositori?

  1. Dalam layanan atau repositori, haruskah saya melakukan sesuatu seperti .. GetCustomerByLastName, GetCustomerByFirstName, GetCustomerByEmail? atau hanya membuat metode yang mirip dengan GetCustomerBy (Func predicate)?

Untuk alasan yang sama seperti yang saya sebutkan pada poin 1, pasti tidak menggunakan GetCustomerBy(Func<string, bool> predicate). Pada awalnya mungkin terlihat menggoda, tetapi inilah mengapa orang-orang belajar membenci repositori generik. Itu bocor.

Hal-hal seperti GetByPredicate(Func<T, bool> predicate)hanya berguna ketika mereka tersembunyi di balik kelas yang konkret. Jadi, jika Anda memiliki kelas dasar abstrak RepositoryBase<T>yang disebut terpapar protected T GetByPredicate(Func<T, bool> predicate)yang hanya digunakan oleh repositori beton (misalnya, public class CustomerRepository : RepositoryBase<Customer>) maka itu akan baik-baik saja.

MetaFight
sumber
Jadi ucapan Anda, boleh saja memiliki kelas DTO di lapisan domain?
kode ikan
Bukan itu yang ingin saya katakan. Saya bukan guru DDD jadi saya tidak bisa mengatakan apakah itu dapat diterima. Karena penasaran, mengapa Anda tidak mengembalikan entitas penuh? Apakah terlalu besar?
MetaFight
Oh tidak juga. Saya hanya ingin tahu apa yang benar dan dapat diterima untuk dilakukan. Apakah untuk mengembalikan entitas penuh atau hanya sebagian saja. Saya kira itu tergantung pada jawaban Anda.
codefish
6

Ada komunitas orang yang cukup besar yang menggunakan CQRS untuk mengimplementasikan domain mereka. Perasaan saya adalah bahwa, jika antarmuka repositori Anda analog dengan praktik terbaik yang digunakan oleh mereka, Anda tidak akan tersesat terlalu jauh.

Berdasarkan apa yang saya lihat ...

1) Penangan perintah biasanya menggunakan repositori untuk memuat agregat melalui repositori. Perintah menargetkan contoh spesifik agregat; repositori memuat root dengan ID. Tidak ada, yang bisa saya lihat, kasus di mana perintah dijalankan terhadap kumpulan agregat (sebagai gantinya, Anda pertama-tama akan menjalankan kueri untuk mendapatkan kumpulan agregat, kemudian menghitung koleksi dan mengeluarkan perintah untuk masing-masing.

Oleh karena itu, dalam konteks di mana Anda akan memodifikasi agregat, saya berharap repositori mengembalikan entitas (alias akar agregat).

2) Penangan permintaan tidak menyentuh agregat sama sekali; sebagai gantinya, mereka bekerja dengan proyeksi agregat - objek nilai yang menggambarkan keadaan agregat / agregat pada beberapa titik waktu. Jadi pikirkan ProjectionDTO, daripada AggregateDTO, dan Anda memiliki ide yang tepat.

Dalam konteks di mana Anda akan menjalankan kueri terhadap agregat, menyiapkannya untuk ditampilkan, dan seterusnya, saya berharap untuk melihat DTO, atau koleksi DTO, dikembalikan, bukan entitas.

Semua getCustomerByPropertypanggilan Anda tampak seperti pertanyaan bagi saya, sehingga mereka akan masuk ke dalam kategori yang terakhir. Saya mungkin ingin menggunakan satu titik masuk untuk menghasilkan koleksi, jadi saya akan mencari tahu apakah

getCustomersThatSatisfy(Specification spec)

adalah pilihan yang masuk akal; penangan permintaan kemudian akan membangun spesifikasi yang sesuai dari parameter yang diberikan, dan meneruskan spesifikasi itu ke repositori. The downside adalah bahwa tanda tangan benar-benar menunjukkan bahwa repositori adalah kumpulan dalam memori; tidak jelas bagi saya bahwa predikat membeli Anda banyak jika repositori hanyalah abstraksi menjalankan pernyataan SQL terhadap database relasional.

Ada beberapa pola yang bisa membantu. Sebagai contoh, alih-alih membangun spesifikasi dengan tangan, sampaikan ke repositori deskripsi kendala, dan biarkan implementasi repositori untuk memutuskan apa yang harus dilakukan.

Peringatan: java seperti mengetik terdeteksi

interface CustomerRepository {
    interface ConstraintBuilder {
        void setLastName();
        void setFirstName();
    }

    interface ConstraintDescriptor {
        void copyTo(ConstraintBuilder builder);
    }

    List<CustomerProjection> getCustomersThatSatisfy(ConstraintDescriptor descriptor);
}

SQLBackedCustomerRepository implements CustomerRepository {
    List<CustomerProjection> getCustomersThatSatisfy(ConstraintDescriptor descriptor) {
        WhereClauseBuilder builder = new WhereClauseBuilder();
        descriptor.copyTo(builder);
        Query q = createQuery(builder.build());
        //...
     }
}

CollectionBackedCustomerRepository implements CustomerRepository {
    List<CustomerProjection> getCustomersThatSatisfy(ConstraintDescriptor descriptor) {
        PredicateBuilder builder = new PredicateBuilder();
        descriptor.copyTo(builder);
        Predicate p = builder.build();
        // ...
}

class MatchLastName implements CustomerRepository.ConstraintDescriptor {
    private final lastName;
    // ...

    void copyTo(CustomerRepository.ConstraintBuilder builder) {
        builder.setLastName(this.lastName);
    }
}

Kesimpulannya: pilihan antara menyediakan agregat dan menyediakan DTO tergantung pada apa yang Anda harapkan konsumen lakukan dengannya. Dugaan saya akan menjadi salah satu implementasi konkret yang mendukung antarmuka untuk setiap konteks.

VoiceOfUnreason
sumber
Itu semua bagus dan bagus, tetapi penanya tidak menyebutkan menggunakan CQRS. Anda benar, masalahnya tidak akan ada jika dia melakukannya.
MetaFight
Saya tidak memiliki pengetahuan tentang CQRS yang pernah saya dengar. Seperti yang saya pahami, ketika memikirkan apa yang harus dikembalikan jika AggregateDTO atau ProjectionDTO, saya akan mengembalikan ProjectionDTO. Kemudian pada getCustomersThatSatisfy(Specification spec)is saya hanya akan daftar properti yang saya butuhkan untuk opsi pencarian. Apakah saya benar?
codefish
Tidak jelas. Saya tidak percaya AggregateDTO harus ada. Inti dari agregat adalah untuk memastikan bahwa semua perubahan memenuhi invarian bisnis. Itu enkapsulasi dari aturan domain yang perlu dipenuhi - yaitu, itu adalah perilaku. Proyeksi, di sisi lain, adalah representasi dari beberapa potret negara yang dapat diterima oleh bisnis. Lihat edit untuk upaya menjelaskan spesifikasi.
VoiceOfUnreason
Saya setuju dengan VoiceOfUnason bahwa AgregatDTO seharusnya tidak ada - Saya menganggap ProjectionDTO sebagai model tampilan untuk data saja. Itu dapat menyesuaikan bentuknya dengan apa yang diperlukan oleh penelepon, menarik data dari berbagai sumber yang diperlukan. Root Agregat harus merupakan representasi lengkap dari semua data terkait sehingga semua aturan referensial dapat diuji sebelum disimpan. Itu hanya berubah bentuk dalam keadaan yang lebih drastis, seperti perubahan tabel DB atau hubungan data yang dimodifikasi.
Brad Irby
1

Berdasarkan pengetahuan saya tentang DDD Anda harus memiliki ini,

public CustomerDTO GetCustomerByName(string name) { /*some code*/ }

Dalam repositori, haruskah saya mengembalikan IQueryable atau IEnumerable?

Itu tergantung, Anda akan menemukan pandangan campuran dari orang yang berbeda untuk pertanyaan ini. Tapi saya pribadi percaya bahwa jika repositori Anda akan digunakan oleh layanan seperti CustomerService maka Anda dapat menggunakan IQueryable jika tidak tetap dengan IEnumerable.

Haruskah Repositori mengembalikan IQueryable?

Dalam layanan atau repositori, haruskah saya melakukan sesuatu seperti .. GetCustomerByLastName, GetCustomerByFirstName, GetCustomerByEmail? atau hanya membuat metode yang mirip dengan GetCustomerBy (Func predicate)?

Dalam repositori Anda, Anda harus memiliki fungsi generik seperti yang Anda katakan GetCustomer(Func predicate)tetapi di lapisan layanan Anda tambahkan tiga metode yang berbeda, ini karena ada kemungkinan layanan Anda dipanggil dari klien yang berbeda dan mereka membutuhkan DTO yang berbeda.

Anda juga dapat menggunakan Pola Repositori Generik untuk CRUD umum, jika Anda belum menyadarinya.

Muhammad Raja
sumber