0

I am using domain driven design with an aggregate root and child entities.

The aggregate invariants are enforced via the aggregate root ConfigurableService methods, chaining through to methods of the child entities, with a list of Groups and Dependencies which map requirements between the groups.

One invariant example is ConfigurableService only allow the dependency to be made if the SkuIds are in contained within one of the Groups in the list.

If i go and make Groups or Dependencies public (as mongodb requires for persitence), then this domain logic can be bypassed - So i thought the class could be expanded with public properties which would call the entity methods, such as the below Dependencies and Groups properties:

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);
            }
        }
    }
}

I would repeat for each internal property that needs to be persisted as this will check the domain logic.

This would work fine during reading from repository when the object is built, as long as the properties are set in the correct order... for instance if Groups were set before Dependencies we would end up throwing an exception (SkuID doesnt exist).

Questions

  1. What are the recommended ways to protect invariants and allow Mongodb to persist/retrieve the domain entity object?

  2. Can i control the order the properties are set when mongodb hydrates from its database?

  3. Or would i be best to create a custom serialization method (how would i do this)?

4

1 回答 1

0

My colleague came up with a custom serializer which resolved the issue, entity could be reconstructed manually from the 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;
    }
}
于 2014-07-22T18:28:33.217 回答