Wednesday, February 26, 2014

These Aren’t The API Models You’re Looking For

I recently answered a question on Stack Overflow that started me thinking about the differences between domain models and the models you expose through your Web API. While the question was only tangentially related, I thought it would be helpful to think through why I believe that domain models shouldn’t be exposed directly through your API.

I’ve long argued for model-per-view in MVC and I’ve carried that thinking over to WebAPI as well. In an MVC context, I think that separation of concerns is the strongest argument in favor of using separate view models. Views have specific data needs for display that don’t belong in your domain models. For example, they may contain auxiliary information containing localization or paging. Your views may not need or you may not desire your view models (which can also used as the arguments for POST actions) to contain all domain properties – for example, whether a user is in a role. Or you may expose this as part of the model but validate it in a completely different way, leading to differing validation requirements. Lastly, your views may flatten multiple models into a single, composite model, omitting unimportant details from that particular view’s point of view.

Many or most of these also apply to API models, even though they are or seem to be strictly about the data. While I don't claim the following to be an exhaustive list, here the reasons I think that you should use separate models for your API and map between your domain and your API models rather than directly expose your domain models.

Hiding Implementation Details

There are two reasons why you might want to hide the implementation details of your model. As in MVC, there are likely some record-keeping attributes on your domain models that are not necessary for the proper operation of the API. For example, you may track who created the item and when, who updated it and when - in a HIPPA world you may even track who viewed it, when, and, perhaps, for what purpose. You may also have actual domain information that needs to remain private even if some of the information is exposed; or it may only conditionally be available based on the API consumer's privileges. This, in itself, is probably enough of a reason to separate your API models from your domain models. Doing this allows you to keep your domain model simpler and cleaner, more appropriate to the data rather than the usage of the data. The alternative would, perhaps, be a multiplication of domain models, representing purpose-specific views and an accompanying complication of your API (or additional APIs) to support those models.

Transforming Implementation Details

Similarly, there are aspects of your model to represent relationships in the data that may be irrelevant to the API consumer. You may model a relationship as many-to-many in your domain model. For example, a product may have many accessories while an accessory may be available for more than one product. From a catalog management perspective this makes perfect sense, but for an API that allows an external consumer to access your catalog you may only want to expose it as a one-to-many relationship. This product has these accessories and not expose that the accessories belong to more than one product. You may not even want to model the reverse relationship from accessory to product in the API at all. It might simply be immaterial.

Also you may want to provide localization for product attributes so that your API can be language aware. While your domain model must account for the relationships to alternative text for attributes, you're likely to perform substitution for those attributes appropriate to the language requested rather than provide all translations, forcing the consumer to pick the correct one for their purpose.

Navigation

Navigation in your domain model is established through foreign key relationships in the database, which are translated to object references and collections of object references in your object model. Navigation in APIs is represented by URLs. "RESTful", "REST-like", "Hypermedia" - that's a discussion for a different post - but modern thinking is that your API on the web should leverage web protocols (the verbs and request patterns). This can be a challenge in designing your API as you trade off between model purity and efficiency.

In your object model the cost of navigation is very low (an object reference) but on the web it is exponentially higher - a full web request. The thing that's clear, though, unless your data set is very small, is that you will have to make compromises to keep your requests from ballooning in size. While you may be able to keep your entire product catalog in memory, it's not likely that you're going to want to serialize all of it and deliver it in a single web request. Instead, you're going to break your API up into multiple methods, each perhaps corresponding to a aggregate root. You may choose to expose navigation between related elements via explicit navigation properties or simply ids that the client uses in constucting the appropriate URLs to request more information.

If you embrace web protocols in your API, you'll need to account for the differences in navigation properties between your object model and your API.

Versioning

Another strong argument in favor of using separate view models is versioning. Because APIs represent a "public" product, tying disparate systems together, it's rarely possible to simply discard an older API when the underlying domain model is modified. In response API designers need to account for this by introducing some sort of versioning into their API design. If you directly expose your domain models, this could also mean that you would need to introduce versioning into your domain model to support older versions of an API. Decoupling your domain models from your API through the use of purpose-specific API models insulates your domain model from this need. While you may need to main multiple versions of your API models to support multiple versions of your API, your domain model can evolve to support new features without compromise for the sake of the API.

Summary

In short, you should use separate API models. Yes, it might seem simpler to directly use your domain models and there are a lot of examples that take this expedient. In the long run and with a more complex API than typically used for tutorials, separate API models are going to serve you well.