Kalıcılıkla başa çıkmak için komut işleyicilerini kullanma şekli, IO ile ilgili kodu olabildiğince inceltmek istediğimiz tamamen işlevsel bir dile nasıl sığar?
Etki Alanına Dayalı Tasarım'ı nesne yönelimli bir dilde uygularken, durum değişikliklerini yürütmek için Komut / İşleyici desenini kullanmak yaygındır . Bu tasarımda, komut işleyicileri etki alanı nesnelerinizin üstünde bulunur ve depoları kullanmak ve etki alanı olaylarını yayınlamak gibi sıkıcı kalıcılıkla ilgili mantıktan sorumludur. İşleyiciler, alan adı modelinizin genel yüzüdür; kullanıcı arayüzü gibi uygulama kodu, etki alanı nesnelerinin durumunu değiştirmesi gerektiğinde işleyicileri çağırır.
C # 'da bir eskiz:
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);
}
}
document
Etki alanı nesnesi (( "Zaten atılır oldu bir belgeyi iptal edemez" veya "kullanıcı belgeyi atmak iznine sahip olmalıdır" gibi) iş kurallarını uygulamaktan ve yayınladığımız gereken alan olayları oluşturmaktan sorumlu document.NewEvents
olur IEnumerable<Event>
bir DocumentDiscarded
etkinlik olabilir ve muhtemelen bir etkinlik içerir ).
Bu güzel bir tasarımdır - genişletmek kolaydır (etki alanı modelinizi değiştirmeden yeni komut işleyicileri ekleyerek yeni kullanım örnekleri ekleyebilirsiniz) ve nesnelerin nasıl kalıcı olduğuna dair agnostiktir (bir Moğol için NHibernate deposunu kolayca değiştirebilirsiniz) sahte veya alay kullanarak test etmeyi kolaylaştıran bir EventStore yayıncısı için bir RabbitMQ yayıncısını değiştirin). Ayrıca model / görünüm ayrımına da uyar - komut işleyicinin toplu iş, GUI veya REST API tarafından kullanılıp kullanılmadığı hakkında hiçbir fikri yoktur.
Haskell gibi tamamen işlevsel bir dilde, komut işleyicisini kabaca şu şekilde modelleyebilirsiniz:
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"
İşte anlamaya çalıştığım kısım. Genellikle, GUI veya REST API gibi komut işleyicisini çağıran bir çeşit 'sunum' kodu olacaktır. Şimdi programımızda, Haskell'de büyük bir hayır olan IO - komut işleyicisi ve görünüm - yapması gereken iki katman var.
Yapabildiğim kadarıyla, burada iki karşıt güç var: biri model / görüş ayrımı ve diğeri de modeli devam ettirme ihtiyacı. Modeli bir yerde devam ettirmek için IO kodu olması gerekiyor , ancak model / görünüm ayrımı, onu diğer tüm IO koduyla birlikte sunum katmanına koyamayacağımızı söylüyor.
Tabii ki, "normal" bir dilde, IO her yerde olabilir (ve olur). İyi tasarım, farklı IO tiplerinin ayrı tutulmasını gerektirir, ancak derleyici bunu zorlamaz.
Öyleyse: modelin kalıcı olması gerektiğinde, IO kodunu programın en uç noktasına itme arzusu ile model / görünüm ayrımını nasıl uzlaştırırız? İki farklı G / Ç türünü nasıl ayrı tutarız , ancak yine de tüm saf kodlardan nasıl uzak tutarız?
Güncelleme : Ödülün süresi 24 saatten az sürer. Şu anki cevapların hiçbirinin sorumu ele aldığını hissetmiyorum. @ Ptharien's Flame'ın hakkındaki yorumu acid-state
umut verici görünüyor, ancak bu bir cevap değil ve ayrıntılı olarak eksik. Bu noktaların boşa gitmesinden nefret ederim!
acid-state
harika görünüyor, bu bağlantı için teşekkürler. API tasarımı açısından hala buna bağlı gibi görünmektedir IO
; sorum bir kalıcılık çerçevesinin daha büyük bir mimariye nasıl uyduğu ile ilgili. acid-state
Bir sunum katmanının yanında kullanılan ve bu ikisini ayrı tutmayı başaran açık kaynaklı uygulamalar biliyor musunuz ?
Query
Ve Update
monads oldukça uzak kaldırılır IO
aslında. Bir cevapta basit bir örnek vermeye çalışacağım.
acid-state
tanımladığınız şeye yakın görünüyor .