Bagaimana pola penggunaan penangan perintah untuk menangani ketekunan cocok dengan bahasa murni fungsional, di mana kami ingin membuat kode terkait IO setipis mungkin?
Saat menerapkan Desain Berbasis Domain dalam bahasa berorientasi objek, biasanya menggunakan pola Command / Handler untuk mengeksekusi perubahan status. Dalam desain ini, penangan perintah duduk di atas objek domain Anda, dan bertanggung jawab atas logika terkait kegigihan yang membosankan seperti menggunakan repositori dan menerbitkan peristiwa domain. Penangan adalah wajah publik dari model domain Anda; kode aplikasi seperti UI memanggil penangan ketika perlu mengubah status objek domain.
Sebuah sketsa dalam C #:
public class DiscardDraftDocumentCommandHandler : CommandHandler<DiscardDraftDocument>
{
IDraftDocumentRepository _repo;
IEventPublisher _publisher;
public DiscardDraftCommandHandler(IDraftDocumentRepository repo, IEventPublisher publisher)
{
_repo = repo;
_publisher = publisher;
}
public override void Handle(DiscardDraftDocument command)
{
var document = _repo.Get(command.DocumentId);
document.Discard(command.UserId);
_publisher.Publish(document.NewEvents);
}
}
The document
domain objek bertanggung jawab untuk melaksanakan aturan bisnis (seperti "pengguna harus memiliki izin untuk membuang dokumen" atau "Anda tidak bisa membuang dokumen yang sudah dibuang") dan untuk menghasilkan peristiwa domain kita perlu untuk menerbitkan ( document.NewEvents
akan menjadi IEnumerable<Event>
dan mungkin akan berisi DocumentDiscarded
acara).
Ini adalah desain yang bagus - mudah untuk diperluas (Anda dapat menambahkan kasus penggunaan baru tanpa mengubah model domain Anda, dengan menambahkan penangan perintah baru) dan agnostik mengenai bagaimana objek bertahan (Anda dapat dengan mudah menukar repositori NHibernate untuk Mongo repositori, atau tukar penerbit RabbitMQ dengan penerbit EventStore) yang membuatnya mudah untuk diuji menggunakan tipuan dan cemoohan. Itu juga mematuhi pemisahan model / tampilan - penangan perintah tidak tahu apakah itu digunakan oleh pekerjaan batch, GUI, atau API REST.
Dalam bahasa yang murni fungsional seperti Haskell, Anda dapat memodelkan penangan perintah secara kasar seperti ini:
newtype CommandHandler = CommandHandler {handleCommand :: Command -> IO Result)
data Result a = Success a | Failure Reason
type Reason = String
discardDraftDocumentCommandHandler = CommandHandler handle
where handle (DiscardDraftDocument documentID userID) = do
document <- loadDocument documentID
let result = discard document userID :: Result [Event]
case result of
Success events -> publishEvents events >> return result
-- in an event-sourced model, there's no extra step to save the document
Failure _ -> return result
handle _ = return $ Failure "I expected a DiscardDraftDocument command"
Inilah bagian yang saya berjuang untuk mengerti. Biasanya, akan ada semacam kode 'presentasi' yang memanggil pemanggil perintah, seperti GUI atau API REST. Jadi sekarang kita memiliki dua lapisan dalam program kita yang perlu dilakukan IO - pengendali perintah dan tampilan - yang merupakan no-no besar di Haskell.
Sejauh yang saya bisa tahu, ada dua kekuatan yang berlawanan di sini: satu adalah model / pandangan pemisahan dan yang lainnya adalah kebutuhan untuk mempertahankan model. Perlu ada kode IO untuk mempertahankan model di suatu tempat , tetapi pemisahan model / view mengatakan bahwa kita tidak bisa meletakkannya di lapisan presentasi dengan semua kode IO lainnya.
Tentu saja, dalam bahasa "normal", IO dapat (dan memang) terjadi di mana saja. Desain yang baik menentukan bahwa berbagai jenis IO disimpan terpisah, tetapi kompiler tidak menegakkannya.
Jadi: bagaimana kita mendamaikan pemisahan model / tampilan dengan keinginan untuk mendorong kode IO ke tepi program, ketika model perlu dipertahankan? Bagaimana kita memisahkan dua jenis IO , tetapi masih jauh dari semua kode murni?
Pembaruan : Karunia berakhir dalam waktu kurang dari 24 jam. Saya tidak merasa bahwa salah satu jawaban saat ini telah menjawab pertanyaan saya sama sekali. Komentar @ Ptharien Flame tentang acid-state
tampaknya menjanjikan, tapi itu bukan jawaban dan kurang detail. Aku benci poin-poin ini sia-sia!
acid-state
terlihat cukup bagus, terima kasih untuk tautannya. Dalam hal desain API tampaknya masih terikat IO
; pertanyaan saya adalah tentang bagaimana kerangka kegigihan cocok dengan arsitektur yang lebih besar. Apakah Anda tahu ada aplikasi open-source yang digunakan di acid-state
samping lapisan presentasi, dan berhasil memisahkan keduanya?
Query
dan Update
monad yang cukup jauh dari IO
, sebenarnya. Saya akan mencoba memberikan contoh sederhana dalam jawaban.
acid-state
tampaknya dekat dengan apa yang Anda gambarkan .