4

Background

I like Jeffrey Palermo's Onion Architecture model (similar to Hexagonal Architecture) which prescribes that the Domain Model be at the 'center' and the concrete implementations of infrastructure, specifically Concrete Repositories be on the periphery.

enter image description here

So say I have a Domain Model:

//https://libphonenumber.codeplex.com/
using libphonenumber;

namespace MyApplication.Domain
{
    public class Speaker
    {
         public virtual string Name {get;set;}
         public virtual PhoneNumber PhoneNumber {get;set;}
    }
}

Now I need to expose this Domain Model to other teams:

  • The UI Team hypothetically wants to add several Data Validation attributes and custom JSON serialization attributes.
  • The Infrastructure Team hypothetically wants to add XML Serialization attributes and some custom attributes from a 3rd party Database implementation.
  • The Public API Team hypothetically wants to add WCF attributes.

I don't want to give every team carte blanche to add their Attributes to my Domain Model and I especially don't want them adding all of their "layer specific" dependencies to my model assembly.

And this case is made more complicated because I'm using 3rd party 'domain models' in my own (in this case using Google's LibPhoneNumber to handle the Phone Number).

Ideally, they'd each need to create their own wrapper class like:

using MyApplication.Domain;
namespace MyApplication.UI.DomainWrappers
{
    public class UISpeaker
    {
         private Speaker _speaker;
         public class UISpeaker(Speaker speaker = null)
         {
             _speaker = speaker ?? new Speaker();
         }

         [Required]
         public virtual string Name {
            get{ return _speaker.Name; }
            set{ _speaker.Name = value; }
         }

         [Required]
         public virtual PhoneNumber PhoneNumber {
            get{ return _speaker.PhoneNumber ; }
            set{ _speaker.PhoneNumber = value; }
         }

         //Conversion operators
         public static implicit operator UISpeaker(Speaker s)
         {
           return new UISpeaker(s);
         }

         public static implicit operator Speaker(UISpeaker s)
         {
           return s._speaker;
         }             
    }
}

Question

Writing and maintaining the UISpeaker class is a pain and is boring boilerplate code.

Is there either a better way to add the Attributes each team wants to add without letting them directly edit the Domain Model? Or is there some tooling that can help generate these wrapper classes (I was thinking possibly a weaving tool like Fody or T4 Templates, but I'm not familiar enough with either to know if they could help in this use case).

Research

I looked around Stackoverflow and found some similar questions, but none that hit the full scope I'm looking for:

Community
  • 1
  • 1
Philip Pittle
  • 11,821
  • 8
  • 59
  • 123
  • In some layers you can use metadata classes and relate them to main classes using `AssociatedMetadataTypeTypeDescriptionProvider`. In some layers you can use wrapper / mapped classes and simplify tasks using libraries like `AutoMapper`. Also in all layers you can simplify the jobs using some code generation strategies using t4 templates. – Reza Aghaei Jan 15 '16 at 20:54
  • @RezaAghaei - `AssociatedMetadataTypeTypeDescriptionProvider`look interesting based on the brief MSDN page. But do you know of any example code out there that will let me inject additional metadata into a class and then have that metadata be consumable by 3rd party code? – Philip Pittle Jan 15 '16 at 20:56
  • 1
    You may find [this post](http://stackoverflow.com/a/34481967/3110834) useful. – Reza Aghaei Jan 15 '16 at 20:57

1 Answers1

2

You can use these options to simplify the job:

  • Metadata Classes
  • Object to Object Mappers
  • Code Generation

Metadata Classes

You can create metadata classes and add attributes like data annotations and validation attributes to those metadata classes and then relate these metadata classes to your main domain classes using AssociatedMetadataTypeTypeDescriptionProvider. Such metadata classes are only attribute containers and using type descriptor mechanisms add attributes to your main classes.

For example, you can register a metadata class for your model this way, and let all infrastructures that benefit TypeDescriptor see your metadata attributes for your model:

var provider = new AssociatedMetadataTypeTypeDescriptionProvider(typeof(Model), 
                                                                 typeof(ModelMetadata));
TypeDescriptor.AddProvider(provider, typeof(Model));

Object to Object Mappers

You can have view models, business models and domain models in different layers and using decorate them with attributes that you need for each layer, then using an object to object mapper like AutoMapper simplify the task of mapping those classes to each other.

AutoMapper is an object-object mapper. Object-object mapping works by transforming an input object of one type into an output object of a different type. What makes AutoMapper interesting is that it provides some interesting conventions to take the dirty work out of figuring out how to map type A to type B. As long as type B follows AutoMapper's established convention, almost zero configuration is needed to map two types.

Code Generation

You can make creating metadata classes or view model classes more easy using some code generation tools. For example you can create the wrapper classes simply using a code generation mechanism like T4 Templates.

In Visual Studio, a T4 text template is a mixture of text blocks and control logic that can generate a text file. The control logic is written as fragments of program code in Visual C# or Visual Basic. The generated file can be text of any kind, such as a Web page, or a resource file, or program source code in any language.

Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • when describing Metadata Classes, you said "let all infrastructures that benefit `TypeDescriptor` see your metadata". What do you mean by that? Would Json.Net for example pick up additional serialization attributes that were added that way? Say `JsonIgnore`? Or for that to work do you need to be the one doing the type inspection? – Philip Pittle Jan 19 '16 at 19:42
  • I don't know if there is a built-in support for `TypeDescriptor` when using Json Serializer, but even if there is no built-in support, you can simply create a custom contract resolver deriving from `DefaultContractResolver` and do it yourself. Also "let all infrastructures that benefit TypeDescriptor see your metadata" means let that parts of your infrastructure that supports using TypeDescriptor mechanisms, like many parts of ASP.NET MVC, or All Parts of Windows Forms use them. Also you can add support for TypeDescriptor to infrastructures yourself . – Reza Aghaei Jan 19 '16 at 19:54