Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/amazon-s3/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Domain driven design 聚合与数据模型_Domain Driven Design_Aggregateroot - Fatal编程技术网

Domain driven design 聚合与数据模型

Domain driven design 聚合与数据模型,domain-driven-design,aggregateroot,Domain Driven Design,Aggregateroot,从整个存储中请求聚合,并将其视为单个单元。建议不要设计小型骨料,以免影响性能。这部分对我来说很有挑战性。特别是在持久化数据方面 我有一个带有DueDate属性的活动。活动有参与者,这些参与者可以参与活动的阶段,但只能在截止日期之前参与。 因此,每次用户为阶段做出贡献时,我都需要检查他是否是参与者,现在代码>贡献者贡献者=参与者服务(PrimeDeFrimeFor)(PrimeID,SeaReNeDD)中使该域的规则PAR与THW类似。阶段.贡献(贡献者)//内部检查contributor.pha

从整个存储中请求聚合,并将其视为单个单元。建议不要设计小型骨料,以免影响性能。这部分对我来说很有挑战性。特别是在持久化数据方面

我有一个带有
DueDate
属性的
活动
。活动有
参与者
,这些参与者可以参与活动的
阶段
,但只能在截止日期之前参与。
因此,每次用户为阶段做出贡献时,我都需要检查他是否是参与者,
现在

似乎我不需要为每个参与者、阶段和贡献加载整个活动图。

同时,如果已经存在对此阶段的贡献,我必须限制阶段内容的更改

除此之外,不同参与者捐款的平行交易不会相互影响。

这给了我一个提示,
contributionPhase
必须是一个独立的聚合,并且可能根据身份引用活动聚合。
虽然我仍然需要加载Activity aggregate才能获得DueDate属性的值。老实说,这让我很担心。

数据模型如下所示:

Activity
------------
Id
Title
Description
DueDate
....

Phase
------------
Id
ActivityId
Order
Title
Description
....


ContributionToPhase
------------
Id
PhaseId
ParticipantId
....
可以看出,在数据模型中,
活动
贡献阶段
之间没有直接联系。如果我将其设计为事务脚本,我会创建一个临时DTO,其中包含验证特定事务所需的所有数据(但不是更多):

ContributionRelatedDTO
    Id
    ActivtyId
    PhaseId
    UserId
    ActivityDueDate
    TimeStamp
    ....

那么,我应该如何使用DDD范式来处理它呢?
如果我将ContributionPhase聚合建模为具有存储在Activty表中的只读属性DueDate,可以吗?或者这是一种错误的聚合设计的味道?

要用DDD和ORM解决此类问题,请尝试实现一些CQR。我喜欢DDD,但我不认为你应该全力以赴地遵循它,我认为DDD有助于我们遵循良好的实践,但请记住,大师们每天都在改进它,因为它还没有解决所有问题的方法

对于每个事务,我们称之为命令。要执行命令,我们需要一个
CommandHandler
和一个
CommandData
。我看到一个
CommandData
,因为它是一个DTO。在那里,您放置了执行该命令所需的所有内容。
CommandHandler
更像一个小型服务,处理业务登录,因此它们属于域。让我们创建一个简单的示例:

public interface ICommandHandler<T>
{

    T Handle(T command);

}

public class ContributeToPhaseCommandData
{

    public Guid ContributionToPhaseId { get; set; }
    public Guid ActivityId { get; set; }
    public Guid PhaseId { get; set; }
    public Participant Contributor { get; set; }
    public DateTime ActivityDueDate { get; set; }

    public bool Success { get; set; }
    public string CommandResultMessage { get; set; }


    public ContributeToPhaseCommandData( /* mandatory data in constructor */ ) { }

}

public class ContributeToPhaseCommandHandler : ICommandHandler<ContributeToPhaseCommandData>
{

    public ContributeToPhaseCommandHandler( /* inject other services, if needed */ )
    {

    }

    public ContributeToPhaseCommandData Handle(ContributeToPhaseCommandData command)
    {
        // do stuff here, you might set some response data in the 'command' and return it.
       // You might send a DomainEvent here, if needed.
        return command;
    }

}
这个
ExceptionHandler
是实现
IExcepionHandler
的类,它应该处理应用程序逻辑异常。它们可以被注入应用程序的类构造函数中。实际上,您甚至可以在构造函数中发送
AuthorizationService
,并在每次应用程序调用中重用它

抛出异常的目的不仅仅是为了简化测试

现在我们来谈谈CQR。简单地说,它的目的是将查询和存储分离开来。发件人:

…其核心思想是,您可以使用不同的模型来更新信息,而不是使用不同的模型来读取信息。对于某些情况,这种分离可能是有价值的,但要注意,对于大多数系统,CQR会增加风险复杂性

这种方法带来的一个好处是,在执行命令后,您可以将调用委托给具有非规范化数据的辅助存储器,以实现只读目的。这个辅助存储甚至可能不需要密钥和关系。这就像将DTO/ViewModels存储在某个地方

人们认为我们读取的数据比存储的数据多,所以这允许您以UI读取速度比以往更快的状态存储数据,从而“准备好呈现”数据。对于模型中的每一个新更改,您都可以插入新的注册表,而不是更新/删除,因此获取历史数据、差异和其他内容更快更容易

由您和您的企业决定存储多少,以及非规范化的级别。因为现在存储越来越便宜,你可以考虑在辅助存储中存储更多的东西,因为它是相关的

它也可以是另一种存储,如NoSQL、cache(它将我们引入缓存失效),由您决定。我并不是说这很容易实现,我们应该定义这些层之间的事务级别,以及其他我现在记不起来的东西


因此我认为存储非规范化数据是可以的,因为您将它们用于只读目的,并且要小心使它们与域模型存储同步(可能是SQL和EF)。我希望这有助于研究这一主题,我的示例的目的是根据具体场景提出替代解决方案,您应该尝试组合好的解决方案,在合适的时候使用CQR,在合适的时候聚合。允许将它们合并,直到有人(再次)证明相反的情况。

是否可以更改ActivityDueDate?如果是这样的话,那么你已经承认了一个事实,即规则最终将是一致的。您可以将ActivityDueDate复制到一个阶段,然后执行类似于
PhaseComtribution contribution=Phase.contribute(…);添加(贡献)。使用域事件保持ActivityDueDate的同步,如果某个贡献最终被确定为非法,则可能会发出补偿操作。至于检查贡献者是否是参与者。您可以使用授权服务在应用程序服务中这样做。您也可以在应用程序服务>代码>贡献者贡献者=参与者服务(PrimeDeFrimeFor)(PrimeID,SeaReNeDD)中使该域的规则PAR与THW类似。阶段.贡献(贡献者)//内部检查contributor.phaseId==phase.id
@plax是,它可以更改并仅限制contribut的日期
public interface ICommandHandler<T>
{

    T Handle(T command);

}

public class ContributeToPhaseCommandData
{

    public Guid ContributionToPhaseId { get; set; }
    public Guid ActivityId { get; set; }
    public Guid PhaseId { get; set; }
    public Participant Contributor { get; set; }
    public DateTime ActivityDueDate { get; set; }

    public bool Success { get; set; }
    public string CommandResultMessage { get; set; }


    public ContributeToPhaseCommandData( /* mandatory data in constructor */ ) { }

}

public class ContributeToPhaseCommandHandler : ICommandHandler<ContributeToPhaseCommandData>
{

    public ContributeToPhaseCommandHandler( /* inject other services, if needed */ )
    {

    }

    public ContributeToPhaseCommandData Handle(ContributeToPhaseCommandData command)
    {
        // do stuff here, you might set some response data in the 'command' and return it.
       // You might send a DomainEvent here, if needed.
        return command;
    }

}
public SomeResponseToCaller ContributeToPhase(ICommandHandler<ContributeToPhaseCommandData> command, Guid phaseId, IPrincipal caller, IAuthorizationService authorizer)
{
    if (!authorizer.authorizes(caller))
        this.ExceptionHandler.Handle("Caller is not authorized! Shall we log this info?");
    using(var db = new ActivitiesContext())
    {
        ContributeToPhaseCommandData data = db.Phases
            .Select(p => new ContributeToPhaseCommandData()
                {
                    ActivityId = p.ActivityId,
                    PhaseId = p.Id,
                    Contributor = p.Activity.Participants.SingleOrDefault(part => part.Name == caller.Identity.Name)
                    ActivityDueDate = p.Activity.DueDate
                }).SingleOrDefault(p => p.Id == phaseId);

        if (data == null)
            this.ExceptionHandler.Handle("Phase not found");

        if (data.Contributor == null)
            this.ExceptionHandler.Handle("Caller is not a participant of this Activity!!!!");

        data.ContributionToPhaseId = Guid.NewGuid();

        var result = command.Handle(data);

        db.SaveChanges();

        return new SomeResponseToCaller() {
            Success = result.Success,
            ContributionId = result.ContributionToPhaseId,
            Message = result.CommandResultMessage
        };
    }
}