У меня есть общий корень Products
, который содержит список объектов Selection
, который, в свою очередь, содержит список объектов, называемых Features
.
- Агрегированный корень
Product
имеет идентификатор только имени - Объект
Selection
имеет идентификатор имени (и соответствующий ему Product identity) - Объект
Feature
имеет идентификатор имени (а также соответствующий ему идентификатор выбора)
Если идентификаторы для объектов построены следующим образом:
var productId = new ProductId("dedisvr");
var selectionId = new SelectionId("os",productId);
var featureId = new FeatureId("windowsstd",selectionId);
Обратите внимание, что зависимое тождество принимает идентификатор родителя как часть составного элемента.
Идея состоит в том, что это сформировало бы номер детали продукта, который может быть идентифицирован определенной особенностью в выборе, т.е. ToString()
для указанного выше объекта featureId вернет dedisvr-os-windowsstd
.
Все существует в агрегате Product, где бизнес-логика используется для обеспечения неизменности отношений между выборами и функциями. В моем домене нет смысла, чтобы функция существовала без выбора и выбора без связанного с ней продукта.
При запросе продукта для связанных функций объект Feature возвращается, но ключевое слово С# internal
используется для скрытия любых методов, которые могут мутировать сущность, и, таким образом, гарантировать, что объект неизменен для службы вызывающего приложения (в другая сборка из кода домена).
Эти два вышеприведенных утверждения предусмотрены двумя функциями:
class Product
{
/* snip a load of other code */
public void AddFeature(FeatureIdentity identity, string description, string specification, Prices prices)
{
// snip...
}
public IEnumerable<Feature> GetFeaturesMemberOf(SelectionIdentity identity);
{
// snip...
}
}
У меня есть агрегированный корень, называемый порядком обслуживания, он будет содержать ConfigurationLine, который будет ссылаться на Feature
внутри корня агрегата Product
на FeatureId
. Это может быть в совершенно другом ограниченном контексте.
Так как FeatureId содержит поля SelectionId
и ProductId
, я буду знать, как перейти к этой функции через общий корень.
Мои вопросы:
Составные тождества, образованные с идентичностью родителя - хорошая или плохая практика?
В другом примере кода DDD, где идентификаторы определяются как классы, я еще не видел никаких композитов, сформированных из идентификатора локального объекта и его родительского идентификатора. Я думаю, что это приятное свойство, так как мы всегда можем перейти к этому объекту (всегда через общий корень) со знанием пути к нему (Product → Selection → Feature).
В то время как мой код с составной цепочкой идентификаторов с родителем имеет смысл и позволяет мне перейти к сущности через корневой агрегат, не видя других примеров кода, где идентичности формируются аналогично композитам, вызывает у меня очень нервную реакцию - любая причина для этого или это плохая практика?
Ссылки на внутренние сущности - переходные или долгосрочные?
bluebook упоминает ссылки на сущности внутри агрегата, допустимы, но должны быть только временными (внутри кодового блока). В моем случае мне нужно хранить ссылки на эти сущности для использования в будущем, хранение не является временным.
Однако необходимость сохранения этой ссылки предназначена только для целей отчетности и поиска, и даже если я хочу получить дочерний объект bu, перемещаясь через корень, возвращаемые объекты являются неизменными, поэтому я не вижу никакого вреда, может быть сделано или инварианты сломаны.
Я считаю, что это правильно, и если да, то почему он упоминается, что привязка дочерних сущностей преходяще?
Исходный код ниже:
public class ProductIdentity : IEquatable<ProductIdentity>
{
readonly string name;
public ProductIdentity(string name)
{
this.name = name;
}
public bool Equals(ProductIdentity other)
{
return this.name.Equals(other.name);
}
public string Name
{
get { return this.name; }
}
public override int GetHashCode()
{
return this.name.GetHashCode();
}
public SelectionIdentity NewSelectionIdentity(string name)
{
return new SelectionIdentity(name, this);
}
public override string ToString()
{
return this.name;
}
}
public class SelectionIdentity : IEquatable<SelectionIdentity>
{
readonly string name;
readonly ProductIdentity productIdentity;
public SelectionIdentity(string name, ProductIdentity productIdentity)
{
this.productIdentity = productIdentity;
this.name = name;
}
public bool Equals(SelectionIdentity other)
{
return (this.name == other.name) && (this.productIdentity == other.productIdentity);
}
public override int GetHashCode()
{
return this.name.GetHashCode();
}
public override string ToString()
{
return this.productIdentity.ToString() + "-" + this.name;
}
public FeatureIdentity NewFeatureIdentity(string name)
{
return new FeatureIdentity(name, this);
}
}
public class FeatureIdentity : IEquatable<FeatureIdentity>
{
readonly SelectionIdentity selection;
readonly string name;
public FeatureIdentity(string name, SelectionIdentity selection)
{
this.selection = selection;
this.name = name;
}
public bool BelongsTo(SelectionIdentity other)
{
return this.selection.Equals(other);
}
public bool Equals(FeatureIdentity other)
{
return this.selection.Equals(other.selection) && this.name == other.name;
}
public SelectionIdentity SelectionId
{
get { return this.selection; }
}
public string Name
{
get { return this.name; }
}
public override int GetHashCode()
{
return this.name.GetHashCode();
}
public override string ToString()
{
return this.SelectionId.ToString() + "-" + this.name;
}
}