在DDD(领域驱动设计)中,存储库方法可以接受组成聚合的实体或值对象吗?

问题描述 投票:0回答:1

据我所知,每个聚合根都有自己的存储库,并且存储库应该仅与聚合实体及其主键一起使用。

例如,对于聚合预约:

    public class Appointment
    {
        public int Id { get; set; }
        public DateOnly ForDate { get; set; }
        public string ForTimeInterval { get; set; }
        public Person ForPerson { get; set; }
    }

预约存储库的一个版本可能如下:

    public class AppointmentRepository
    {
        public Appointment FindById(int Id) { }
        public void Appointment Add(Appointment appointment) { }
        public void Remove (Appointment appointment) { }
        public int Count() { }
    }

我的问题是,存储库中是否允许使用以下任何方法,或者它们是否应该在其他地方,例如 AppointmentDataAccess(DAO/DAL):

    public class Repository
    {
        . . .

        public IEnumerable<Appointment> FindAppointmentsForDate(DateOnly date) { }

        public IEnumerable<Appointment> FindAppointmentsForPerson(Person person) { }
        . . . 
    }

顺便说一下,我需要进行一个查询,返回某个日期的所有空闲 TimeIntervals。如果预订数量不超过 X,TimeInterval 也是免费的。所有这些都是业务逻辑,因此对我来说,在存储库中拥有这两种方法并在应用程序服务(或域服务可能???)中检查逻辑是有意义的。数据用于填充下拉菜单或类似的 UI 元素,通常我使用更抽象的数据访问逻辑(例如 DAO/DAL 和排序)来完成此操作。

问题已标记为 CQRS,但无需为其发布答案。

c# domain-driven-design repository-pattern cqrs value-objects
1个回答
0
投票

1.您的第一个问题
FindAppointmentsForDate

考虑一下如果您在存储库中没有有该方法会发生什么。您将被迫将每个实体检索到内存中并在服务中过滤它们。您最终可能每次都会检索数百万个实体。像这样的东西:

注意:这是一个坏主意!

public class AppointmentService(AppointmentRepository repo)
{
    public IEnumerable<Appointment> FindAppointmentsForDate(DateOnly date)
    {
        // Get all appointments for all time.
        var all = await repo.GetAllAppointments();

        // Filter in memory.
        return all.Where(a => a.ForDate.Date == date).ToArray();
    }
}

这不太可能实用,我想你会本能地(正确地!)觉得这是一个坏主意。

因此,在存储库中进行这样的简单过滤是完全合适的。常见的例子有:

  • 日期范围
  • 外键查找,例如另一个 SQL 表上的 EXISTS
  • 部分匹配,例如字符串“以”某物开头

2.你的下一个问题是关于寻找空闲时间间隔

这开始看起来像业务逻辑,但这并不意味着对存储库方法硬说“不”。答案取决于几个因素:

  1. 逻辑是否足够简单以至于不需要单元测试。
  2. 存储库必须实现逻辑是否有强有力的性能原因。

如果您可以自信地对上述两个问题回答“是”,您可以考虑将该逻辑放入存储库实现中。您的第一个问题符合这些标准。但对于你的第二个问题:如果不了解更多有关实体的信息,很难给出答案。

为了说明为什么确切的用例很重要,这里有两个例子:

如果逻辑真的很简单的话

假设:

  1. “每个时间段不超过 X 个预约”的静态规则。
  2. 我们正在使用 SQL。
  3. 有一张桌子可供
    Appointments
  4. 还有另一张桌子
    Slots

也许我们可以说这里没有太多需要单元测试的地方。因此,我们可以使用带有外连接、

GROUP BY
HAVING
的简单 SQL 语句来实现它。练习留给读者!你的存储库方法可能最终会像这样:

IEnumerable<DateTimeOffset> FindFreeSlotsForDate(DateOnly date);

如果逻辑比较复杂的话

也许每个时间段的最大预约数量会根据其他因素而变化。例如:

  • 在指定时间段内只能有1种“特别预约”,但最多可以有4种“普通预约”
  • 公共假期没有预约
  • 在确认之前不会进行预约。

现在,您可能想要对逻辑进行单元测试。您想要尝试不同的测试用例并验证它。您的存储库应该只有一个

FindAppointmentsForDate
,并且业务逻辑将在服务中实现。

这是一个完全虚构的示例,但希望它说明了属于服务而不是存储库的复杂程度:

public record Timeslot(
    String TimeslotType, // E.g. Weekday
    TimeOnly TimeOfDay,  // E.g. 1100
    int MaxNormalAppointmentsPermitted,
    int MaxSpecialAppointmentsPermitted
);

public class AppointmentService(AppointmentRepository repo)
{
    public async Task<IEnumerable<Timeslot>> FindFreeTimeslots(DateOnly date)
    {
        var appointments = await repo.FindAppointmentsForDate(date);
        Timeslots[] allTimeslots = await repo.GetTimeslotsConfiguration();
        var appointmentsByTime = appointments.ToLookup(a => a.ForDate);

        var freeTimeslots = allTimeslots.Where(t =>
        {
            var appointmentsThisSlot = appointmentsByTime[t.TimeOfDay];

            var specialAppointments = appointmentsThisSlot.Where(a => a.Type == "Special");
            var normalAppointments = appointmentsThisSlot.Where(a => a.Type == "Normal");

            if (appointmentsThisSlot.Count() > specialAppointments.Count() + normalAppointments.Count())
            {
                throw new MyException("Can't handle this appointment type");
            }

            if (normalAppointments.Count() > t.MaxNormaAppointmentsPermitted) return false;
            if (specialAppointments.Count() > t.MaxSpecialAppointmentsPermitted) return false;

            return true;
        }

        return freeTimeslots.ToArray();
    }
}

希望有帮助!

© www.soinside.com 2019 - 2024. All rights reserved.