Apakah masih valid untuk berbicara tentang model anemia dalam konteks pemrograman fungsional?

40

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

Pavel Voronin
sumber
1
Jika Anda mengatakan apa yang saya pikir Anda katakan di sini, DTO adalah anemia, tetapi objek kelas satu di DDD, dan bahwa ada pemisahan alami antara DTO dan layanan yang memprosesnya. Saya setuju secara prinsip. Begitu juga dengan posting blog ini , rupanya.
Robert Harvey
2
Sangat terkait, jika bukan duplikat langsung: Mengapa model domain anemia dianggap buruk di OOP, tetapi sangat penting dalam FP?
Robert Harvey
1
"apakah istilah 'model anemia' masih ada dalam kasus itu" Jawaban singkatnya, istilah model anemia ini diciptakan dalam konteks OO. Berbicara tentang model anemia dalam konteks FP tidak masuk akal sama sekali. Mungkin ada persamaan dalam arti menggambarkan apa itu FP idiomatik, tetapi tidak ada hubungannya dengan model anemia.
plalx
5
Eric Evans pernah ditanya apa yang dia katakan kepada orang-orang yang menuduhnya bahwa apa yang dia jelaskan dalam bukunya hanya desain berorientasi objek yang baik, dan dia menjawab bahwa itu bukan tuduhan, itu kebenaran, DDD hanya OOD yang baik, dia hanya menulis turunkan beberapa resep dan pola dan beri mereka nama, sehingga lebih mudah untuk mengikuti mereka dan membicarakannya. Jadi, tidak mengherankan jika DDD terhubung dengan OOD. Pertanyaan yang lebih luas adalah, apa persimpangan dan perbedaan antara OOD dan FPD, meskipun Anda harus mendefinisikan apa yang Anda maksud dengan "pemrograman fungsional" terlebih dahulu.
Jörg W Mittag
2
@ JörgWMittag: Maksud Anda selain definisi yang biasa? Ada banyak platform ilustratif, Haskell menjadi yang paling jelas.
Robert Harvey

Jawaban:

24

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 Accountkelas yang tidak dapat digunakan untuk mengaktifkan atau menonaktifkan akun atau bahkan mencari informasi tentang akun kecuali ditangani melalui AccountManagerkelas. 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 UserIDdiwakili 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 melalui newtype:

newtype UserID = UserID String

Ini mendefinisikan UserIDfungsi yang ketika diberi Stringmembangun nilai yang diperlakukan seperti sebuah UserIDoleh sistem jenis, tapi yang masih hanya Stringpada saat runtime. Sekarang fungsi dapat menyatakan bahwa mereka membutuhkan UserIDbukan string; menggunakan UserIDs di mana Anda sebelumnya menggunakan penjaga string terhadap kode yang menggabungkan dua UserIDs bersama-sama. Jenis sistem menjamin itu tidak bisa terjadi, tidak perlu tes.

Kelemahan di sini adalah bahwa kode masih dapat mengambil Stringsuka suka "hello"dan membangun UserIDdari itu. Langkah selanjutnya termasuk membuat fungsi "konstruktor pintar" yang ketika diberi string memeriksa beberapa invarian dan hanya mengembalikan a UserIDjika mereka puas. Kemudian UserIDkonstruktor "bodoh" dijadikan pribadi sehingga jika klien menginginkannya, UserIDmereka harus menggunakan konstruktor pintar, sehingga mencegah UserID yang cacat muncul.

Bahkan langkah lebih lanjut mendefinisikan UserIDtipe data sedemikian rupa sehingga tidak mungkin untuk membangun yang salah atau "tidak layak", cukup dengan definisi. Misalnya, mendefinisikan UserIDsebagai daftar digit:

data Digit = Zero | One | Two | Three | Four | Five | Six | Seven | Eight | Nine
data UserID = UserID [Digit]

Untuk membangun UserIDdaftar digit harus disediakan. Dengan definisi ini, mudah untuk menunjukkan bahwa tidak mungkin UserIDada 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.

Mendongkrak
sumber
2
Dan bagaimana dengan agregat dan akar agregat yang menjaga invariantы, apakah yang terakhir juga dapat diekspresikan dan mudah dipahami oleh pengembang setelahnya? Bagi saya, bagian paling berharga dari DDD adalah pemetaan langsung model bisnis ke kode. Dan jawaban Anda persis tentang itu.
Pavel Voronin
2
Pidato yang bagus, tetapi tidak ada jawaban untuk pertanyaan OP.
SerG
10

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.

Karl Bielefeldt
sumber
2
"Untuk sebagian besar, ketidakmampuan membuatnya tidak perlu untuk memadatkan erat fungsi Anda dengan data Anda sebagai pendukung OOP.": Alasan lain untuk menggabungkan data dan prosedur adalah menerapkan polimorfisme melalui pengiriman yang dinamis.
Giorgio
2
Keuntungan utama dari perilaku kopling dengan data dalam konteks DDD adalah menyediakan antarmuka terkait bisnis yang bermakna; itu selalu ada di tangan. Kami memiliki cara alami untuk mendokumentasikan kode sendiri (setidaknya itu yang biasa saya lakukan), dan itulah kunci keberhasilan komunikasi dengan pakar bisnis. Lalu bagaimana hal itu dicapai dalam FP? Mungkin bantuan pipa, tapi apa lagi? Bukankah sifat generik dari FP membuat persyaratan bisnis lebih sulit untuk direkayasa ulang dari kode?
Pavel Voronin
7

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.RaiseSalarymungkin bermutasi pada salarybidang Employeeinstance, yang seharusnya tidak dapat diselesaikan secara publik.

Di FP, mutasi dihindari, jadi Anda akan menerapkan perilaku ini dengan membuat RaiseSalaryfungsi yang mengambil Employeeinstance yang ada dan mengembalikan instance baru Employee dengan gaji baru. Jadi tidak ada mutasi yang terlibat: hanya membaca dari objek asli dan membuat objek baru. Untuk alasan ini, RaiseSalaryfungsi seperti itu tidak perlu didefinisikan sebagai metode di Employeekelas, tetapi bisa hidup di mana saja.

Dalam hal ini, menjadi wajar untuk memisahkan data dari perilaku: satu struktur mewakili Employeedata 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): Employeemungkin 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.

la-yumba
sumber
-1

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.

Michael Borgwardt
sumber
5
Ya, pada dasarnya itulah yang dikatakan OP dalam pertanyaannya. Anda berdua sepertinya telah melewatkan titik bahwa Anda masih dapat memiliki komposisi fungsional.
Robert Harvey
-3

Saya ingin tahu bagaimana DDD cocok dengan paradigma FP

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.)

dan apakah istilah 'model anemia' masih ada dalam kasus itu.

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.

Darien
sumber
1
Model anemia akan terjadi ketika Anda meneruskan tipe data abstrak, seperti tupel, catatan, atau daftar, ke berbagai fungsi untuk diproses. Anda tidak perlu sesuatu yang hampir sama eksotisnya dengan "fungsi yang tidak dijalankan" (apa pun itu).
Robert Harvey
Oleh karena itu tanda kutip di sekitar "fungsi", untuk menekankan betapa tidak pantas label menjadi ketika mereka mengalami anemia.
Darien
Jika Anda menjadi ironis, itu agak halus.
Robert Harvey