Repositori DDD dalam aplikasi atau layanan domain

29

Saya sedang mempelajari DDD hari ini, dan saya memiliki beberapa pertanyaan mengenai bagaimana mengelola repositori dengan DDD.

Sebenarnya, saya telah bertemu dua kemungkinan:

Pertama

Cara pertama mengelola layanan yang saya baca adalah menyuntikkan repositori dan model domain dalam layanan aplikasi.

Dengan cara ini, dalam salah satu metode layanan aplikasi, kami memanggil metode layanan domain (memeriksa aturan bisnis) dan jika kondisinya baik, repositori dipanggil pada metode khusus untuk bertahan / mengambil entitas dari database.

Cara sederhana untuk melakukan ini adalah:

class ApplicationService{

  constructor(domainService, repository){
    this.domainService = domainService
    this.repository = repository
  }

  postAction(data){
    if(this.domainService.validateRules(data)){
      this.repository.persist(new Entity(data.name, data.surname))
    }
    // ...
  }

}

Kedua

Kemungkinan kedua adalah menyuntikkan repositori di dalam domainService saja, dan hanya menggunakan repositori melalui layanan domain:

class ApplicationService{

  constructor(domainService){
    this.domainService = domainService
  }

  postAction(data){
    if(this.domainService.persist(data)){
      console.log('all is good')
    }
    // ...
  }

}

class DomainService{

  constructor(repository){
    this.repository = repository
  }

  persist(data){
    if(this.validateRules(data)){
      this.repository.save(new Entity(data.name))
    }
  }

  validateRules(data){
    // returns a rule matching
  }

}

Mulai sekarang, saya tidak dapat membedakan mana yang terbaik (jika ada yang terbaik) atau apa yang mereka maksudkan keduanya dalam konteks mereka.

Bisakah Anda memberi saya contoh di mana yang satu bisa lebih baik dari yang lain dan mengapa?

mfrachet
sumber
"untuk menyuntikkan repositori dan model domain dalam layanan aplikasi." Apa yang Anda maksud dengan menyuntikkan "model domain" di suatu tempat? AFAICT dalam hal model domain DDD berarti seluruh rangkaian konsep dari domain dan interaksi di antara mereka yang relevan untuk aplikasi. Ini hal yang abstrak, ini bukan objek dalam memori. Anda tidak bisa menyuntikkannya.
Alexey

Jawaban:

31

Jawaban singkatnya adalah - Anda dapat menggunakan repositori dari layanan aplikasi, atau layanan domain - tetapi penting untuk mempertimbangkan mengapa, dan bagaimana, Anda melakukannya.

Tujuan Layanan Domain

Layanan Domain harus merangkum konsep / logika domain - dengan demikian, metode layanan domain:

domainService.persist(data)

tidak termasuk dalam layanan domain, karena persistbukan bagian dari bahasa di mana - mana dan operasi kegigihan bukan bagian dari logika bisnis domain.

Secara umum, layanan domain berguna ketika Anda memiliki aturan / logika bisnis yang memerlukan koordinasi atau bekerja dengan lebih dari satu agregat. Jika logika hanya melibatkan satu agregat, harus dalam metode pada entitas agregat itu.

Repositori dalam Layanan Aplikasi

Jadi dalam pengertian itu, dalam contoh Anda, saya lebih suka opsi pertama Anda - tetapi bahkan ada ruang untuk perbaikan, karena layanan domain Anda menerima data mentah dari api - mengapa layanan domain harus tahu tentang struktur data? Selain itu, data tampaknya hanya terkait dengan agregat tunggal, jadi ada nilai terbatas dalam menggunakan layanan domain untuk itu - umumnya saya akan meletakkan validasi di dalam konstruktor entitas. misalnya

postAction(data){

  Entity entity = new Entity(data.name, data.surname);

  this.repository.persist(entity);

  // ...
}

dan melemparkan pengecualian jika tidak valid. Bergantung pada kerangka aplikasi Anda, mungkin sederhana untuk memiliki mekanisme yang konsisten untuk menangkap pengecualian dan memetakannya ke respons yang sesuai untuk jenis api - mis. Untuk api REST, kembalikan kode status 400.

Gudang di Layanan Domain

Terlepas dari hal di atas, kadang-kadang berguna untuk menyuntikkan dan menggunakan repositori dalam layanan domain, tetapi hanya jika repositori Anda diimplementasikan sedemikian rupa sehingga mereka menerima dan mengembalikan akar agregat saja, dan juga di mana Anda mengabstraksi logika yang melibatkan banyak agregat. misalnya

postAction(data){

  this.domainService.doSomeBusinessProcess(data.name, data.surname, data.otherAggregateId);

  // ...
}

implementasi layanan domain akan terlihat seperti:

doSomeBusinessProcess(name, surname, otherAggregateId) {

  OtherEntity otherEntity = this.otherEntityRepository.get(otherAggregateId);

  Entity entity = this.entityFactory.create(name, surname);

  int calculationResult = this.someCalculationMethod(entity, otherEntity);

  entity.applyCalculationResultWithBusinessMeaningfulName(calculationResult);

  this.entityRepository.add(entity);

}

Kesimpulan

Kuncinya di sini adalah layanan domain merangkum proses yang merupakan bagian dari bahasa di mana-mana. Untuk memenuhi perannya, ia perlu menggunakan repositori - dan tidak apa-apa untuk melakukannya.

Tetapi menambahkan layanan domain yang membungkus repositori dengan metode yang disebut persist menambah sedikit nilai.

Atas dasar itu, jika layanan aplikasi Anda mengekspresikan use case yang meminta untuk hanya bekerja dengan agregat tunggal, tidak ada masalah menggunakan repositori langsung dari layanan aplikasi.

Chris Simon
sumber
Oke, jadi jika saya memiliki aturan bisnis (mengakui aturan Pola Spesifikasi), jika itu hanya menyangkut satu entitas, saya harus tetapi validasi dalam entitas itu? Tampaknya aneh untuk menyuntikkan aturan bisnis seperti mengendalikan format email pengguna yang baik di dalam entitas pengguna. Bukan? Mengenai tanggapan global, terima kasih. Itu mendapat bahwa tidak ada "aturan default untuk diterapkan", dan itu benar-benar bergantung pada penggunaan kami. Saya memiliki beberapa pekerjaan yang harus dilakukan untuk membedakan semua pekerjaan ini dengan baik
mfrachet
2
Untuk memperjelas, aturan yang termasuk dalam entitas hanya aturan yang menjadi tanggung jawab entitas itu. Saya setuju, mengendalikan format email pengguna yang baik tidak terasa seperti milik entitas Pengguna. Secara pribadi, saya suka memasukkan aturan validasi seperti itu ke dalam Value Object yang mewakili alamat email. Pengguna akan memiliki properti tipe EmailAddress, dan konstruktor EmailAddress menerima string, dan melempar pengecualian jika string tidak cocok dengan format yang diperlukan. Kemudian Anda dapat menggunakan kembali ValueObject Alamat EmailAddress pada entitas lain yang perlu menyimpan alamat email.
Chris Simon
Oke saya mengerti mengapa menggunakan Value Object sekarang. Tetapi itu berarti bahwa objek nilai berutang properti yang merupakan aturan bisnis yang mengelola format?
mfrachet
1
Nilai Objek harus tidak berubah. Secara umum ini berarti Anda menginisialisasi dan memvalidasi dalam konstruktor, dan untuk properti apa pun gunakan pola get / public set publik. Tetapi Anda dapat menggunakan konstruksi bahasa untuk mendefinisikan kesetaraan, proses ToString, dll. Misalnya kacper.gunia.me/ddd-building-blocks-in-php-value-object atau github.com/spring-projects/spring-gemfire-examples/ gumpalan / master / ...
Chris Simon
Terima kasih @ ChrisSimon, akhirnya dan menjawab situasi DDD kehidupan nyata yang melibatkan kode dan bukan hanya teori. Saya telah menghabiskan 5 hari menjaring SO dan web untuk contoh fungsional pembuatan dan penghematan agregat, dan ini adalah penjelasan paling jelas yang saya temukan.
e_i_pi
2

Ada masalah dengan jawaban yang diterima:

Model domain tidak diperbolehkan bergantung pada repositori dan layanan domain adalah bagian dari model domain -> layanan domain tidak boleh bergantung pada repositori.

Yang harus Anda lakukan adalah merakit semua entitas Anda yang diperlukan untuk eksekusi logika bisnis yang sudah ada dalam layanan aplikasi dan kemudian hanya menyediakan model Anda dengan objek yang dipakai.

Berdasarkan contoh Anda itu bisa terlihat seperti ini:

class ApplicationService{

  constructor(domainService, repository){
    this.domainService = domainService
    this.repositoryA = repositoryA
    this.repositoryB = repositoryB
    this.repositoryC = repositoryC
  }

  // any parsing and/or pre-business validation already happened in controller or whoever is a caller
  executeUserStory(data){
    const entityA = this.repositoryA.get(data.criterionForEntityA)
    const entityB = this.repositoryB.get(data.criterionForEntityB)

    if(this.domainService.validateSomeBusinessRules(entityA, entityB)){
      this.repositoryC.persist(new EntityC(entityA.name, entityB.surname))
    }
    // ...
  }
}

Jadi, aturan praktis: Model domain tidak bergantung pada lapisan luar

Aplikasi vs Layanan domain Dari artikel ini :

  • Layanan domain sangat granular di mana layanan aplikasi adalah fasad yang bertujuan menyediakan API.

  • Layanan domain berisi logika domain yang tidak dapat secara alami ditempatkan dalam entitas atau objek nilai sedangkan layanan aplikasi mengatur eksekusi logika domain dan tidak sendiri menerapkan logika domain apa pun.

  • Metode layanan domain dapat memiliki elemen domain lainnya sebagai operan dan nilai kembali sedangkan layanan aplikasi beroperasi pada operan sepele seperti nilai identitas dan struktur data primitif.

  • Layanan aplikasi menyatakan ketergantungan pada layanan infrastruktur yang diperlukan untuk mengeksekusi logika domain.

SMS
sumber
1

Tak satu pun dari pola Anda yang baik kecuali layanan dan objek Anda merangkum beberapa tanggung jawab yang koheren.

Pertama-tama katakanlah apa objek domain Anda dan bicarakan apa yang dapat dilakukan dalam bahasa domain. Jika bisa valid atau tidak valid mengapa tidak memiliki ini sebagai properti dari objek domain itu sendiri?

Jika misalnya validitas objek hanya masuk akal dalam hal objek lain maka mungkin Anda memiliki tanggung jawab 'aturan validasi X untuk objek domain' yang dapat.dienkapsulasi dalam satu set layanan.

Apakah memvalidasi objek mengharuskan penyimpanannya dalam aturan bisnis Anda? Mungkin tidak. Tanggung jawab 'menyimpan objek' biasanya berjalan dalam objek repositori terpisah.

Sekarang Anda memiliki operasi yang ingin Anda lakukan yang mencakup berbagai tanggung jawab, membuat objek, memvalidasinya, dan jika valid, simpanlah.

Apakah operasi ini intrinsik ke objek domain? Kemudian Jadikan itu bagian dari objek itu yaituExamQuestion.Answer(string answer)

Apakah itu cocok dengan beberapa bagian lain dari domain Anda? letakkan di sanaBasket.Purchase(Order order)

Apakah Anda lebih suka melakukan layanan ADM REST? Baiklah kalau begitu.

Controller.Post(json) 
{ 
    parse(json); 
    verify(parsedStruct); 
    save(parsedStruct); 
    return 400;
}
Ewan
sumber