รูปแบบของการใช้ตัวจัดการคำสั่งในการจัดการกับการคงอยู่ของภาษาที่ใช้งานได้อย่างแท้จริงซึ่งเราต้องการทำให้โค้ดที่เกี่ยวข้องกับ IO มีความบางที่สุด
เมื่อใช้การออกแบบที่ขับเคลื่อนด้วยโดเมนในภาษาเชิงวัตถุเป็นเรื่องปกติที่จะใช้รูปแบบคำสั่ง / ตัวจัดการเพื่อดำเนินการเปลี่ยนแปลงสถานะ ในการออกแบบตัวจัดการคำสั่งจะอยู่ด้านบนของวัตถุโดเมนของคุณและรับผิดชอบต่อตรรกะที่เกี่ยวข้องกับการคงอยู่ที่น่าเบื่อเช่นการใช้ที่เก็บข้อมูลและการเผยแพร่กิจกรรมโดเมน ตัวจัดการเป็นหน้าสาธารณะของโมเดลโดเมนของคุณ รหัสแอปพลิเคชันเช่น UI เรียกตัวจัดการเมื่อจำเป็นต้องเปลี่ยนสถานะของวัตถุโดเมน
ร่างใน 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);
}
}
document
วัตถุโดเมนเป็นผู้รับผิดชอบในการใช้กฎระเบียบทางธุรกิจ (เช่น "ผู้ใช้ควรได้รับอนุญาตให้ยกเลิกเอกสาร" หรือ "คุณไม่สามารถทิ้งเอกสารที่ได้รับแล้วทิ้ง") และสำหรับการสร้างเหตุการณ์โดเมนที่เราต้องการที่จะเผยแพร่ ( document.NewEvents
จะ เป็นIEnumerable<Event>
และอาจจะมีDocumentDiscarded
เหตุการณ์)
นี่คือการออกแบบที่ดี - ง่ายต่อการขยาย (คุณสามารถเพิ่มกรณีการใช้งานใหม่โดยไม่ต้องเปลี่ยนรูปแบบโดเมนของคุณโดยเพิ่มตัวจัดการคำสั่งใหม่) และไม่เชื่อเรื่องวิธีการเก็บวัตถุ (คุณสามารถสลับพื้นที่เก็บข้อมูล NHibernate สำหรับ Mongo ได้อย่างง่ายดาย พื้นที่เก็บข้อมูลหรือแลกเปลี่ยนผู้เผยแพร่ RabbitMQ สำหรับผู้เผยแพร่ EventStore) ซึ่งทำให้ง่ายต่อการทดสอบโดยใช้ fakes และ mocks นอกจากนี้ยังเป็นไปตามรูปแบบ / การแยกมุมมอง - ตัวจัดการคำสั่งไม่ทราบว่ากำลังถูกใช้โดยงานแบ็ตช์, GUI หรือ REST API
ในภาษาที่ใช้งานได้จริงอย่าง Haskell คุณอาจทำตัวเป็นตัวจัดการคำสั่งอย่างคร่าวๆดังนี้:
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"
นี่คือส่วนที่ฉันพยายามเข้าใจ โดยทั่วไปจะมีรหัส 'งานนำเสนอ' บางประเภทที่เรียกใช้ตัวจัดการคำสั่งเช่น GUI หรือ REST API ดังนั้นตอนนี้เรามีสองเลเยอร์ในโปรแกรมของเราซึ่งจำเป็นต้องทำ IO - ตัวจัดการคำสั่งและมุมมอง - ซึ่งเป็นสิ่งที่ไม่มีความใหญ่ใน Haskell
เท่าที่ฉันจะทำได้มีกองกำลังต่อต้านอยู่สองอย่างที่นี่: หนึ่งคือการแยกแบบ / มุมมองและอื่น ๆ คือความต้องการที่จะคงรูปแบบ ต้องมีรหัส IO เพื่อคงรูปแบบไว้ที่ใดที่หนึ่งแต่การแยกโมเดล / มุมมองบอกว่าเราไม่สามารถใส่มันในเลเยอร์การนำเสนอพร้อมกับรหัส IO อื่นทั้งหมด
แน่นอนในภาษา "ปกติ" IO สามารถ (และไม่) เกิดขึ้นได้ทุกที่ การออกแบบที่ดีนั้นกำหนดว่า IO ชนิดต่าง ๆ จะแยกกัน แต่คอมไพเลอร์ไม่บังคับใช้
ดังนั้น: เราจะกระทบยอดการแยกโมเดล / มุมมองด้วยความปรารถนาที่จะผลักดันรหัส IO ไปยังขอบสุดของโปรแกรมได้อย่างไรเมื่อโมเดลนั้นต้องคงอยู่? เราจะแยก IO สองประเภทที่ต่างกันได้อย่างไร แต่ยังคงอยู่ห่างจากรหัสบริสุทธิ์ทั้งหมด?
อัปเดต : เงินรางวัลจะหมดอายุในเวลาน้อยกว่า 24 ชั่วโมง ฉันไม่รู้สึกว่าคำตอบอย่างใดอย่างหนึ่งในปัจจุบันได้ตอบคำถามของฉันเลย @ ความคิดเห็นของ Flame Ptharien เกี่ยวกับacid-state
ดูเหมือนว่าจะมีแนวโน้ม แต่มันไม่ได้คำตอบและมันไม่มีรายละเอียด ฉันจะเกลียดจุดเหล่านี้เสียเปล่า!
acid-state
ดูดีมากขอบคุณสำหรับลิงค์นั้น ในแง่ของการออกแบบ API มันยังดูเหมือนว่าจะผูกพันกับIO
; คำถามของฉันเกี่ยวกับกรอบการติดตาที่เข้ากับสถาปัตยกรรมที่ใหญ่กว่าอย่างไร คุณรู้จักแอพพลิเคชั่นโอเพนซอร์ซที่ใช้acid-state
ร่วมกับเลเยอร์การนำเสนอและประสบความสำเร็จในการแยกทั้งสองอย่างออกจากกันหรือไม่?
Query
และวัดUpdate
นั้นห่างไกลจากความIO
จริง ฉันจะพยายามยกตัวอย่างง่ายๆในคำตอบ
acid-state