Mongodb сохранит доменную сущность и защитит инварианты
Я использую управляемый доменом дизайн с совокупными корневыми и дочерними объектами.
Агрегатные инварианты применяются через агрегатный корень ConfigurableService
методы, связанные с методами дочерних объектов, со списком Groups
а также Dependencies
какая карта требований между группами.
Один инвариантный пример ConfigurableService
разрешить создание зависимости можно только в том случае, если SkuIds содержатся в одной из групп в списке.
Если я пойду и сделаю "Группы" или "Зависимости" общедоступными (поскольку mongodb требует постоянства), то эту логику домена можно обойти - поэтому я подумал, что класс можно расширить с помощью открытых свойств, которые будут вызывать методы сущностей, такие как Dependencies
а также Groups
свойства:
public class ConfigurableService
{
List<Groups> groups = new List<Groups>();
Dependencies dependencies = new Dependencies();
public void AddDependency(SkuId on, SkuId requires)
{
if(IsContainsSku(on) && IsContainsSku(requires))
this.dependencies.SetRequiresFor(on, requires);
else
throw new Exception("SkuId doesnt exist");
}
public bool IsContainsSku(SkuId sku)
{
foreach(var group in groups)
{
if(group.ContainsSku(sku)==true)
{
return true;
}
}
return false;
}
// other code snipped for breverity
IEnumerable<Dependency> Dependencies
{
get { return this.dependencies.GetAllDependencies(); }
set
{
foreach(var dependency in value)
{
this.AddDependency(
new SkuId(dependency.Source),
new SkuId(dependency.Target)
);
}
}
}
IEnumerable<Group> Groups
{
get { return this.groups; }
set
{
this.groups.Clear();
foreach(var group in groups)
{
this.groups.Add(group);
}
}
}
}
Я бы повторил для каждого внутреннего свойства, которое необходимо сохранить, поскольку это проверит логику домена.
Это будет хорошо работать при чтении из репозитория при построении объекта, если свойства установлены в правильном порядке... например, если группы были установлены ранее Dependencies
мы бы в итоге бросили исключение (SkuID не существует).
Вопросы
Каковы рекомендуемые способы защиты инвариантов и разрешения Mongodb сохранять / извлекать объект сущности домена?
Могу ли я контролировать порядок установки свойств, когда mongodb гидратируется из своей базы данных?
Или мне лучше всего создать собственный метод сериализации (как бы я это сделал)?
1 ответ
Мой коллега придумал специальный сериализатор, который решил проблему, объект можно было восстановить вручную из BsonSerializer DTO:
public class ConfigurableServicePersistenceMapper
{
public ConfigurableServiceId Id { get; set; }
public string Description { get; set; }
public HashSet<Group> Groups { get; set; }
public Dependencies Dependencies { get; set; }
}
public class ConfigurableServiceSerializer : BsonBaseSerializer
{
public override void Serialize(BsonWriter bsonWriter, Type nominalType, object value, IBsonSerializationOptions options)
{
// implement using bsonWriter
if (nominalType != typeof(ConfigurableService))
throw new Exception("Object should be of type 'ConfigurableService'");
var obj = (ConfigurableService)value;
var map = new ConfigurableServicePersistenceMapper()
{
Dependencies = obj.Dependencies,
Description = obj.Description,
Groups = obj.Groups,
Id = obj.Id
};
BsonSerializer.Serialize(bsonWriter, map);
}
public override object Deserialize(BsonReader bsonReader, Type nominalType, Type actualType, IBsonSerializationOptions options)
{
// implement using bsonreader
if (nominalType != typeof(ConfigurableService))
throw new Exception("object should be of type 'ConfigurableService'");
var bson = BsonSerializer.Deserialize<BsonDocument>(bsonReader);
var configurableServiceMapper = BsonSerializer.Deserialize<ConfigurableServicePersistenceMapper>(bson);
var configurableService = new ConfigurableService(configurableServiceMapper.Id)
{
Description = configurableServiceMapper.Description
};
foreach (var group in configurableServiceMapper.Groups)
{
configurableService.NewGroup(group.Id);
var retrievedGroup = configurableService.GetGroup(group.Id);
retrievedGroup.Description = group.Description;
foreach (var sku in group.Skus)
{
retrievedGroup.Add(sku);
}
// set requirements
List<Group> groupList = new List<Group>(configurableServiceMapper.Groups);
foreach (var sku in group.Skus)
{
List<string> dependencies =
new List<string>(configurableServiceMapper.Dependencies.GetDependenciesFor(sku));
foreach (var dependencySkuString in dependencies)
{
retrievedGroup.SetRequirementFor(sku)
.Requires(new SkuId(dependencySkuString));
}
}
}
return configurableService;
}
}