Stop Mapping, Start Adapting

I spoke to quite a few people at CodeGarden 2016 who swear to mapping between published content and view models. I use mapping and models, but I don't think I ever did it for content in Umbraco. However, I think I understand the rationale behind it. Most of us have been on a lifelong architectural journey and found automatic mapping to be salvation. Don't get me wrong, I've done a lot of mapping. It really has its uses. But I'd still like to challenge its use in Umbraco. In this article I’ll show you how to reap the same benefits by using the adapter pattern with typed models.

Why we do mapping

There are good reasons for mapping between data representations in our software. Our database schema might be highly normalized and cumbersome to use for presentation. We might map and transform several tables to a rich object graph that serves our views better. We might map the tables to a rich domain model whose logic should only be invoked using commands. We’d expose the data using read optimized representations of the domain models. We might distribute our software or build subsystems with other domain models. Communicating between servers and systems is done using data transfer objects (DTOs) fit for the use-cases. So we map between entities, DTOs, commands and view models to separate concerns properly.

This is all good. Sometimes however, developers tend to be overzealous with this kind of separation. I’ve seen (and created) several solutions where the database schema matched the presentation 100%. There were never to be any replacing of database server, object-relational mapper (ORM), inversion of control container (IoC) or whatever we used for layering. The domain model is completely void of logic (anemic), the DTOs are exact copies except they’re not inheriting ORMBaseClass. The ViewModels are yet a copy, perhaps with some MVC attributes for metadata. Such architectures makes me feel more as a plumber instead of a creative developer. In fact, I could’ve just as well used the entities in my views. If the model got richer or scalability requirements changed, I could’ve implemented the appropriate abstractions.

Meeting Umbraco

In 2012, I met Umbraco and its then fairly new IPublishedContent. It can give the same feeling of protectionism that we get from our object-relation-mapper-powered domain models. We look at them and see a lot of logic (or an anemic domain model), so we naturally assume they should be protected and abstracted away. Coming from our background of carefully crafting representations for each layer, we pull up our sleeves, get out our favorite mapping tools and start crafting great new view models for our content.

But wait a minute. Is IPublishedContent really an entity or a DTO that belongs in the lower or middle layers of our site architecture?

Looking closer at Umbraco, we find Umbraco.Core.Persistence where there's Content and Entity types. These types are mapped from the database using PetaPoco and have an ancient and arcane form of DTO mapping. They all have an entity.ToXml() method returning a string. So in the deepest layers of Umbraco, there's an ORM, and there's DTOs (strings) for transfer to the next layer. The next step is the XML cache. It's persisted anew in DTO form (well-formed XML) to the database. Then everything is persisted to disk and it's also analyzed in an Examine (Lucene) index.

By now, the content has passed through a persistence layer, a caching layer and a search layer. Finally, the DTOs are hydrated from XML and exposed as a new type: XmlPublishedContent. The HQ guys were looking ahead though, thinking the DTOs might not stay in XML representation forever. So XmlPublishedContent was abstracted as IPublishedContent.

And there you go - the architectural plumbing issue is already handled. The wheel has been invented. The view model is already there. No need for more abstractions and more layering.

But GetPropertyValue<IHtmlString>("propertyName") is hardly worthy of being called view model, you say. I agree. But do we need to map property by property to new POCOs and start doing all that plumbing to fix it? Not only that, but we've got smaller specialized models, you say. I've got ListItemModel, I've got PromotionModel, I've got FeedbackModel. Gotta map.

A bright idea

Just before I started using Umbraco I took all of Uncle Bob's courses. I’d had that plumber feeling for a while, and I confronted him whether I had to do all that mapping. What if I used read only interfaces instead? “Well, interfaces are DTOs”, he said. And they are, aren't they? Compare these two artifacts:

    public class ListItemModel
    {
        public string Name { get; set; }
        public IHtmlString Content { get; set; }
        public Uri Url { get; set; }
    }

    public interface IListable
    {
        string Name { get; }
        string Content { get; }
        Uri Url { get; }
    }

How do they differ? The view model could have had a constructor and private setters, but it still needs the plumbing to set the values. It has "Model" in its name, and it belongs pretty close to the UI layer. The interface on the other hand, can't be instantiated. And it can't be transferred over the wire without some concrete implementation. However, if we'd implement that interface on an entity in the deepest dungeons of our DAL or BLL, it could still be used in the outer layers without exposing embarrassing details. When we do need to scale out, we'll create a serializable POCO DTO that also implements that interface so it can cross the border. The interface however, could live in a common assembly used throughout our system. Using interfaces, we abstract away the fact that we have more layers.

We don't have to set up mapping from all of our listable entities to ListItemModel or IListable. The types that have listable content all just implement that interface. It doesn't expose any irrelevant properties or any actual business logic we might have on our artifacts. It only exposes what's used for presentation in lists. The actual type of the object we are listing might have several other interfaces and a wealth of logic, but it’s leveraging the Interface Segregation principle. (And hopefully still adheres to the Single Responsibility principle.)

Applying to Umbraco

Could we apply this new way of doing things to our too-abstract IPublishedContent view model then? Yes! I’ve been jumping through some hoops to do this since it became possible with PublishedContentModelFactory back in 2013. Now that ModelsBuilder has been built-in to Umbraco, it’s so easy there’s no excuse not to do it. You’ve probably gathered, but we’re going to use what’s become known as strongly typed models in Umbraco.

What we’ll be doing is using the typed models as adapters for our interfaces. (Which lets our code live happy, dependency-less lives in slick testable assemblies.) On their own, they’re implementing a pattern known as decorators. They decorate the IPublishedContent instance with nice typed accessors to the properties in the document. Adding interfaces, we adapt them to be compatible with code that does not know about it. This works especially well when the property aliases matches your interface’s property names. It works even better when you use compositions in Umbraco. And dependency-wise, these classes end up in the bottom-most assembly of your stack: the web. They can depend on everything.

I imagine a lot of people are so used to getting model representations through .AsDto(), .AsModel(), .As<FancyType>() extension methods, that they don’t realize that ModelsBuilder and Umbraco take away the burden of transformation. When ModelsBuilder is enabled on a site, the typed models replace the old IPublishedContent before it leaves the cache. You’ll always be using instances of them in any code you write. Controllers, startup-handlers, plugins, you name it – they’ll all get concrete typed models.

An example

Let’s see how this can work with a simple use-case in Umbraco. We’ll create a Page- and a Post document type, both with a composition we call Listable. The listable composition has a summary property we’ll use to display pages and posts in searches and overview pages. Using mapping, we could’ve devised ways to get at a DTO representation of the listable composition. Using nothing, we could’ve used .GetPropertyValue<IHtmlString>(“summary”) in our view and not seen the difference. Both approaches however have us ensuring in some way that we only have stuff composed of listable, or stuff that has a summary property.

If we use ModelsBuilder and configure it to use AppData mode, we’ll have Page.generated.cs, Post.generated.cs and Listable.generated.cs in our ~/App_Data/Models folder. (support for ~/Models/… is right around the cornder). In Listable.generated.cs we’ve already got an interface for free, and Page and Post both implement it. The classes are all partial, so to adapt them with our previous Listable interface, we can create a file called Listable.partial.cs. In it, we just add the interface:

public partial interface IListable : Custom.IListable
    {
    }

We'll get three build errors, though. Our custom IListable interface expects string Content { get; } to be implemented, and none of our types has one. We called the property “summary”. Had it been called “content”, we’d be all done, but we have to do some more adaption. (This will be easier in the future – trust me.) Page and Post do get the interface though. In any case, to resolve it, we’ll actually have to add the property in individual partials for each type. Had we used mapping, we’d have to set this up anyway, so we haven’t gotten a new problem. Here’s how it'll look:

    public partial class Page : IListable
    {
        public string Content { get { return Summary; } }
    }

    // repeat for Post and Listable

We only had to do Content though. Name and Url is already implemented by PublishedContentWrapper, which all the classes already derive. (It’s a requirement in Umbraco) All of our code though, just have to know about Custom.IListable to be able to list these document types. We can go foreach(var listable in Model.Content.Children<IListable>()) and use the IListable interface as a view model in the for-loop. We could even declare it as the view model in a view using @inherits UmbracoViewPage<IListable>. From a main view of a page, we could pass Model.Content into any view using IListable as its model. Of course this also goes for any code you write in your system. And that code only knows about your interface.

This technique can be used for several other aspects of our full document types. Here's a few ideas:

  • ISeo
  • IArticle
  • IPromotable
  • ISearchable
  • IMenuItem
  • IPriced
  • IPurchasable

I suspect there's a high chance you spot at least one with the same name as some of your carefully crafted view models. Some of them might also be recognized as compositions used in your document type structure. They could even expose value types converted by property value converters.

Dangerous temptations

A few temptations might arise using this technique. Here's a few dos and don'ts:

Don't use published content and typed models as your main repository for ecommerce products, orders or customers. Those kinds of artifacts belong in a protected layer with a rich domain model. And they should be mapped to objects that are allowed to cross the border to another part of the system. Maybe as interfaces to start with. They could co-exist with content representations, though.

Don't map from Examine to the typed models, use Umbraco.TypedContent() to resolve the model.

Do lean on JSON and property value converters for complex data. Don't put coherent values in different properties on your document type. The typed models will reflect your JSON as nice domain value types. Your content model will be richer from it too.

Convinced?

I hope I managed to show that typed models is a highly worthy alternative to mapping and manually crafting view models. If you're still not convinced, I'd really appreciate hearing why. There might be something valuable I missed. Pop me a tweet at @bleedo and tell me your thoughts. Even better, write a blog post about it or write it on skrift.io.

About the Author

Lars-Erik is a lead developer at MarkedsPartner AS in Norway. He likes the SOLID principles, automated testing and code that can be read by humans. Since 2012 he’s been an avid Umbraco fan, and in 2016 was awarded Umbraco MVP for contributions to the core. He blogs at http://blog.aabech.no.

comments powered by Disqus