Sebagian besar pola desain taktis DDD milik paradigma berorientasi objek, dan model anemia menggambarkan situasi ketika semua logika bisnis dimasukkan ke dalam layanan daripada objek sehingga menjadikan mereka semacam DTO. Dengan kata lain model anemik adalah sinonim dari gaya prosedural, yang tidak disarankan untuk model kompleks.
Saya tidak terlalu berpengalaman dalam pemrograman fungsional murni, namun saya ingin tahu bagaimana DDD cocok dengan paradigma FP dan apakah istilah 'model anemia' masih ada dalam kasus itu.
Pembaruan : Recentry buku dan video yang diterbitkan tentang subjek
functional-programming
domain-driven-design
Pavel Voronin
sumber
sumber
Jawaban:
Cara masalah "model anemia" dijelaskan tidak diterjemahkan dengan baik ke FP sebagaimana adanya. Pertama, perlu digeneralisasikan secara tepat. Pada intinya, model anemia adalah model yang berisi pengetahuan tentang cara menggunakannya dengan benar yang tidak dienkapsulasi oleh model itu sendiri. Sebaliknya, pengetahuan itu tersebar di sekitar tumpukan layanan terkait. Layanan-layanan itu seharusnya hanya menjadi klien model, tetapi karena anemia mereka bertanggung jawab untuk itu. Misalnya, pertimbangkan
Account
kelas yang tidak dapat digunakan untuk mengaktifkan atau menonaktifkan akun atau bahkan mencari informasi tentang akun kecuali ditangani melaluiAccountManager
kelas. Akun tersebut harus bertanggung jawab atas operasi dasar di atasnya, bukan kelas manajer eksternal.Dalam pemrograman fungsional, masalah serupa terjadi ketika tipe data tidak secara akurat mewakili apa yang seharusnya mereka modelkan. Misalkan kita perlu mendefinisikan tipe yang mewakili ID pengguna. Definisi "anemia" akan menyatakan bahwa ID pengguna adalah string. Itu layak secara teknis, tetapi mengalami masalah besar karena ID pengguna tidak digunakan seperti string arbitrer. Tidak masuk akal untuk menggabungkan mereka atau memotong substring dari mereka, Unicode seharusnya tidak terlalu penting, dan mereka harus mudah disematkan dalam URL dan konteks lain dengan karakter yang ketat dan batasan format.
Memecahkan masalah ini biasanya terjadi dalam beberapa tahap. Potongan pertama yang sederhana adalah untuk mengatakan, "Ya, a
UserID
diwakili setara dengan string, tetapi mereka berbeda jenis dan Anda tidak dapat menggunakan yang mana Anda mengharapkan yang lain." Haskell (dan beberapa bahasa fungsional lainnya yang diketik) menyediakan fitur ini melaluinewtype
:Ini mendefinisikan
UserID
fungsi yang ketika diberiString
membangun nilai yang diperlakukan seperti sebuahUserID
oleh sistem jenis, tapi yang masih hanyaString
pada saat runtime. Sekarang fungsi dapat menyatakan bahwa mereka membutuhkanUserID
bukan string; menggunakanUserID
s di mana Anda sebelumnya menggunakan penjaga string terhadap kode yang menggabungkan duaUserID
s bersama-sama. Jenis sistem menjamin itu tidak bisa terjadi, tidak perlu tes.Kelemahan di sini adalah bahwa kode masih dapat mengambil
String
suka suka"hello"
dan membangunUserID
dari itu. Langkah selanjutnya termasuk membuat fungsi "konstruktor pintar" yang ketika diberi string memeriksa beberapa invarian dan hanya mengembalikan aUserID
jika mereka puas. KemudianUserID
konstruktor "bodoh" dijadikan pribadi sehingga jika klien menginginkannya,UserID
mereka harus menggunakan konstruktor pintar, sehingga mencegah UserID yang cacat muncul.Bahkan langkah lebih lanjut mendefinisikan
UserID
tipe data sedemikian rupa sehingga tidak mungkin untuk membangun yang salah atau "tidak layak", cukup dengan definisi. Misalnya, mendefinisikanUserID
sebagai daftar digit:Untuk membangun
UserID
daftar digit harus disediakan. Dengan definisi ini, mudah untuk menunjukkan bahwa tidak mungkinUserID
ada yang tidak dapat direpresentasikan dalam URL. Mendefinisikan model data seperti ini di Haskell sering dibantu oleh fitur sistem tipe canggih seperti Jenis Data dan Generalized Algebraic Data Types (GADTs) , yang memungkinkan sistem tipe untuk menentukan dan membuktikan lebih banyak invarian tentang kode Anda. Ketika data dipisahkan dari perilaku, definisi data Anda adalah satu-satunya cara Anda harus menegakkan perilaku.sumber
Untuk sebagian besar, ketidakmampuan membuatnya tidak perlu untuk pasangan erat fungsi Anda dengan data Anda sebagai pendukung OOP. Anda dapat membuat salinan sebanyak yang Anda suka, bahkan membuat struktur data turunan, dalam kode yang jauh dari kode aslinya, tanpa takut struktur data asli secara tak terduga berubah dari bawah Anda.
Namun, cara yang lebih baik untuk membuat perbandingan ini mungkin untuk melihat fungsi mana yang Anda alokasikan ke lapisan model versus lapisan layanan . Meskipun tidak terlihat sama seperti di OOP, itu adalah kesalahan umum dalam FP untuk mencoba menjejalkan apa yang seharusnya menjadi beberapa level abstraksi ke dalam satu fungsi.
Sejauh yang saya tahu, tidak ada yang menyebutnya model anemia, karena itu istilah OOP, tetapi efeknya sama. Anda dapat dan harus menggunakan kembali fungsi umum di mana pun berlaku, tetapi untuk operasi yang lebih kompleks atau khusus aplikasi, Anda juga harus menyediakan serangkaian fungsi yang kaya hanya untuk bekerja dengan model Anda. Membuat lapisan abstraksi yang tepat adalah desain yang baik dalam paradigma apa pun.
sumber
Saat menggunakan DDD di OOP, salah satu alasan utama untuk menempatkan logika bisnis dalam objek domain itu sendiri adalah bahwa logika bisnis biasanya diterapkan dengan memutasikan keadaan objek. Ini terkait dengan enkapsulasi:
Employee.RaiseSalary
mungkin bermutasi padasalary
bidangEmployee
instance, yang seharusnya tidak dapat diselesaikan secara publik.Di FP, mutasi dihindari, jadi Anda akan menerapkan perilaku ini dengan membuat
RaiseSalary
fungsi yang mengambilEmployee
instance yang ada dan mengembalikan instance baruEmployee
dengan gaji baru. Jadi tidak ada mutasi yang terlibat: hanya membaca dari objek asli dan membuat objek baru. Untuk alasan ini,RaiseSalary
fungsi seperti itu tidak perlu didefinisikan sebagai metode diEmployee
kelas, tetapi bisa hidup di mana saja.Dalam hal ini, menjadi wajar untuk memisahkan data dari perilaku: satu struktur mewakili
Employee
data as (benar-benar anemia), sementara satu (atau beberapa) modul berisi fungsi yang beroperasi pada data tersebut (menjaga imutabilitas).Perhatikan bahwa ketika Anda menggabungkan data dan perilaku seperti dalam DDD, Anda biasanya melanggar Prinsip Tanggung Jawab Tunggal (SRP):
Employee
mungkin perlu diubah jika aturan perubahan gaji berubah; tetapi mungkin juga perlu berubah jika aturan untuk menghitung bonus EOY berubah. Dengan pendekatan yang dipisahkan, ini tidak terjadi, karena Anda dapat memiliki beberapa modul, masing-masing dengan satu tanggung jawab.Jadi, seperti biasa pendekatan FP memberikan modularitas / kompabilitas yang lebih besar.
sumber
Saya pikir inti dari masalah ini adalah bahwa model anemia dengan semua logika domain dalam layanan yang beroperasi pada model pada dasarnya adalah pemrograman prosedural - yang bertentangan dengan pemrograman OO "nyata" di mana Anda memiliki objek yang "pintar" dan tidak hanya berisi data tetapi juga logika yang paling erat terkait dengan data.
Dan kontras yang sama ada dengan pemrograman fungsional: "nyata" FP berarti menggunakan fungsi sebagai entitas kelas satu, yang dilewatkan sebagai parameter, serta dibangun dengan cepat dan dikembalikan sebagai nilai kembali. Tetapi ketika Anda gagal menggunakan semua kekuatan itu dan hanya memiliki fungsi yang beroperasi pada struktur data yang diedarkan di antara mereka, maka Anda berakhir di tempat yang sama: Anda pada dasarnya melakukan pemrograman prosedural.
sumber
Saya pikir memang demikian, tetapi sebagian besar sebagai pendekatan taktis untuk transisi antara Objek Nilai yang tidak berubah atau sebagai cara untuk memicu metode pada entitas. (Di mana sebagian besar logika masih hidup dalam entitas.)
Nah, jika Anda bermaksud "dengan cara yang analog dengan OOP tradisional", maka itu membantu untuk mengabaikan detail implementasi yang biasa dan kembali ke dasar: Bahasa apa yang digunakan oleh pakar domain Anda? Apa maksud Anda menangkap dari pengguna Anda?
Misalkan mereka berbicara tentang proses chaining dan fungsi bersama, maka tampaknya fungsi (atau setidaknya objek "do-er") pada dasarnya adalah objek domain Anda!
Jadi dalam skenario itu, "model anemia" mungkin akan terjadi ketika "fungsi" Anda sebenarnya tidak dapat dieksekusi, dan sebaliknya hanya rasi bintang metadata yang ditafsirkan oleh Layanan yang melakukan pekerjaan nyata.
sumber