Custom Field Design with C# and RavenDB
Asked Answered
W

1

8

I'm facing a key design question related to how to attach custom fields to entities in my system. The entities are represented in C# and persisted in RavenDB. We are roughly following tenants of Domain Driven Design and our entities are aggregate roots.

[Note: I would like to avoid any debate around the appropriateness of a generic feature like custom fields in a DDD approach. Let's assume we have a legitimate user need to attach and display arbitrary data to our entities. Also, I have made my examples generic for illustrating the design challenges. :)]

My question is concerning how best to lay out the field definitions and the field value instances.

Imagine a domain where we have aggregate roots of Book and Author. We want users to be able to attach arbitrary data attributes to instances of Books and Authors. So, we might define a custom field with a class like this:

public enum CustomFieldType
{
    Text,
    Numeric,
    DateTime,
    SingleSelect,
    MultiSelect
}

public class CustomFieldDefinition
{
    public string Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public CustomFieldType Type { get; set; }
    public Collection<string> Options { get; set; } 
}

A CustomFieldDefinition (CFD) that attached to Book might have values like:

  • Id: "BookCustomField\1"
  • Name: "FooCode"
  • Type: Text
  • Description: "Foo Corp's special identifier."
  • Type: Text
  • Options: null

The first question I'm facing is what to store on each instance of a Book. The choices range from...

the low end:

store just the CFD Id and the instance value

to

the high end:

store the entire CFD along with the value

The "low end" is bad because I cannot display a Book without pulling in the CFD, which is in another document. Also, if I change the CFD in any way, I've change the meaning of values in historical documents.

The "high end" is bad because there would be a lot of duplication. The CFD could be pretty heavy for select list CFDs because the definition contains all of the selectable options.

The first question is... How much should be stored in the document for each Book? Just enough to display the Book (and I'd have to go back to the CFD to display the options and description if I'm going to allow the user to edit the CF value)?

The second question is... Should I store I store the entire collection of CFDs for one entity type in one document or keep each CFD in it's own document? enter image description here

Each CFD as a document keeps things simple for each CFD (especially when I start to do things like deactivate definitions), but then I need a way to separate Book CFDs from Author CFDs. This also forces me to load 1 document for each CF attached to the entity whenever I want to edit the entity.

All of the CFDs for a given type in one document allows me to load just one document, but then I'm loading all of the deactivated definitions as well.

Third question... Is there a better way to implement this altogether?

Fourth question... Are there any sample or open source solutions out there so I don't have to reinvent this wheel?

Walburga answered 12/7, 2013 at 20:17 Comment(5)
Have you explored using dynamic for the custom data?Alvarado
Also, does every Book share the same set of custom fields? What about the options of those fields, might they be different for each book?Alvarado
Matt, at any given point in time, every Book shares the same set of custom fields, but the set may change over time. So, a Book from last year may have different fields than a Book from today.Walburga
Is it ok if a year later I look at a book and I see fields that exist today? What if I only have data from fields that aren't around anymore? Or should I just see the fields and options as they existed last year when the book was added to the system?Alvarado
I'm thinking that a Book from a year ago should show the custom fields as of a year ago.Walburga
A
1

Since you said in comments:

... a Book from a year ago should show the custom fields as of a year ago.

There are only two viable options I can see.

Option 1

  • Custom field definitions exist in their own documents.
  • Every book contains a copy of the custom field definitions that apply to that book, along with the selected values for each custom field.
  • They are copied when the book is first created, but could be copied again as your logic sees fit. Perhaps on edit, you might want to take a new copy, potentially invalidating the current selections.
  • Advantages: Self-contained, easy to index and manipulate.
  • Disadvantages: Lots of copies of the custom field definitions. Storage requirements could be very large.

Option 2

  • Use the Temporal Versioning Bundle (disclaimer: I am its author).
  • Custom field definitions still exist in their own documents, but they are tracked temporally. This means that revisions to the custom fields will be maintained in a usable history.
  • Books only contain the selected values. They don't contain copies of the definitions.
  • Books don't need to be tracked temporally, but they do need some kind of effective date in their data. Perhaps an "entered on" date. Use whatever makes sense for you.
  • The Book-to-CFD relationship is an Nt:Tx type. You can find another example of this relationship type here. You might want to get an overview of temporal relationships in order to make some sense of this. Beware, this is a tricky subject and gets complicated quickly.
  • Advantages: Much less storage required, since there are not many duplicate copies of the custom field definition data.
  • Disadvantages: Learning curve. Complexity of working with temporal data. Requirement to install a custom bundle on your database server.

With either option, I would simply keep a property on the custom field definition that says what type(s) it applies to (Book, Author, etc).

Alvarado answered 14/7, 2013 at 2:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.