好的,所以我刚开始用DDD弄湿,我有点不知所措。有很多因素需要考虑,我觉得自己正在磨砺,进展甚微。
我正在开发一个应用程序,该应用程序将在我公司的接收部门用于处理传入的库存。现在,我只是想全力以赴开发领域模型,并了解持久性层现在将如何工作,因为我要坚持持久性无知。
整个系统由SPA Web客户端,.NET Core Web API和SQL数据库组成。为了使事情变得更加复杂,这将在我们现有的ERP系统的基础上部分开发,因此需要聚合多个数据源以构成整个域模型。在此阶段,现有系统将不会有任何回写,因此我正在处理的过程的这一特定部分将保留在一个我完全控制的单独数据库中。这个想法是,以后可能可以为该特定部门替换大多数旧系统,但是所有这些都将逐步发生,因此不幸的是,我将不得不处理这种额外的复杂性。这是我尝试使用具有严格关注点分离的DDD架构的动机的一部分-如果我很好地构建了域模型并提取了数据库,我希望它会促进后备数据存储的转换。
这是我的域模型和实体的草稿:
public Receiver(ReceiverPurchaseOrder po, string packingSlipNumber,
string dockedBy, List<ReceiverDetail> details)
{
ReceiverNumber = GenerateReceiverNumber();
PurchaseOrder = po;
PackingSlipNumber = packingSlipNumber;
DockDate = DateTime.Now;
Status = ReceiverStatus.OnDock;
DockedBy = dockedBy;
Details = details;
}
//unique id assigned in db
public string ReceiverNumber { get; private set; }
//the vendors unique id from the packing slip
public string PackingSlipNumber { get; private set; }
//the purchase order that this shipment was ordered with
public ReceiverPurchaseOrder PurchaseOrder { get; private set; }
//the timestamp of when this receiver arrived in the facility
public DateTime DockDate { get; private set; }
//who received it
public string DockedBy { get; private set; }
public ReceiverStatus Status { get; private set; }
//the timestamp of when the inspection process started/ended
public DateTime? InspectionStartDate { get; private set; }
public DateTime? InspectionEndDate { get; private set; }
public string InspectorEmployeeId { get; private set; }
//the items in this shipment
public List<ReceiverDetail> Details { get; private set; }
private string GenerateReceiverNumber()
{
return "generate a receiver number";
}
public void BeginInspection(string inspectorEmployeeId)
{
/*
* after a shipment is received, the items need to be inspected
* a Receiver can go from OnDock -> InspectionInProcess
* or InspectionIncomplete -> InspectionInProcess
* A receiver with status complete or
* received cannot begin inspection
*/
if (Status == ReceiverStatus.OnDock
|| Status == ReceiverStatus.InspectionIncomplete)
{
InspectorEmployeeId = inspectorEmployeeId;
}
if (Status == ReceiverStatus.OnDock)
{
Status = ReceiverStatus.InspectionInProcess;
InspectionStartDate = DateTime.Now;
}
}
public void AddInspectionEntry(string receiverDetailId,
ReceiverDetailInspectionEntry entry)
{
/*
* each item requires an inspection entry to complete
* the receiving process
* the receiver must be status InspectionInProcess
* to modify the inspection entries
*/
}
public void EndInspection()
{
/*
* If all items have been inspected and have no exceptions,
* set status to InspectionComplete
* Otherwise set status to inspection incomplete
* (maybe another status required to indicate there is
* an exception requiring external resolution)
*/
if (Details.Any(x => x.InspectionEntry == null)
|| Details.Any(x => !x.InspectionEntry.InspectionCompleted))
Status = ReceiverStatus.InspectionIncomplete;
else
{
InspectionEndDate = DateTime.Now;
Status = ReceiverStatus.InspectionComplete;
}
}
}
public enum ReceiverStatus
{
OnDock,
InspectionInProcess,
InspectionIncomplete,
InspectionComplete,
Received
}
public class ReceiverPurchaseOrder
{
public string PurchaseOrderNumber { get; }
public string SupplierName { get; }
public string Buyer { get; }
public string Note { get; }
}
public class ReceiverDetail
{
public ReceiverDetail(ReceiverPurchaseOrderLineItem item, decimal receivedQty)
{
Item = item;
ReceivedQty = receivedQty;
}
public string ReceiverDetailKey { get; set; }
public ReceiverPurchaseOrderLineItem Item { get; private set; }
//the quantity stated on the vendor packing slip
public decimal ReceivedQty { get; private set; }
public ReceiverDetailInspectionEntry InspectionEntry { get; set; }
}
public class ManufacturerItem
{
public string ManufacturerItemKey { get; set; }
public string Manufacturer { get; set; }
public string ManufacturerPartNumber { get; set; }
}
public class ReceiverPurchaseOrderLineItem
{
//po line item
public int PoLineNumber { get; set; }
public string PoItemType { get; set; }
public string PurchaseUoM { get; set; }
public string InspectionNote { get; set; }
public bool InspectionRequired { get; set; }
public ManufacturerItem ManufacturerItem { get; set; }
//item config
public string PartNumber { get; set; }
public string Revision { get; set; }
public string PartClass { get; set; }
public string PartType { get; set; }
public string Description { get; set; }
public string UoM { get; set; }
}
public class ReceiverDetailInspectionEntry
{
public decimal AcceptedQty { get; set; }
public decimal RejectedQty { get; set; }
public bool PartNumberIsMismatch { get; set; }
public string PartNumberMismatchNote { get; set; }
public ManufacturerItem ReceivedManufacturerItem { get; set; }
public string InspectionNote { get; set; }
public bool InspectionCompleted { get; set; }
}
现有系统将继续在该过程中使用(至少在第一阶段)。 我们可以说接收过程分为三个阶段:
我正在此过程的第二阶段,即接受检查。如前所述,最终我们希望实现整个过程,但是第一阶段和第三阶段是通过旧系统完成的,而第二阶段没有实现。
从客户端应用程序的角度来看,这些是将要发生的操作类型(这是简化的操作,还有很多事情要做):
在这一点上,我的主要问题是围绕以下问题:
我在这里很迷路。我已经粗略了一下我认为Receiver类的外观,并放入了两种方法来表示可以操纵它的方式(仅用于接收过程的检查部分)。我真的对如何实现AddInspectionEntry流程感到困惑。我是否可以在Receiver类中使用它?为了能够添加检查条目,我需要做一些事情,例如针对接收器的状态进行验证。
关于我类中的属性,是否可以说所有属性都将是私有集,因为通过方法可以进行任何操纵?
此外,我是否可以说接收方是我的根本聚集者?我仍然不确定这些概念,但是是否有可能我需要进一步分解问题?在整个过程中涉及很多实体。
例如,采购订单本身是接收者的先决条件,并且任何采购订单或采购订单行项目都可能有多个接收者反对(分批装运等)。为简便起见,在此我仅将PO表示为ReceiverPurchaseOrder,仅包含接收过程所需的信息。 PO数据对于单个细节而言更为重要,因为这是检验过程将要验证的一部分。
我对此还有更多疑问。我现在的想法是,尤其是由于我有多个数据源,因此需要使用Factory类和存储库来创建域类。现在,我使用Entity Framework,并且已经阅读了DDD的工作原理,但是我仍然不确定一旦从各种数据中检索了所有需要的数据后,应该如何将数据库实体转换为域模型。资料来源。
作为一个例子,假设我需要执行BeginInspection()方法。在我的存储库中,我将同时从旧数据库和新数据库中提取数据。现在,将其转换为我的域模型的正确方法是什么?假设我所有的域模型属性都是私有集,那么如何将数据库对象映射到域对象?
然后,当涉及通过检查条目更新单个细节时,我是否正确理解我将需要构造整个域模型,使用新的或更新的检查条目更新域模型,然后将其映射回我的持久层然后如何找出如何仅对必填字段进行部分更新?我知道ORM可以管理其中的一些,但是也许由于我选择使用单独的持久性模型,所以这不可能吗?我是否需要使用某种类型的事件机制来表示这些更改要保存到数据库中?
我可以继续下去,但是我在这里打字的方式比整个周末都写的要多,所以任何形式的建议都将不胜感激。尤其是在全部写完这些之后,我可以看到我在这里有很多东西要学习,但是我正在逐步了解它。