Quantcast
Channel: Passion for code » DDD
Viewing all articles
Browse latest Browse all 2

Using Domain Driven Design Value Objects as Document Keys in RavenDB

$
0
0

See here for post on performance considerations of Value Object document keys in RavenDB.

Consider federated authentication where an identity provider, such as Windows Live ID, provides a “name identifier” claim for the user. The name identifier claim would be used in conjunction with another claim specifying the provider (thereby guaranteeing the name identifiers uniqueness) in order to identify the authenticated user. These name and name provider fields only have meaning when used together; both fields require validation; the fields should be immutable; the fields may be used internally within the application. For these reasons they would ideally be encapsulated into a NameIdentifier value object (VO) rather than being two unique and unrelated strings. Further, this value object would act a great identifier for a User entity type.

By default RavenDB uses strings as document keys for all persisted entities. This post outlines how this behaviour can be replaced with the using a value object as the document key, thereby encapsulating all key information about the NameIdentifier whole, its validation and its method functions into a simple object.

Approach

RavenDB provides several options for document key generation (see Document Key Generation Options). One of these options is to place responsibility for assigning/managing keys with the application rather than with RavenDB. In order to use VO’s as document keys we need to put responsibility of assigning/managing keys with the application as well as leverage RavenDB’s feature of allowing structs as document keys (N.B. the DDD concept of VO’s is distinct from any specific language construct, for example in C# value objects are not necessarily represented by value types). For the proposed NameIdentifier object this presents a slight concern: encapsulating reference types (such as a string) in structs is not considered good practice. However in my opinion, on this occasion, our approach is justified because of the benefits provided by adopting a VO, e.g. simplified validation and cleaner code. This is further supported by:

  • strings, although reference types, are immutable
  • performance tests on using strings in structs can be found here, and they appear to be favourable (despite having to access external referential data)
  • RavenDB only allows strings and structs to be used as document keys, so there aren’t any other options if you really want to use a VO

(Thanks to Mike Scott for pointing out in the comments section that only a reference to the string is stored in the struct rather than the string value itself. This means that we have no concerns about large string sizes overloading the stack memory)

The Code

NameIdentifier Value Object Struct

Below is the NameIdentifier struct. Note the validation (CuttingEdge.Conditions available through NuGet) in the constructor. This is an attempt to ensure the object remains in a valid state. Structs have a default parameterless constructor, which would lead to the name and provider strings being initialized to their default value which is null. We don’t want setters on the Name and Provider properties as the object should be immutable, therefore I have opted for a post condition on each of the properties getters which will throw an exception if the string value is set to its default null.

Another point to note is the JsonIgnore attributes on the private variables, otherwise RavenDB will serialize both the properties and their corresponding values.

Both Equals() and GetHashCode() have been overridden to ensure that equality is based on the objects relevant values. IEquatable<T> is implemented to provide performance benefits when checking equality.

The final point to note is the override in ToString(). This returns a concatenation of the name and name provider values separated by a colon – which represents the format which RavenDB will use for the document identifier.

Source code: NameIdentifier value object
public struct NameIdentifier : IEquatable<NameIdentifier>
{
    [JsonIgnore]
    private readonly string name;

    [JsonIgnore]
    private readonly string provider;

    public NameIdentifier(string name, string provider)
    {
        Condition.Requires(name, "name").IsNotNullOrEmpty();
        Condition.Requires(provider, "provider").IsNotNullOrEmpty();

        this.name = name;
        this.provider = provider;
    }

    public string Name 
    { 
        get { 
            Condition.Ensures(this.name, "name").IsNotNullOrEmpty(); 
            return this.name; 
        } 
    }

    public string Provider
     { 
        get { 
            Condition.Ensures(this.provider, "provider").IsNotNullOrEmpty(); 
            return this.name; 
        } 
    }

    public override bool Equals(object other)
    {
        if (other == null || other.GetType() != typeof(NameIdentifier))
        {
            return false;
        }

        return this.Equals((NameIdentifier)other);
    }

    public bool Equals(NameIdentifier other)
    {
        return other.name == this.name & other.provider == this.provider;
    }

    public override int GetHashCode()
    {
        return this.name.GetHashCode() ^ this.provider.GetHashCode();
    }

    public override string ToString()
    {
        return string.Format("{0}:{1}", this.provider, this.name);
    }
}

NameIdentifierConverter for Object/String Document Key Conversion

RavenDB needs to know how to work with the NameIdentifier document key. This is done by implementing ITypeConverter.

Source code: NameIdentifierConverter
public class NameIdentifierConverter : ITypeConverter {
    public bool CanConvertFrom(Type sourceType)
    {
        return sourceType == typeof(NameIdentifier);
    }

    public string ConvertFrom(string tag, object value, bool allowNull)
    {
        var identifier = (NameIdentifier)value;

        if (string.IsNullOrEmpty(identifier.Name) && string.IsNullOrEmpty(identifier.Provider) && allowNull)
        {
            return null;
        }

        return string.Concat(tag, identifier.ToString());
    }

    public object ConvertTo(string value)
    {
        var values = value.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
        return new NameIdentifier(values[1], values[0]);
    }
}

Registering NameIdentifierConverter with RavenDB

The NameIdentifierConverter needs to be registered with RavenDB Document Store for use.

Source code: SetupDocumentStore method
public static IDocumentStore SetupDocumentStore()
{
    var store = new DocumentStore() { ConnectionStringName = "RavenDB" }.Initialize();
    store.Conventions.IdentityTypeConvertors.Add(new NameIdentifierConverter());

    return store;
}

User Entity with NameIdentifier Document Key

This is a very basic User entity using the NameIdentifier VO as the document key. Note that we do still need to follow the “Id” naming convention.

Source code: User entity
public class User {
    public NameIdentifier Id { get; set; }
}

Example Usage

Below is a very simplistic example of how this would be implemented.

Source code: Usage
private static string name = "gary";
private static string provider = "provider";

static void Main(string[] args)
{
    // create entity with VO identifier var user = new User {
        Id = new NameIdentifier(name, provider)
    };

    // persist with RavenDB using (var session = SessionFactory())
    {
        session.Store(user);
        session.SaveChanges();
    }

    // re-load the persisted entity using the document key using (var session = SessionFactory())
    {
        var reloadedUser = session.Load<User>(new NameIdentifier(name, provider));
    }
}

 

 

Summary

In summary, this post has presented a case for using value objects as document keys in RavenDB, and detailed how this could be achieved. Two key limitations of the approach have also been highlighted, i.e. the 16 byte guideline for structs and the default parameterless constructor provided with structs – notably the issue that this causes with immutable objects. Steps taken to try and mitigate this last issue have also been detailed.

Happy Coding :-)

The post Using Domain Driven Design Value Objects as Document Keys in RavenDB appeared first on Passion for code.


Viewing all articles
Browse latest Browse all 2

Trending Articles