DDD 的主要内容是把重点放在领域,这就是为什么它被称为领域驱动设计。
当您开始询问关系、聚合和实体时,甚至没有深入探索您的域的组成部分,您实际上是在寻找数据库建模而不是域。
拜托,我不是说你问错了问题,也不是批评他们,我认为你在学习中尝试实践并没有错。
我不是 DDD 专家,我和你一样在学习,但我会尽力提供帮助。
首先考虑假日管理可能出现的情况。当您对某事有不同的规则时,您可以从使用 strategies 开始(我说的是最终解决方案)。
构建一个漂亮且有意义的域非常困难(至少对我而言)。你写代码。测试一下。有洞察力,抛出你的代码并重写它。重构它。在软件的生命周期中,您应该关注领域,因此您应该始终改进它。
从编码开始(如域的草稿),看看它的样子。让我们锻炼一下。首先,为什么我们需要管理这些东西?我们试图解决什么问题?啊,有时员工会请假,我们想控制它。我们可能会批准或不批准,这取决于他们想要“假期”的原因,以及我们的团队状态如何。如果我们拒绝了,他们仍然回家,我们会迟到决定是解雇还是减薪。执行无处不在的语言,让我们用代码表达这个问题:
public interface IHolydayStrategy
{
bool CanTakeDaysOff(HolydayRequest request);
}
public class TakeCareOfChildren : IHolydayStrategy
{
public bool CanTakeDaysOff(HolydayRequest request)
{
return IsTotalDaysRequestedUnderLimit(request.Range.TotalDays());
}
public bool IsTotalDaysRequestedUnderLimit(int totalDays)
{
return totalDays < 3;
}
}
public class InjuredEmployee : IHolydayStrategy
{
public bool CanTakeDaysOff(HolydayRequest request)
{
return true;
}
}
public class NeedsToRelax : IHolydayStrategy
{
public bool CanTakeDaysOff(HolydayRequest request)
{
return IsCurrentPercentageOfWorkingEmployeesAcceptable(request.TeamRealSize, request.WorkingEmployees)
|| AreProjectsWithinDeadline(request.Projects);
}
private bool AreProjectsWithinDeadline(IEnumerable<Project> projects)
{
return !projects.Any(p => p.IsDeadlineExceeded());
}
private bool IsCurrentPercentageOfWorkingEmployeesAcceptable(int teamRealSize, int workingEmployees)
{
return workingEmployees / teamRealSize > 0.7d;
}
}
public class Project
{
public bool IsDeadlineExceeded()
{
throw new NotImplementedException();
}
}
public class DateRange
{
public DateTime Start { get; set; }
public DateTime End { get; set; }
public int TotalDays()
{
return End.Subtract(Start).Days;
}
public bool IsBetween(DateTime date)
{
return date > Start && date < End;
}
}
public enum HolydayTypes
{
TakeCareOfChildren,
NeedToRelax,
BankOfHours,
Injured,
NeedToVisitDoctor,
WannaVisitDisney
}
public class HolydayRequest
{
public IEnumerable<Project> Projects { get; internal set; }
public DateRange Range { get; set; }
public HolydayTypes Reason { get; set; }
public int TeamRealSize { get; internal set; }
public int WorkingEmployees { get; internal set; }
}
我是这样快速写下这篇文章的:
- 可能会授予或不授予假期,具体取决于情况和
原因,让我们创建一个
IHolydayStrategy。
- 创建了一个空的(无属性)
HolydayRequest 类。
- 针对每种可能的原因,让我们制定不同的策略。
- 如果是为了照顾孩子,他们可以请假
请求的总天数低于限制。
- 如果原因是员工受伤,我们没有
允许请求以外的选择。
- 如果原因是因为他们需要放松,我们检查是否有
可接受的工作员工百分比,或者项目是否在
截止日期。
- 一旦我需要策略中的一些数据,我就使用
CTRL + .
在HolydayRequest 中自动创建属性。
看看我什至不知道这些东西将如何存储/映射?我只是编写代码来解决问题,并获取解决问题所需的信息。
显然这不是最终域名,只是一个草稿。如果需要,我可能会拿走这段代码并重写,但还没有感觉。
人们可能认为创建 InjuredEmployee 类只是为了始终返回 true 是没有用的,但这里的重点是利用 无处不在的语言,使事情尽可能明确,任何人都会阅读并理解同一件事:“好吧,如果我们有受伤的员工,他们总是可以请假,不管团队的情况和他们需要多少天。”。 DDD 中的这一概念解决的问题之一是开发人员、产品所有者、领域专家和其他参与者之间对术语和规则的误解。
在此之后,我将开始使用模拟数据编写一些测试。我可能会重构代码。
这个“3”:
public bool IsTotalDaysRequestedUnderLimit(int totalDays)
{
return totalDays < 3;
}
还有这个“0.7d”:
private bool IsCurrentPercentageOfWorkingEmployeesAcceptable(int teamRealSize, int workingEmployees)
{
return workingEmployees / teamRealSize > 0.7d;
}
是规范,在我看来,它不应该存在于策略中。我们可能会应用规范模式来解耦。
在我们通过测试得到一个合理的初始解决方案之后,现在让我们考虑应该如何存储它。我们可能会在这里使用最终定义的类(例如 Team、Project、Employee)来由 ORM 映射。
一旦你开始编写你的域,你的实体之间就会出现关系,这就是为什么我通常根本不关心 ORM 将如何持久化我的域,以及什么是聚合点。
看看我是如何还没有创建 Employee 类的,尽管这听起来很重要。这就是为什么我们不应该从创建实体及其属性开始,因为这与创建表和字段完全相同。
您的 DDD 变成了数据库驱动设计,我们不希望这样。当然,最终我们会创建 Employee,但让我们一步一步来,只在需要时创建。不要试图立即开始建模所有内容,预测您将需要的所有实体。专注于您的问题,以及如何解决它。
关于您的问题,什么是实体,什么是聚合,我认为您不是在问它们的定义,而是考虑到您的领域,是否将 Employee 视为一个或另一个。一旦您的代码开始显示您的域,您最终会回答自己。当您开始开发 Application Layer 时,您就会知道这一点,它应该负责加载数据并委托给您的域。我的域逻辑需要什么数据,我从哪里开始查询。
我希望我帮助了某人。