Хорошо, я просто промочил ноги от DDD и чувствую себя немного ошеломленным. Есть так много вещей, которые нужно учесть, и я чувствую, что делаю это очень медленно.
Я разрабатываю приложение, которое будет использоваться в приемном отделе моей компании для обработки входящего инвентаря. Прямо сейчас я просто пытаюсь сосредоточиться на разработке модели предметной области, а также понять, как будет работать слой постоянства, когда я пытаюсь придерживаться постоянного невежества.
Вся система состоит из веб-клиента SPA, веб-API .NET Core и базы данных 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; }
}
Существующая система должна продолжать использоваться в процессе (по крайней мере, на первом этапе).
Можно сказать, что в процессе получения есть 3 этапа:
- Груз прибывает, и он регистрируется в системе как находящийся там (НА ДОКЕ)
- Содержимое посылки проходит процесс проверки
- Процесс получения завершен, а полученные предметы переданы в инвентарь
Я работаю над вторым этапом этого процесса, приемной инспекцией. Как указывалось ранее, в конечном итоге мы хотели бы реализовать весь процесс, но первый и третий этапы выполняются через устаревшую систему, а второй этап не реализован.
С точки зрения клиентского приложения, это типы операций, которые будут выполняться (это упрощено, будет происходить намного больше):
- Сотрудник начинает проверку груза
- Сотрудник регистрирует контрольную запись для одного из предметов в отгрузке
- Сотрудник завершает процесс проверки
На данный момент мои основные вопросы касаются следующего:
- Дизайн доменной модели
Я довольно потерян здесь. Я описал класс Receiver и добавил несколько методов, представляющих способы, которыми можно манипулировать (только для проверочной части процесса получения). Я действительно застрял на том, как реализовать процесс AddInspectionEntry. Правильно ли я имею это в классе Receiver? Мне нужно сделать что-то вроде проверки состояния получателя, чтобы иметь возможность добавить контрольную запись.
Что касается свойств в моих классах, правильно ли говорить, что все свойства будут частными, поскольку любые манипуляции будут выполняться с помощью методов?
Кроме того, прав ли я, что получатель является моим корневым агрегатом? Я все еще немного неуверен в понятиях, но возможно ли, что мне понадобится решить проблему еще дальше? Есть целый ряд организаций, вовлеченных в этот процесс.
Например, сам заказ на покупку является предварительным условием для получателя, и у любого заказа или строки заказа на покупку может быть несколько получателей (частичные поставки и т. Д.). Здесь для краткости я просто представляю PO как ReceiverPurchaseOrder только с информацией, необходимой для процесса получения. Данные заказа на поставку гораздо важнее для отдельных деталей, поскольку это является частью того, что процесс проверки будет проверять.
- Стойкость доменной модели
У меня есть еще вопросы о том, как это работает. Сейчас я думаю, что, поскольку у меня несколько источников данных, мне нужно использовать класс Factory и репозиторий для создания класса моего домена. Прямо сейчас я использую Entity Framework и изучаю, как это работает с DDD, но я все еще не уверен, как мне следует преобразовывать сущности моей базы данных в мою модель домена, как только я получу все необходимые данные из различных данных. источники.
В качестве примера, скажем, мне нужно выполнить метод BeginInspection (). В моем хранилище я собираю данные как из прежней базы данных, так и из новой базы данных. Теперь, как правильно превратить это в мою модель предметной области? Предполагая, что все свойства моей доменной модели являются частными, как можно сопоставить объекты базы данных с доменным объектом?
Затем, когда дело доходит до обновления одной детали с помощью контрольной записи, правильно ли я понимаю, что мне нужно будет построить всю модель домена, обновить модель домена с помощью новой или обновленной контрольной записи, а затем сопоставить ее с мой уровень персистентности, то как выяснить, как сделать только частичное обновление для обязательных полей? Я понимаю, что ORM могут справляться с некоторыми из этих проблем, но, возможно, поскольку я предпочитаю использовать отдельную модель постоянства, это невозможно Нужно ли использовать какой-либо механизм событий, чтобы сигнализировать об этих изменениях для сохранения в базе данных?
Я мог бы продолжать и продолжать, но я набрал здесь больше, чем мне удавалось кодировать весь уик-энд, так что любые советы были бы очень благодарны. Особенно после того, как все это написано, я вижу, что мне есть чему поучиться, но я получаю это по частям.