Ketidakcocokan konseptual antara Layanan Aplikasi DDD dan REST API

20

Saya mencoba merancang aplikasi yang memiliki domain bisnis yang kompleks dan persyaratan untuk mendukung REST API (tidak sepenuhnya REST, tetapi berorientasi sumber daya). Saya mengalami beberapa masalah dengan cara mengekspos model domain dengan cara yang berorientasi sumber daya.

Di DDD, klien dari model domain harus melalui lapisan 'Layanan Aplikasi' prosedural untuk mengakses fungsionalitas bisnis apa pun, yang diterapkan oleh Entitas dan Layanan Domain. Misalnya ada layanan aplikasi dengan dua metode untuk memperbarui entitas Pengguna:

userService.ChangeName(name);
userService.ChangeEmail(email);

API Layanan Aplikasi ini memperlihatkan perintah (kata kerja, prosedur), bukan status.

Tetapi jika kita juga perlu menyediakan API RESTful untuk aplikasi yang sama, maka ada model sumber daya Pengguna, yang terlihat seperti ini:

{
name:"name",
email:"[email protected]"
}

API berorientasi sumber daya memperlihatkan status , bukan perintah . Ini menimbulkan kekhawatiran berikut:

  • setiap operasi pembaruan terhadap API REST dapat memetakan ke satu atau lebih panggilan prosedur Layanan Aplikasi, tergantung pada properti apa yang sedang diperbarui pada model sumber daya

  • setiap operasi pembaruan terlihat seperti atom ke klien REST API, tetapi tidak diimplementasikan seperti itu. Setiap panggilan Layanan Aplikasi dirancang sebagai transaksi terpisah. Memperbarui satu bidang pada model sumber daya dapat mengubah aturan validasi untuk bidang lain. Jadi kita perlu memvalidasi semua bidang model sumber daya bersama-sama untuk memastikan bahwa semua panggilan Layanan Aplikasi potensial valid sebelum kita mulai membuatnya. Memvalidasi satu set perintah sekaligus jauh lebih mudah daripada melakukan satu per satu. Bagaimana kita melakukannya pada klien yang bahkan tidak tahu perintah individual ada?

  • memanggil metode Layanan Aplikasi dalam urutan berbeda mungkin memiliki efek yang berbeda, sementara REST API membuatnya tampak seperti tidak ada perbedaan (dalam satu sumber daya)

Saya bisa memunculkan masalah yang lebih mirip, tetapi pada dasarnya mereka semua disebabkan oleh hal yang sama. Setelah setiap panggilan ke Layanan Aplikasi, keadaan sistem berubah. Aturan tentang apa itu perubahan yang valid, serangkaian tindakan yang dapat dilakukan entitas dalam perubahan berikutnya. API berorientasi sumber daya mencoba menjadikan semuanya tampak seperti operasi atom. Tetapi kerumitan melintasi celah ini harus pergi ke suatu tempat, dan tampaknya sangat besar.

Selain itu, jika UI lebih berorientasi pada perintah, yang sering terjadi, maka kita harus memetakan antara perintah dan sumber daya di sisi klien dan kemudian kembali ke sisi API.

Pertanyaan:

  1. Haruskah semua kompleksitas ini ditangani oleh lapisan pemetaan REST-to-AppService (tebal)?
  2. Atau apakah saya kehilangan sesuatu dalam pemahaman saya tentang DDD / REST?
  3. Mungkinkah REST tidak praktis untuk mengekspos fungsionalitas model domain pada tingkat kompleksitas (yang cukup rendah) tertentu?
astreltsov
sumber
3
Saya pribadi tidak menganggap REST itu perlu. Namun dimungkinkan untuk memilih DDD ke dalamnya: infoq.com/articles/rest-api-on-cqrs programmer.stackexchange.com/questions/242884/… blog.42.nl/articles/rest-and-ddd-compatible
Den
Pikirkan klien REST sebagai pengguna sistem. Mereka sama sekali tidak peduli tentang BAGAIMANA sistem melakukan tindakan yang dilakukan. Anda tidak akan lagi mengharapkan klien REST untuk mengetahui semua tindakan yang berbeda pada domain daripada yang Anda harapkan dari pengguna. Seperti yang Anda katakan, logika ini harus pergi ke suatu tempat, tetapi harus pergi ke suatu tempat dalam sistem apa pun, jika Anda tidak menggunakan REST Anda hanya akan memindahkannya ke klien. Tidak melakukan hal itu justru merupakan inti dari REST, klien hanya harus tahu bahwa ia ingin memperbarui status dan tidak tahu bagaimana Anda melakukannya.
Cormac Mulhall
2
@astr Jawaban sederhananya adalah sumber daya bukan model Anda, jadi desain kode penanganan sumber daya tidak boleh memengaruhi desain model Anda. Sumber daya adalah aspek yang menghadap ke luar dari sistem, di mana sebagai model internal. Pikirkan sumber daya dengan cara yang sama seperti yang Anda pikirkan tentang UI. Seorang pengguna dapat mengklik satu tombol pada UI dan seratus hal berbeda terjadi dalam model. Mirip dengan sumber daya. Seorang klien memperbarui sumber daya (pernyataan PUT tunggal) dan satu juta hal berbeda mungkin terjadi dalam model. Ini adalah anti-pola untuk memasangkan model Anda dengan sumber daya Anda.
Cormac Mulhall
1
Ini adalah pembicaraan yang baik tentang memperlakukan tindakan dalam domain Anda sebagai efek samping dari perubahan status REST, menjaga domain dan web Anda terpisah (maju cepat hingga 25 menit untuk sedikit berair) yow.eventer.com/events/1004/talks/1047
Cormac Mulhall
1
Saya juga tidak yakin tentang keseluruhan "pengguna sebagai robot / mesin negara". Saya pikir kita harus berusaha untuk membuat antarmuka pengguna kita lebih alami dari itu ...
guillaume31

Jawaban:

10

Saya memiliki masalah yang sama dan "menyelesaikannya" dengan memodelkan sumber daya REST secara berbeda, misalnya:

/users/1  (contains basic user attributes) 
/users/1/email 
/users/1/activation 
/users/1/address

Jadi pada dasarnya saya telah membagi sumber daya yang lebih besar dan kompleks menjadi beberapa yang lebih kecil. Masing-masing berisi kelompok atribut yang agak kohesif dari sumber daya asli yang diharapkan akan diproses bersama.

Setiap operasi pada sumber daya ini adalah atom, meskipun dapat diimplementasikan dengan menggunakan beberapa metode layanan - setidaknya di Spring / Java EE itu tidak masalah untuk membuat transaksi yang lebih besar dari beberapa metode yang awalnya dimaksudkan untuk memiliki transaksi sendiri (menggunakan transaksi yang DIBUTUHKAN perambatan). Anda sering masih perlu melakukan validasi tambahan untuk sumber daya khusus ini, tetapi masih cukup mudah dikelola karena atributnya (seharusnya) kohesif.

Ini juga baik untuk pendekatan HATEOAS, karena sumber daya Anda yang lebih halus menyampaikan lebih banyak informasi tentang apa yang dapat Anda lakukan dengan mereka (alih-alih memiliki logika ini pada klien dan server karena tidak dapat dengan mudah direpresentasikan dalam sumber daya).

Tentu saja ini tidak sempurna - jika UI tidak dimodelkan dengan sumber daya ini dalam pikiran (terutama UI berorientasi data), itu dapat menciptakan beberapa masalah - misalnya UI menyajikan bentuk besar dari semua atribut sumber daya yang diberikan (dan sub-sumber daya) dan memungkinkan Anda untuk sunting semuanya dan simpan sekaligus - ini menciptakan ilusi atomitas walaupun klien harus menyebut beberapa operasi sumber daya (yang sendiri atomik tetapi keseluruhan urutannya bukan atomik).

Juga, pembagian sumber daya ini terkadang tidak mudah atau tidak jelas. Saya melakukan ini terutama pada sumber daya dengan perilaku / siklus hidup yang kompleks untuk mengelola kompleksitasnya.

qbd
sumber
Itulah yang saya telah pikirkan juga - membuat representasi sumber daya lebih granular karena mereka lebih nyaman untuk operasi tulis. Bagaimana Anda menangani permintaan sumber daya ketika mereka menjadi sangat terperinci? Membuat representasi de-normalisasi read-only juga?
astreltsov
1
Tidak, saya tidak memiliki representasi yang hanya dinon-de-normalisasi. Saya menggunakan standar jsonapi.org dan memiliki mekanisme untuk memasukkan sumber daya terkait dalam respons untuk sumber daya yang diberikan. Pada dasarnya saya katakan "beri saya Pengguna dengan ID 1 dan juga sertakan email subresources dan aktivasi". Ini membantu menyingkirkan panggilan REST tambahan untuk sub-sumber daya dan itu tidak memengaruhi kerumitan klien yang berurusan dengan sub-sumber daya jika Anda menggunakan beberapa pustaka klien API JSON yang bagus.
qbd
Jadi satu permintaan GET pada server diterjemahkan menjadi satu atau lebih query aktual (tergantung pada berapa banyak sub-sumber daya yang dimasukkan) yang kemudian digabungkan menjadi satu objek sumber daya tunggal?
astreltsov
Bagaimana jika diperlukan lebih dari satu tingkat sarang?
astreltsov
Ya, dalam dbs relasional ini mungkin akan diterjemahkan ke beberapa kueri. Bersarang sewenang-wenang didukung oleh JSON API, dijelaskan di sini: jsonapi.org/format/#fetching-includes
qbd
0

Masalah utama di sini adalah, bagaimana logika bisnis dipanggil secara transparan ketika panggilan REST dibuat? Ini adalah masalah yang tidak langsung ditangani oleh REST.

Saya telah memecahkan ini dengan membuat layer manajemen data saya sendiri di atas penyedia ketekunan seperti JPA. Menggunakan model meta dengan anotasi khusus, kami dapat menggunakan logika bisnis yang sesuai ketika status entitas berubah. Ini memastikan bahwa terlepas dari bagaimana entitas entitas mengubah logika bisnis dipanggil. Itu membuat arsitektur Anda KERING dan juga logika bisnis Anda di satu tempat.

Menggunakan contoh di atas, kita dapat memanggil metode logika bisnis yang disebut validateName ketika bidang nama diubah menggunakan REST:

class User { 
      String name;
      String email;

      /**
       * This method will be transparently invoked when the value of name is changed
       * by REST.
       * The XorUpdate annotation becomes effective for PUT/POST actions
       */
      @XorPostChange
      public void validateName() {
        if(name == null) {
          throw new IllegalStateException("Name cannot be set as null");
        }
      }
    }

Dengan alat seperti yang Anda inginkan, semua yang perlu Anda lakukan adalah menjelaskan metode logika bisnis Anda dengan tepat.

codedabbler
sumber
0

Saya mengalami beberapa masalah dengan cara mengekspos model domain dengan cara yang berorientasi sumber daya.

Anda tidak boleh mengekspos model domain dengan cara yang berorientasi sumber daya. Anda harus mengekspos aplikasi dengan cara yang berorientasi sumber daya.

jika UI lebih berorientasi pada perintah, yang sering terjadi, maka kita harus memetakan antara perintah dan sumber daya di sisi klien dan kemudian kembali ke sisi API.

Tidak sama sekali - mengirim perintah ke sumber daya aplikasi yang berinteraksi dengan model domain.

setiap operasi pembaruan terhadap API REST dapat memetakan ke satu atau lebih panggilan prosedur Layanan Aplikasi, tergantung pada properti apa yang sedang diperbarui pada model sumber daya

Ya, meskipun ada cara yang sedikit berbeda untuk mengeja ini yang dapat membuat segalanya lebih sederhana; setiap operasi pembaruan terhadap api REST memetakan proses yang mengirimkan perintah ke satu atau lebih agregat.

setiap operasi pembaruan terlihat seperti atom ke klien REST API, tetapi tidak diimplementasikan seperti itu. Setiap panggilan Layanan Aplikasi dirancang sebagai transaksi terpisah. Memperbarui satu bidang pada model sumber daya dapat mengubah aturan validasi untuk bidang lain. Jadi kita perlu memvalidasi semua bidang model sumber daya bersama-sama untuk memastikan bahwa semua panggilan Layanan Aplikasi potensial valid sebelum kita mulai membuatnya. Memvalidasi satu set perintah sekaligus jauh lebih mudah daripada melakukan satu per satu. Bagaimana kita melakukannya pada klien yang bahkan tidak tahu perintah individual ada?

Anda mengejar ekor yang salah di sini.

Bayangkan: keluarkan REST dari gambar sepenuhnya. Bayangkan saja Anda sedang menulis antarmuka desktop untuk aplikasi ini. Lebih jauh mari kita bayangkan bahwa Anda memiliki persyaratan desain yang sangat baik, dan sedang mengimplementasikan UI berbasis tugas. Jadi pengguna mendapat antarmuka minimalis yang disesuaikan dengan sempurna untuk tugas yang sedang mereka kerjakan; pengguna menentukan beberapa input lalu tekan tombol "VERB!" tombol.

Apa yang terjadi sekarang? Dari perspektif pengguna, ini adalah tugas atom tunggal yang harus dilakukan. Dari perspektif domainModel, ini adalah sejumlah perintah yang dijalankan oleh agregat, di mana setiap perintah dijalankan dalam transaksi terpisah. Itu sepenuhnya tidak kompatibel! Kami membutuhkan sesuatu di tengah untuk menjembatani kesenjangan!

Sesuatu adalah "aplikasi".

Di jalur bahagia, aplikasi menerima beberapa DTO, dan mem-parsing objek itu untuk mendapatkan pesan yang dimengerti, dan menggunakan data dalam pesan untuk membuat perintah yang dibentuk dengan baik untuk satu atau lebih agregat. Aplikasi akan memastikan setiap perintah yang dikirim ke agregat terbentuk dengan baik (itulah lapisan anti korupsi yang sedang bekerja), dan itu akan memuat agregat, dan menyimpan agregat jika transaksi berhasil diselesaikan. Agregat akan memutuskan sendiri apakah perintahnya valid, mengingat statusnya saat ini.

Kemungkinan hasil - perintah semua berjalan dengan sukses - lapisan anti-korupsi menolak pesan - beberapa perintah berjalan dengan sukses, tetapi kemudian salah satu dari agregat mengeluh, dan Anda memiliki kemungkinan untuk dimitigasi.

Sekarang, bayangkan Anda memiliki aplikasi yang dibangun; bagaimana Anda berinteraksi dengannya dengan tenang?

  1. Klien mulai dengan deskripsi hypermedia dari status saat ini (yaitu: UI berbasis tugas), termasuk kontrol hypermedia.
  2. Klien mengirimkan representasi tugas (yaitu: DTO) ke sumber daya.
  3. Sumber daya mem-parsing permintaan HTTP yang masuk, mengambil representasi, dan menyerahkannya ke aplikasi.
  4. Aplikasi menjalankan tugas; dari sudut pandang sumber daya, ini adalah kotak hitam yang memiliki salah satu hasil berikut
    • aplikasi berhasil memperbarui semua agregat: sumber daya melaporkan keberhasilan kepada klien, mengarahkannya ke status aplikasi baru
    • lapisan anti-korupsi menolak pesan: sumber daya melaporkan kesalahan 4xx kepada klien (mungkin Permintaan Buruk), mungkin menyampaikan deskripsi masalah yang dihadapi.
    • aplikasi memperbarui beberapa agregat: sumber daya melaporkan kepada klien bahwa perintah diterima, dan mengarahkan klien ke sumber daya yang akan menyediakan representasi dari kemajuan perintah.

Diterima adalah cara biasa keluar ketika aplikasi akan menunda memproses pesan sampai setelah menanggapi klien - yang biasa digunakan ketika menerima perintah asinkron. Tetapi ini juga bekerja dengan baik untuk kasus ini, di mana sebuah operasi yang seharusnya menjadi atom membutuhkan mitigasi.

Dalam idiom ini, sumber daya mewakili tugas itu sendiri - Anda memulai contoh tugas baru dengan memposting representasi yang sesuai ke sumber daya tugas, dan sumber daya itu berinteraksi dengan aplikasi dan mengarahkan Anda ke status aplikasi berikutnya.

Dalam , hampir setiap saat Anda mengoordinasikan banyak perintah, Anda ingin berpikir dalam hal proses (alias proses bisnis, alias saga).

Ada ketidaksesuaian konseptual yang serupa dalam model baca. Sekali lagi, pertimbangkan antarmuka berbasis tugas; jika tugas memerlukan modifikasi beberapa agregat, maka UI untuk mempersiapkan tugas mungkin termasuk data dari sejumlah agregat. Jika skema sumber daya Anda adalah 1: 1 dengan agregat, itu akan sulit untuk diatur; sebagai gantinya, berikan sumber daya yang mengembalikan representasi data dari beberapa agregat, bersama dengan kontrol hypermedia yang memetakan hubungan "tugas awal" ke titik akhir tugas seperti dibahas di atas.

Lihat juga: REST in Practice oleh Jim Webber.

VoiceOfUnreason
sumber
Jika kami merancang API untuk berinteraksi dengan domain kami sesuai dengan kasus penggunaan kami .. Mengapa tidak merancang hal-hal sedemikian rupa sehingga Sagas tidak diperlukan sama sekali? Mungkin saya melewatkan sesuatu tetapi dengan membaca tanggapan Anda, saya benar-benar percaya REST tidak cocok dengan DDD dan lebih baik menggunakan prosedur jarak jauh (RPC). DDD adalah perilaku-sentris sedangkan REST adalah http-kata kerja centric. Mengapa tidak menghapus REST dari gambar dan mengekspos perilaku (perintah) di API? Setelah semua, mungkin mereka dirancang untuk memenuhi skenario penggunaan kasus dan prob bersifat transaksional. Apa keuntungan REST jika kita memiliki UI?
iberodev