Mongodb сохраняет объект домена и защищает инварианты

Я использую дизайн, управляемый доменом, с объединенными корневыми и дочерними объектами.

Агрегированные инварианты реализуются с помощью корневых ConfigurableService методов агрегации, соединяющихся по цепочке с методами дочерних сущностей со списком Groups и Dependencies, которые сопоставляют требования между группами.

Один инвариантный пример: ConfigurableService разрешает создание зависимости только в том случае, если SkuId содержатся в одной из групп в списке.

Если я пойду и сделаю группы или зависимости общедоступными (как требует 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 не существует).

Вопросы

  1. Каковы рекомендуемые способы защиты инвариантов и разрешения Mongodb сохранять/извлекать объект объекта домена?

  2. Могу ли я контролировать порядок установки свойств, когда mongodb гидратирует из своей базы данных?

  3. Или мне лучше создать собственный метод сериализации (как бы я это сделал)?


person morleyc    schedule 20.06.2014    source источник


Ответы (1)


Мой коллега придумал собственный сериализатор, который решил проблему, объект можно было реконструировать вручную из DTO BsonSerializer:

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;
    }
}
person morleyc    schedule 22.07.2014