【问题标题】:Changing aggregate behavior during its lifetime在其生命周期内改变聚合行为
【发布时间】:2018-05-25 14:14:30
【问题描述】:

假设我们有一个具有生命周期的聚合,这样它就可以在其生命周期内改变其行为。在它生命的第一阶段,它可以做一些事情,在第二阶段,它可以做其他事情。

我想听听关于我们应该如何限制聚合在每个阶段可以执行的操作的意见。 为了使其更具体一些,让我们以金融交易为例。

  • 交易者创建交易,通知合同及其价格。
  • 风险经理验证交易,并说明理由。
  • BackOffice 可以将交易提交到分类帐,提供会计信息。
  • 交易提交后,会计信息不可更改。

交易显然有 3 个不同的阶段,我将其称为 TypedValidatedSubmitted

我的第一个想法是用InvalidOperationExceptions 污染聚合,我真的不喜欢:

public class Trade 
{
    private enum State { Typed, Validated, Submited }
    private State _state = State.Typed;

    public Guid Id { get; }
    public Contract Contract { get; }
    public decimal Price { get; }

    public Trade (Guid id, Contract contract, decimal price) { ... }

    private string _validationReason = null;
    private AccountingInformation _accInfo = null;

    public void Validate(string reason)  {
        if (_state != State.Typed)
            throw new InvalidOperationException (..)
        ...
        _validationReason = reason;
        _state = State.Validated;
    }

    public string GetValidationReason() {
        if (_state == State.Typed)
            throw new InvalidOperationException (..)
        return _validationReason;
    }

    public void SubmitToLedger(AccountingInformation info) {
        if ((_state != State.Validated))
            throw new InvalidOperationException (..)
        ...
    }

    public AccountingInfo GetAccountingInfo() { .. }
}

我可以执行Maybe pattern 之类的操作,以避免Get... 方法出现异常。但这不适用于行为方法(ValidateSubmitToLedger 等)

奇怪的是,如果我要使用函数式语言(例如 F#),我可能会为每个状态创建不同的类型。

type TypedTrade = { Id : Guid;  Contract: Contract; Price : decimal }

type ValidatedTrade = {  Id : Guid;  
                        Contract: Contract; 
                        Price : decimal;
                        ValidationReason : string}

type SubmittedTrade =  {  Id : Guid;  
                        Contract: Contract; 
                        Price : decimal;
                        ValidationReason : string; 
                        AccInfo : AccountingInfo }

// TypedTrade -> string -> ValidatedTrade
let validateTrade typedTrade reason = 
    ...
    { Id = typedTrade.Id; Contract = typedTrade.Contract;
            Price = typedTrade.Price; Reason = reason }

// ValidatedTrade -> AccountingInfo -> SubmittedTrade
let submitTrade validatedTrade accInfo = 
    ...
    { Id = validatedTrade.Id; 
    Contract = validatedTrade.Contract;
    Price = validatedTrade.Price; 
    Reason = validatedTrad.Reason;
    AccInfo = accInfo }

问题会优雅地消失。但要在 OO 中做到这一点,我必须使我的聚合不可变,并且可能创建某种 o 层次结构(我必须在其中隐藏基本方法!?哎呀!)。

我只是想知道你们在这些情况下的做法,以及是否有更好的方法。

【问题讨论】:

    标签: oop design-patterns domain-driven-design


    【解决方案1】:

    我喜欢为每个州设置不同类型的想法。在我看来,它是一个干净的设计。从逻辑上看,新创建的交易肯定不同于提交的交易。

    public Interface ITrade
    {
        Guid Id { get; }
        Contract Contract { get; }
        decimal Price { get; }
    }
    
    public class Trade : ITrade
    {
        public Trade(Guid id, Contract contract, decimal price)
        {
            Id = id;
            Contract = contract;
            Price = price;
        }
    
        Guid Id { get; }
        Contract Contract { get; }
        decimal Price { get; }
    
        public ValidatedTrade Validate(string reason)
        {
            return new ValidatedTrade(this, reason);
        }
    }
    
    public class ValidatedTrade : ITrade
    {
        private ITrade trade;
        private string validationReason;
    
        public ValidatedTrade(Trade trade, string validationReason)
        {
            this.trade = trade;
            this.validationReason = validationReason;
        }
    
        Guid Id { get { return trade.Id; } }
        Contract Contract { get { return trade.Contract ; } }
        decimal Price { get { return trade.Price ; } }
    
        public string GetValidationReason()
        {
            return validationReason;
        }
    
        public SubmittedTrade SubmitToLedger(AccountingInfo accountingInfo)
        {
            return new SubmittedTrade(this, accountingInfo);
        }
    }
    
    public class SubmittedTrade : ITrade
    {
        private ITrade trade;
        private AccountingInfo accountingInfo;
    
        public SubmittedTrade(ValidatedTrade trade, AccountingInfo accountingInfo)
        {
            this.trade = trade;
            this.accountingInfo = accountingInfo;
        }
    
        Guid Id { get { return trade.Id; } }
        Contract Contract { get { return trade.Contract ; } }
        decimal Price { get { return trade.Price ; } }
    
        public AccountingInfo GetAccountingInfo() { .. }
    }
    

    【讨论】:

    • 我认为它会偏离聚合模式。但这确实是有道理的。您会使用单个 Repository 吗?可能也需要某种访问者模式吧?
    • 为了便于使用,我建议使用多种不同的存储库类型。但这不是必需的。关于访问者模式,我不知道你为什么需要它。为了完成什么?
    • 我想我已经超前了,我认为我最终需要一种方法来对界面 ITrade 采取行动。在 F# 中,我会创建一个有区别的联合来进行模式匹配。但你是对的,这不是预先要求的。
    • 啊,我明白了 :) 访问者模式可能是一种方式,但这取决于您想要达到的目标。有时你只有一个“老派”控制器,它会按程序执行这些步骤。
    【解决方案2】:

    每个州可以有一个班级,而不是一个班级。请参阅 Greg Young 的这篇文章:http://codebetter.com/gregyoung/2010/03/09/state-pattern-misuse/

    状态模式的常见问题是与持久性问题尤其是 ORM 之间的摩擦。由您决定是否值得为更好的健壮性和类型安全性而烦恼。

    【讨论】:

    • 来自 greg young 的好帖子。我将使用单独的课程。
    猜你喜欢
    • 2020-10-28
    • 2017-03-29
    • 1970-01-01
    • 2016-05-20
    • 2020-05-07
    • 2022-11-22
    • 1970-01-01
    • 2012-05-19
    • 2012-09-10
    相关资源
    最近更新 更多