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:
- Haruskah semua kompleksitas ini ditangani oleh lapisan pemetaan REST-to-AppService (tebal)?
- Atau apakah saya kehilangan sesuatu dalam pemahaman saya tentang DDD / REST?
- Mungkinkah REST tidak praktis untuk mengekspos fungsionalitas model domain pada tingkat kompleksitas (yang cukup rendah) tertentu?
sumber
Jawaban:
Saya memiliki masalah yang sama dan "menyelesaikannya" dengan memodelkan sumber daya REST secara berbeda, misalnya:
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.
sumber
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:
Dengan alat seperti yang Anda inginkan, semua yang perlu Anda lakukan adalah menjelaskan metode logika bisnis Anda dengan tepat.
sumber
Anda tidak boleh mengekspos model domain dengan cara yang berorientasi sumber daya. Anda harus mengekspos aplikasi dengan cara yang berorientasi sumber daya.
Tidak sama sekali - mengirim perintah ke sumber daya aplikasi yang berinteraksi dengan model domain.
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.
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?
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 ddd , 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.
sumber