据我所知,每个聚合根都有自己的存储库,并且存储库应该仅与聚合实体及其主键一起使用。
例如,对于聚合预约:
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,但无需为其发布答案。
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();
}
}
这不太可能实用,我想你会本能地(正确地!)觉得这是一个坏主意。
因此,在存储库中进行这样的简单过滤是完全合适的。常见的例子有:
这开始看起来像业务逻辑,但这并不意味着对存储库方法硬说“不”。答案取决于几个因素:
如果您可以自信地对上述两个问题回答“是”,您可以考虑将该逻辑放入存储库实现中。您的第一个问题符合这些标准。但对于你的第二个问题:如果不了解更多有关实体的信息,很难给出答案。
为了说明为什么确切的用例很重要,这里有两个例子:
假设:
Appointments
Slots
也许我们可以说这里没有太多需要单元测试的地方。因此,我们可以使用带有外连接、
GROUP BY
和 HAVING
的简单 SQL 语句来实现它。练习留给读者!你的存储库方法可能最终会像这样:
IEnumerable<DateTimeOffset> FindFreeSlotsForDate(DateOnly date);
也许每个时间段的最大预约数量会根据其他因素而变化。例如:
现在,您可能想要对逻辑进行单元测试。您想要尝试不同的测试用例并验证它。您的存储库应该只有一个
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();
}
}
希望有帮助!