지속성을 처리하기 위해 명령 처리기를 사용하는 패턴은 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
) 이벤트를.
이것은 멋진 디자인입니다-확장하기 쉽고 (새로운 명령 핸들러를 추가하여 도메인 모델을 변경하지 않고 새로운 사용 사례를 추가 할 수 있습니다) 객체가 어떻게 유지되는지에 대해 불가지론 적입니다 (Mongo를 위해 NHibernate 저장소를 쉽게 바꿀 수 있습니다) 저장소 또는 또는 RabbitMQ 게시자를 EventStore 게시자로 교체) 가짜 및 모의를 사용하여 쉽게 테스트 할 수 있습니다. 또한 모델 / 뷰 분리를 준수합니다. 명령 핸들러는 배치 작업, 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 시간 이내에 만료됩니다. 나는 현재 답변 중 하나가 내 질문을 전혀 다루지 않았다고 생각하지 않습니다. @ Ptharien 's Flame의 의견은 acid-state
유망한 것으로 보이지만 답변이 아니며 세부 정보가 부족합니다. 이 포인트가 낭비되는 것을 싫어합니다!
acid-state
그 링크에 감사드립니다. API 디자인의 관점에서는 여전히 바인딩 된 것처럼 보입니다 IO
. 내 질문은 지속성 프레임 워크가 더 큰 아키텍처에 어떻게 적용되는지에 관한 것입니다. acid-state
프리젠 테이션 레이어와 함께 사용 하고 두 개를 분리하여 유지하는 데 성공한 오픈 소스 응용 프로그램에 대해 알고 있습니까?
Query
및 Update
모나드는 꽤 멀리에서 제거됩니다 IO
사실. 대답에 간단한 예를 제시하려고 노력할 것입니다.
acid-state
당신이 묘사하는 것에 가깝습니다 .