gpt4 book ai didi

domain-driven-design - 聚合与数据模型

转载 作者:行者123 更新时间:2023-12-04 08:21:13 25 4
gpt4 key购买 nike

从存储整体上请求聚合,并将其视为单个单元。建议设计较小的骨料不影响性能。这部分对我来说非常具有挑战性。特别是在持久化数据方面。

我有一个ActivityDueDate属性。活动的Participants可以对活动的Phases起作用,但只能在DueDate之前。
因此,每次用户为Phase做出贡献时,我都需要检查他是否是参与者和Now < DueDate
看来我不需要每个参与者,阶段和贡献都加载整个活动图。

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

除此以外,来自不同参与者的贡献的并行事务不会相互影响。


这给了我一个提示,ContributionToPhase必须是一个独立的聚合,并且可能通过标识引用Activity聚合。
尽管我仍然必须加载Activity聚合才能获取DueDate属性的值。老实说,这让我很担心。

数据模型如下:

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

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


ContributionToPhase
------------
Id
PhaseId
ParticipantId
....


可以看出,在数据模型中, ActivityContributionToPhase之间没有直接链接。如果我将其设计为事务脚本,则将创建一个临时DTO,其中包含验证特定事务所需的所有数据(但不更多):

ContributionRelatedDTO
Id
ActivtyId
PhaseId
UserId
ActivityDueDate
TimeStamp
....


要么

PhaseContentsRelatedDTO
Id
ActivtyId
HasContributions
Timestamp
....


那我应该如何用DDD范式处理它呢?
如果我将ContributionToPhase聚合建模为具有存储在Activty表中的只读属性DueDate,可以吗?还是闻到了错误的骨料设计?

最佳答案

要解决DDD和ORM的此类问题,请尝试实现一些CQRS。我喜欢DDD,但我不认为您应该全力以赴地遵循DDD,我认为DDD可以使我们遵循良好的做法,这是很好的,但是请记住,专家每天都会对其进行改进,因为它没有解决方案解决所有问题。

对于每笔交易,我们将其称为命令。要执行命令,我们需要一个CommandHandlerCommandData。我看到一个CommandData,因为它是DTO。在这里,您放置了执行上述命令所需的所有内容。 CommandHandler更像是一个小型服务,用于处理企业登录,因此它们属于Domain。让我们创建一个简单的示例:

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;
}

}


它们通常由应用层调用,以响应某些用例(有人参与某个阶段)。在委派对域的调用(命令处理程序)之前,应用程序层应检查 requester(又名用户或其他系统)是否具有执行此操作的权限。

现在,我们如何获取数据以提供命令?我认为,如果不需要,则不应该强制您加载全部聚合。仅在需要时加载它。

有时您的逻辑很沉重,需要进行全面的整合,因此可以将其放入领域模型/实体中。尽管有时您拥有更复杂的逻辑,但您几乎无法将其放入模型/实体中,并且需要许多零件的某些信息,并且当您根本不需要所有内容时,加载沉重的聚合是不切实际的。这会使您的模型变得贫乏。

在这种情况下,您似乎不需要完整的汇总来应用域逻辑。我只是认为创建聚合的替代 lighter版本是没有用的,除非有人证明相反(我可能错了)。

我会尝试在应用程序层中尽可能地使事情变得更加(KISS):

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
};
}
}


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

不仅在此处引发异常的目的是使测试更加容易。

现在让我们谈谈CQRS。简而言之,其目的是将查询与存储分开。从 Martin Fowler


…其内心的想法是,您可以使用与用于读取信息的模型不同的模型来更新信息。在某些情况下,这种分离可能很有价值,但请注意,对于大多数系统,CQRS会增加风险。


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

人们认为我们读取的数据要比存储的更多,因此,这使您可以在UI读取比以往更快的状态下存储数据,从而“准备好呈现”数据。对于模型中的每个新更改,除了更新/删除之外,您还可以插入其他注册表,因此可以更快,更轻松地获取历史数据,差异和其他内容。

由您和您的企业决定存储多少存储量,非规范化级别。由于当今存储越来越便宜,因此您可以考虑在该辅助存储中存储更多内容。

它也可以是另一种类型的存储,例如NoSQL缓存(由我们来进行缓存失效),由您决定。我并不是说要实现它很容易,我们应该在这些层之间定义事务级别,以及我现在不记得的其他内容。

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

关于domain-driven-design - 聚合与数据模型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34635990/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com