Làm thế nào mô hình sử dụng các trình xử lý lệnh để đối phó với sự bền bỉ phù hợp với một ngôn ngữ chức năng thuần túy, nơi chúng ta muốn làm cho mã liên quan đến IO càng mỏng càng tốt?
Khi triển khai Thiết kế hướng tên miền theo ngôn ngữ hướng đối tượng, người ta thường sử dụng mẫu Lệnh / Trình xử lý để thực hiện các thay đổi trạng thái. Trong thiết kế này, các trình xử lý lệnh nằm trên các đối tượng miền của bạn và chịu trách nhiệm cho logic liên quan đến tính bền vững nhàm chán như sử dụng kho lưu trữ và xuất bản các sự kiện miền. Các trình xử lý là bộ mặt công khai của mô hình miền của bạn; mã ứng dụng như UI gọi trình xử lý khi cần thay đổi trạng thái của các đối tượng miền.
Một bản phác thảo trong 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);
}
}
Các document
đối tượng miền có trách nhiệm thực hiện các quy tắc kinh doanh (như "người sử dụng nên có quyền loại bỏ các tài liệu" hoặc "bạn không thể loại bỏ một tài liệu đó đã bị loại bỏ") và để tạo ra các sự kiện miền chúng ta cần phải xuất bản ( document.NewEvents
sẽ là một IEnumerable<Event>
và có thể sẽ chứa một DocumentDiscarded
sự kiện).
Đây là một thiết kế đẹp - dễ dàng mở rộng (bạn có thể thêm các trường hợp sử dụng mới mà không thay đổi mô hình miền của mình, bằng cách thêm trình xử lý lệnh mới) và không biết cách các đối tượng được duy trì (bạn có thể dễ dàng trao đổi kho lưu trữ NHibernate cho Mongo kho lưu trữ hoặc trao đổi nhà xuất bản RabbitMQ cho nhà xuất bản EventStore) giúp dễ dàng kiểm tra bằng cách sử dụng hàng giả và giả. Nó cũng tuân theo sự phân tách mô hình / khung nhìn - trình xử lý lệnh không biết liệu nó được sử dụng bởi một công việc hàng loạt, GUI hay API REST.
Trong một ngôn ngữ hoàn toàn có chức năng như Haskell, bạn có thể mô hình hóa trình xử lý lệnh như thế này:
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"
Đây là phần tôi đang đấu tranh để hiểu. Thông thường, sẽ có một số loại mã 'trình bày' gọi vào trình xử lý lệnh, như GUI hoặc API REST. Vì vậy, bây giờ chúng tôi có hai lớp trong chương trình của chúng tôi cần thực hiện IO - trình xử lý lệnh và khung nhìn - đó là một điều không lớn trong Haskell.
Theo như tôi có thể nhận ra, có hai lực lượng đối lập ở đây: một là tách biệt mô hình / khung nhìn và hai là cần phải duy trì mô hình. Cần phải có mã IO để duy trì mô hình ở đâu đó , nhưng tách mô hình / khung nhìn nói rằng chúng ta không thể đặt nó trong lớp trình bày với tất cả các mã IO khác.
Tất nhiên, trong một ngôn ngữ "bình thường", IO có thể (và không) xảy ra ở bất cứ đâu. Thiết kế tốt chỉ ra rằng các loại IO khác nhau được giữ riêng biệt, nhưng trình biên dịch không thực thi nó.
Vậy: làm thế nào để chúng ta điều hòa sự tách biệt mô hình / khung nhìn với mong muốn đẩy mã IO đến tận cùng của chương trình, khi mô hình cần được duy trì? Làm thế nào để chúng ta giữ hai loại IO khác nhau , nhưng vẫn cách xa tất cả các mã thuần túy?
Cập nhật : Tiền thưởng hết hạn sau chưa đầy 24 giờ. Tôi không cảm thấy rằng một trong những câu trả lời hiện tại đã giải quyết được câu hỏi của tôi. Nhận xét về ngọn lửa của @ Ptharien acid-state
có vẻ đầy hứa hẹn, nhưng đó không phải là câu trả lời và nó thiếu chi tiết. Tôi ghét những điểm này để lãng phí!
acid-state
trông khá tuyệt, cảm ơn vì liên kết đó Về mặt thiết kế API, nó dường như vẫn bị ràng buộc IO
; câu hỏi của tôi là làm thế nào một khung kiên trì phù hợp với một kiến trúc lớn hơn. Bạn có biết bất kỳ ứng dụng nguồn mở nào sử dụng acid-state
cùng với một lớp trình bày và thành công trong việc giữ hai phần riêng biệt không?
Query
và Update
monads được khá xa rời IO
, trên thực tế. Tôi sẽ cố gắng đưa ra một ví dụ đơn giản trong một câu trả lời.
acid-state
dường như gần với những gì bạn đang mô tả .