TRIM: Adding Adapters to Business Objects

December 19th, 2018

Previously, I mentioned an acronym called TRIM for business objects. In today's post, we apply the Adapter Design Pattern to demonstrate the transformation of business objects.

Three weeks ago, I coined a term on how different parts of a system interact with your business objects. The concept came to me as an observation on how your business objects interact inside an application.

The acronym is TRIM which stands for Transformation, business Rules, Integration, and Mapping of business objects.

I discuss in the link how each one could be used as a foundation for your business objects across an application. Today, I specifically want to talk about utilizing the Transformation piece with the Adapter design pattern.

Adapter Design Pattern

The adapter design pattern works best when taking your business objects and converting them into a different format another system can understand.

At the DOFactory.com, an Adapter Design Pattern is defined as

Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.

Below are some common examples of how your business objects could use an adapter pattern:

Since we've already created Excel spreadsheets from Grids, created RSS Feeds, and built PDF's using iTextSharp, we'll create a quick way to build XML or JSON documents based on a business object.

The Scenario

Let's say you are the owner of the AdventureWorks Cycles company and you have a third-party who wants to work with your company to sell more products. You need to provide them with data in a specific format: XML.

The third-party said it will only be one way; you provide the data through an API and they will consume it. This is an excellent candidate for an adapter design pattern to transform the data into something usable to someone else.

Heck, RSS feeds are based off of XML results.

For our example, I've downloaded the AdventureWorks Cycles database (from link above) and attached it locally to demonstrate.

Our business object is based on the Production.Product table. We only need this entity, but I decided to Scaffold-DbContext the AdventureWorks database and then strip out what I didn't need.

The ProductAdapter.cs takes a list of products through the constructor and returns a formatted XML document.

public class ProductAdapter
{
    private readonly IEnumerable<Product> _products;
    private readonly CompanyNamespaceManager _ns = new CompanyNamespaceManager(new NameValueCollection
    {
        {"p", "http://www.company.com/p"},
        {"a", "http://www.company.com/a"}
    });

    public ProductAdapter(IEnumerable<Product> products)     {         _products = products;     }
    public XmlDocument GetXml()     {         var nsList = _ns.GetAll();
        var result = new XDocument(             new XElement("Products",                 nsList,                 GetProducts()             )         );
        return result.ToXmlDocument();     }
    private IEnumerable<XElement> GetProducts()     {         return _products.Select(GetProductElement);     }
    private XElement GetProductElement(Product product, int index)     {         return             new XElement(_ns.Get("p") + "Product",                 new XElement(_ns.Get("p") + "Name", product.Name),                 new XElement(_ns.Get("p") + "ProductNumber", product.ProductNumber ),                 new XElement(_ns.Get("p") + "Class", product.Class),                 new XElement(_ns.Get("p") + "Color", product.Color),                 new XElement(_ns.Get("p") + "ListPrice", product.ListPrice),                 new XElement(_ns.Get("p") + "ProductLine", product.ProductLine),                 new XElement(_ns.Get("p") + "Size", product.Size),                 new XElement(_ns.Get("p") + "Style", product.Style)                 // GetReviews(product.ProductReview)             );     }
    // private IEnumerable<XElement> GetReviews(ICollection<ProductReview> review) { } }

Once the adapter is done, the controller for the API is easy to create.

[Route("api/[controller]")]
[ApiController]
public class ProductController : ControllerBase
{
    private readonly IProductService _service;

    public ProductController(IProductService service)     {         _service = service;     }
    // GET api/values     [HttpGet]     public ActionResult<string> Get()     {         var products = _service.GetProducts();         var adapter = new ProductAdapter(products);         var xml = adapter.GetXml().InnerXml;
        return Content(xml, "application/xml");     } }

Once we have the data, we pass the products through the adapter and return our XML to the consumer.

We can now run the code and make the call to our Products controller.

http://localhost/api/Product

We now have our products in an XML format.

Why Not Use Serialization?

For one-way formats (like RSS), it makes sense to transform your data, whether it be products or blog entries, into a certain output format.

First, as I mentioned in my TRIM post, transformation of business object does include serializing and deserializing which I consider two-way transfoming. Since XML or JSON is the final result of a business object transformation in this scenario, the adapter design pattern is a better way to output business objects into a different format.

Second, I've seen seasoned veterans try to create an XML document (or a well structured JSON file) that require namespaces and they bungle (yes, technical term) them to the point where they try to String.Replace them in the document. This is WAY easier to define the namespaces upfront and populate the code where appropriate.

Source code is located at GitHub.

Conclusion

The idea behind TRIM is simply taking your business objects and making them more robust and flexible in your system. When you have solid business objects as your hub in a system, transforming them into something else becomes essential for someone to use your application.

Do you transform your business objects? Was this a good way to transform them? Is there a different design pattern to use? Post your comments below and let's discuss.