Collector, Translator And Formatter Pattern

Before we take a deep dive into the pattern, I am assuming the target audience to have some experience in implementing standard design patterns. Also, have a thorough understanding of Object Oriented Principles and Practices.

Those of you are interested to learn and implement a mechanism to gather or collect data, apply translator to alter or modify the content and apply formatter to format the data, you are the right candidate to of this article.

As we all know, Design patterns are solution to the commonly re-occurring problems. Today, we are solving the most common practices for collecting, translating and formatting the data.
Real world scenario’s

Let us try to understand with the real world scenario with an example.

  1. Consider an example of a DateTime object. It holds the actual DateTime value but when it comes to formatting of DateTime, say you may be using ToString method of DateTime by specifying the format as a string, IFormatProvider or combination of them to return a specific format of the date. Internally, the ToString method of DateTime class uses an internal class named “DateTimeFormat” to format the DateTime value. Please take a look to this link to have a feel about the DateTime formatting.

  2. An example of a Healthcare Information Exchange (HIE) product, where you wish to transfer or exchange the patient healthcare information in an interoperable manner. Meaning, you wish to export the patient data in a specific format like CCR, CCD, CDA and FHIR etc. Imagine, you have a database stored with all patients, providers and associated patient’s health information. In such scenario, the consumer of the data is not bound with a specific data format but instead accepts the patient health information based on the industry specified format.

    The software that is responsible for exchanging the health information should be cable of supporting the needs of the client. Here comes the data collector and formatter. The database is a storage medium where your patient data resides. One can collect or gather, translate and transform the data based on client requirement.

    High-level overview of the design pattern

    Note: The below formatter types are just for demonstration purpose.

    pattern

    Collector

    The “Collector” is an entity which is responsible forgathering the data. Your data can be residing in a flat file, database or any format. In the end, the job of the collector is to gather these data. Defining a clear bounded context is important. The collectors look like a data access components. In some cases, it need not be. So it’s good to separate the collectors from data access components.

    Translator

    The “Translator” is an entity which is responsible for modifying or altering the data gathered by a collector.This entity is responsible for massaging the gathered data. There are times you need to perform translation as per the client requirement or apply generic translations. All general and client specific translations rules should go as translators. You don’t necessarily have to go with translators for every collector. Note –The Translators are optional entities. It depends on the domain you are dealing with.

    Formatter

    The “Formatter” is an entity which is responsible for formatting the data gathered by the collector or translated data. The “Formatter” converts the data into a specific format based on the client needs. An example of a formatter would be converting the data that is gathered by collector(s) to JSON or XML format.

        Necessity to Decouple the Collector, Translator and Formatter

        Let us try to understand the need or necessity to decouple the collector, translator and formatter.
        We are going to achieve the below things.

  1. Loose coupling and has single responsibility to collect, translate and formatting of data.

  2. At times, when you are dealing with the data translation. It’s always a good idea to separate them. Here comes the role of a “Translator”.

  3. The formatter don’t have to depend on the collectors. Which means, the collector and the formatter can co-exists and can independently be modified without affecting each other’s.

    The lesser the dependency, it is easy to enhance or extend the existing behaviors of a collector, translator or formatter. You have a freedom to extend a formatter to support additional client requirement or needs.

    Sample Data File

    The sample application that we are coding is making use of the below mentioned HL7 XML Message.

    Using Code

    Let us take look into the sample code and see how the pattern can be implemented. For demonstration purposes, I am taking an example of a customized Health Level 7 (HL7) Message from Open Dental. Within the collector, we will be gathering a HL7 message object consisting of a ‘Message’ entity. Further, the ‘Message’ entity consists of ‘MessageHeader’, ‘PatientIdentification’, ‘Guarantor’, ‘Insurance’ entities.
    1. public class HL7MessageRoot  
    2. {  
    3.     public MessageMessage  
    4.     {  
    5.         get;  
    6.         set;  
    7.     }  
    8. }  
    9.   
    10. public classMessage  
    11. {  
    12.     public MessageHeaderMessageHeader  
    13.     {  
    14.         get;  
    15.         set;  
    16.     }  
    17.     public PatientIdentificationPatientIdentification   
    18.     {  
    19.         get;  
    20.         set;  
    21.     }  
    22.     public GuarantorGuarantor  
    23.     {  
    24.         get;  
    25.         set;  
    26.     }  
    27.     public InsuranceInsurance   
    28.     {  
    29.         get;  
    30.         set;  
    31.     }  
    32. }  
    For the demo purpose, we are not going to hit the database and build the HL7 message object. Instead, we are going to return a Mock object. Below is the high level code snippet for the same.
    1. public classMockHL7Message   
    2. {  
    3.     public HL7MessageRoot GetHL7Message()   
    4.     {  
    5.         return new HL7MessageRoot   
    6.         {  
    7.             Message = GetMessage()  
    8.         };  
    9.     }  
    10.   
    11.     MessageGetMessage()  
    12.     {  
    13.             return newMessage   
    14.             {  
    15.                 MessageHeader = GetMessageHeader(),  
    16.                     PatientIdentification = GetPatientIdentification(),  
    17.                     Guarantor = GetGuarantor(),  
    18.                     Insurance = GetInsurance()  
    19.             };  
    20.         }  
    21.         .  
    22.         .  
    23.         .  
    24. }  
    Here’s the code snippet of our Collector.
    1. public class HL7MessageCollector  
    2. {  
    3.     public HL7MessageRoot Collect()   
    4.     {  
    5.         // Return Mock Data for now  
    6.         return new MockHL7Message().GetHL7Message();  
    7.     }  
    8. }  
    Let us now discuss about ‘Translators’. We are going to have two of them. One to format the phone number and the other to mark the SSN number. Below is the code snippet for the same.
    1. public class PhoneNumberTranslator: ITranslate  
    2. {  
    3.         public string Translate(stringphoneNumber)  
    4.         {  
    5.             return phoneNumber.ToString()  
    6.                 .Replace("-""")  
    7.                 .Replace("(""")  
    8.                 .Replace(")""");  
    9.         }  
    10.     }  
    11.     //Reused code - http://stackoverflow.com/questions/5254197/format-ssn-using-regex  
    12. public class SSNMaskTranslator: ITranslate   
    13. {  
    14.     publicstring Translate(stringoriginalSSN)   
    15.     {  
    16.         string ssn = originalSSN.ToString();  
    17.         if (ssn.Length < 5) originalSSN = ssn;  
    18.         var trailingNumbers = ssn.Substring(ssn.Length - 4);  
    19.         var leadingNumbers = ssn.Substring(0, ssn.Length - 4);  
    20.         var maskedLeadingNumbers = Regex.Replace(leadingNumbers, @ "[0-9]""X");  
    21.         return maskedLeadingNumbers + trailingNumbers;  
    22.     }  
    23. }  
    Now the last important part our solution is the formatter. As part of the demo sample, we will be having two kinds of formatters. One to format the HL7 Message to XML and the other to JSON. Below is the code snippet of HL7JsonFormatter, we are making use of JSON.NET to serialize the HL7 message object. For displaying the JSON output on console, I am using an optional parameter Formatting.Indented.
    1. public class HL7JsonFormatter: IHL7Formatter  
    2. {  
    3.     public string FormatHL7Message(HL7MessageRoot hL7MessageRoot)  
    4.     {  
    5.         return JsonConvert.SerializeObject(hL7MessageRoot, Formatting.Indented);  
    6.     }  
    7. }  
    Below is the code snippet of HL7XMLFormatter. The “FormatHL7Message” method takes a HL7 Message root object and is responsible for building an XML based HL7 message by creating an XML document and adding XML elements for ‘MessageHeader’,
    ‘PatientIdentification’,’Gaurantor’ and ‘Insurance’.
    1. public class HL7XMLFormatter: IHL7Formatter   
    2. {  
    3.     public string FormatHL7Message(HL7MessageRoot hL7MessageRoot)  
    4.     {  
    5.         XDocumentxDocument = new XDocument();  
    6.         var messageElement = new XElement("Message");  
    7.   
    8.         messageElement.Add(GetMessageHeader(hL7MessageRoot));  
    9.         messageElement.Add(GetPatientIdentification(hL7MessageRoot));  
    10.         messageElement.Add(GetGuarantor(hL7MessageRoot));  
    11.         messageElement.Add(GetInsurance(hL7MessageRoot));  
    12.         xDocument.Add(messageElement);  
    13.   
    14.         StringBuilderstringBuilder = new StringBuilder();  
    15.         using(TextWriter writer = new StringWriter(stringBuilder))  
    16.         {  
    17.             xDocument.Save(writer);  
    18.         }  
    19.   
    20.         returnstringBuilder.ToString();  
    21.     }  
    22.   
    23.     XElementGetMessageHeader(HL7MessageRoot hL7MessageRoot)  
    24.     {  
    25.         return new XElement("MessageHeader",  
    26.             new XElement("DateTimeOfMessage",  
    27.                 hL7MessageRoot.Message.MessageHeader.DateTimeOfMessage),  
    28.             new XElement("MessageType",  
    29.                 hL7MessageRoot.Message.MessageHeader.MessageType),  
    30.             new XElement("OpenDentalVersion",  
    31.                 hL7MessageRoot.Message.MessageHeader.OpenDentalVersion));  
    32.     }  
    33.   
    34.     XElementGetPatientIdentification(HL7MessageRoot hL7MessageRoot)  
    35.     {  
    36.         return new XElement("PatientIdentification",  
    37.             newXElement("NameLast",  
    38.                 hL7MessageRoot.Message.PatientIdentification.NameLast),  
    39.             newXElement("NameFirst",  
    40.                 hL7MessageRoot.Message.PatientIdentification.NameFirst),  
    41.             newXElement("NameMiddle",  
    42.                 hL7MessageRoot.Message.PatientIdentification.NameMiddle),  
    43.             newXElement("DateOfBirth",  
    44.                 hL7MessageRoot.Message.PatientIdentification.DateOfBirth),  
    45.             newXElement("Sex",  
    46.                 hL7MessageRoot.Message.PatientIdentification.Sex),  
    47.             newXElement("AliasFirst",  
    48.                 hL7MessageRoot.Message.PatientIdentification.AliasFirst),  
    49.             newXElement("AddressStreet",  
    50.                 hL7MessageRoot.Message.PatientIdentification.AddressStreet),  
    51.             newXElement("AddressOtherDesignation",  
    52.                 hL7MessageRoot.Message.PatientIdentification.AddressOtherDesignation),  
    53.             newXElement("AddressCity",  
    54.                 hL7MessageRoot.Message.PatientIdentification.AddressCity),  
    55.             newXElement("AddressStateOrProvince",  
    56.                 hL7MessageRoot.Message.PatientIdentification.AddressStateOrProvince),  
    57.             newXElement("AddressZipOrPostalCode",  
    58.                 hL7MessageRoot.Message.PatientIdentification.AddressZipOrPostalCode),  
    59.             newXElement("PhoneHome",  
    60.                 hL7MessageRoot.Message.PatientIdentification.PhoneHome),  
    61.             newXElement("EmailAddressHome",  
    62.                 hL7MessageRoot.Message.PatientIdentification.EmailAddressHome),  
    63.             newXElement("PhoneBusiness",  
    64.                 hL7MessageRoot.Message.PatientIdentification.PhoneBusiness),  
    65.             newXElement("MaritalStatus",  
    66.                 hL7MessageRoot.Message.PatientIdentification.MaritalStatus),  
    67.             newXElement("SSN",  
    68.                 hL7MessageRoot.Message.PatientIdentification.SSN),  
    69.             newXElement("NotePhoneAddress",  
    70.                 hL7MessageRoot.Message.PatientIdentification.NotePhoneAddress),  
    71.             newXElement("NoteMedicalComplete", hL7MessageRoot.Message.PatientIdentification.NoteMedicalComplete));  
    72.     }  
    73.   
    74.     XElementGetGuarantor(HL7MessageRoot hL7MessageRoot)  
    75.     {  
    76.         returnnewXElement("Guarantor",  
    77.             newXElement("NameLast",  
    78.                 hL7MessageRoot.Message.Guarantor.NameLast),  
    79.             newXElement("NameFirst",  
    80.                 hL7MessageRoot.Message.Guarantor.NameFirst),  
    81.             newXElement("NameMiddle",  
    82.                 hL7MessageRoot.Message.Guarantor.NameMiddle),  
    83.             newXElement("AddressStreet",  
    84.                 hL7MessageRoot.Message.Guarantor.AddressStreet),  
    85.             newXElement("AddressOtherDesignation",  
    86.                 hL7MessageRoot.Message.Guarantor.AddressOtherDesignation),  
    87.             newXElement("AddressCity",  
    88.                 hL7MessageRoot.Message.Guarantor.AddressCity),  
    89.             newXElement("AddressStateOrProvince",  
    90.                 hL7MessageRoot.Message.Guarantor.AddressStateOrProvince),  
    91.             newXElement("AddressZipOrPostalCode",  
    92.                 hL7MessageRoot.Message.Guarantor.AddressZipOrPostalCode),  
    93.             newXElement("PhoneHome",  
    94.                 hL7MessageRoot.Message.Guarantor.PhoneHome),  
    95.             newXElement("EmailAddressHome",  
    96.                 hL7MessageRoot.Message.Guarantor.EmailAddressHome),  
    97.             newXElement("PhoneBusiness",  
    98.                 hL7MessageRoot.Message.Guarantor.PhoneBusiness),  
    99.             newXElement("DateOfBirth",  
    100.                 hL7MessageRoot.Message.Guarantor.DateOfBirth),  
    101.             newXElement("Sex",  
    102.                 hL7MessageRoot.Message.Guarantor.Sex),  
    103.             newXElement("GuarantorRelationship",  
    104.                 hL7MessageRoot.Message.Guarantor.GuarantorRelationship),  
    105.             newXElement("SSN",  
    106.                 hL7MessageRoot.Message.Guarantor.SSN),  
    107.             newXElement("EmployerName",  
    108.                 hL7MessageRoot.Message.Guarantor.EmployerName),  
    109.             newXElement("MaritalStatus",  
    110.                 hL7MessageRoot.Message.Guarantor.MaritalStatus));  
    111.     }  
    112.   
    113.     XElementGetInsurance(HL7MessageRoot hL7MessageRoot)  
    114.     {  
    115.         returnnewXElement("Insurance",  
    116.             newXElement("CompanyName",  
    117.                 hL7MessageRoot.Message.Insurance.CompanyName),  
    118.             newXElement("AddressStreet",  
    119.                 hL7MessageRoot.Message.Insurance.AddressStreet),  
    120.             newXElement("AddressOtherDesignation",  
    121.                 hL7MessageRoot.Message.Insurance.AddressOtherDesignation),  
    122.             newXElement("AddressCity",  
    123.                 hL7MessageRoot.Message.Insurance.AddressCity),  
    124.             newXElement("AddressStateOrProvince",  
    125.                 hL7MessageRoot.Message.Insurance.AddressStateOrProvince),  
    126.             newXElement("AddressZipOrPostalCode",  
    127.                 hL7MessageRoot.Message.Insurance.AddressZipOrPostalCode),  
    128.             newXElement("PhoneNumber",  
    129.                 hL7MessageRoot.Message.Insurance.PhoneNumber),  
    130.             newXElement("GroupNumber",  
    131.                 hL7MessageRoot.Message.Insurance.GroupNumber),  
    132.             newXElement("GroupName",  
    133.                 hL7MessageRoot.Message.Insurance.GroupName),  
    134.             newXElement("InsuredGroupEmpName",  
    135.                 hL7MessageRoot.Message.Insurance.InsuredGroupEmpName),  
    136.             newXElement("PlanEffectiveDate",  
    137.                 hL7MessageRoot.Message.Insurance.PlanEffectiveDate),  
    138.             newXElement("PlanExpirationDate",  
    139.                 hL7MessageRoot.Message.Insurance.PlanExpirationDate),  
    140.             newXElement("InsuredsNameLast",  
    141.                 hL7MessageRoot.Message.Insurance.InsuredsNameLast),  
    142.             newXElement("InsuredsNameFirst",  
    143.                 hL7MessageRoot.Message.Insurance.InsuredsNameFirst),  
    144.             newXElement("InsuredsNameMiddle",  
    145.                 hL7MessageRoot.Message.Insurance.InsuredsNameMiddle),  
    146.             newXElement("InsuredsRelationToPat",  
    147.                 hL7MessageRoot.Message.Insurance.InsuredsRelationToPat),  
    148.             newXElement("InsuredsDateOfBirth",  
    149.                 hL7MessageRoot.Message.Insurance.InsuredsDateOfBirth),  
    150.             newXElement("InsuredsAddressStreet",  
    151.                 hL7MessageRoot.Message.Insurance.InsuredsAddressStreet),  
    152.             newXElement("InsuredsAddressOtherDesignation",  
    153.                 hL7MessageRoot.Message.Insurance.InsuredsAddressOtherDesignation),  
    154.             newXElement("InsuredsAddressCity",  
    155.                 hL7MessageRoot.Message.Insurance.InsuredsAddressCity),  
    156.             newXElement("InsuredsAddressStateOrProvince",  
    157.                 hL7MessageRoot.Message.Insurance.InsuredsAddressStateOrProvince),  
    158.             newXElement("InsuredsAddressZipOrPostalCode",  
    159.                 hL7MessageRoot.Message.Insurance.InsuredsAddressZipOrPostalCode),  
    160.             newXElement("AssignmentOfBenefits",  
    161.                 hL7MessageRoot.Message.Insurance.AssignmentOfBenefits),  
    162.             newXElement("ReleaseInformationCode",  
    163.                 hL7MessageRoot.Message.Insurance.ReleaseInformationCode),  
    164.             newXElement("PolicyNumber",  
    165.                 hL7MessageRoot.Message.Insurance.PolicyNumber),  
    166.             newXElement("PolicyDeductible",  
    167.                 hL7MessageRoot.Message.Insurance.PolicyDeductible),  
    168.             newXElement("PolicyLimitAmount",  
    169.                 hL7MessageRoot.Message.Insurance.PolicyLimitAmount),  
    170.             newXElement("InsuredsSex",  
    171.                 hL7MessageRoot.Message.Insurance.InsuredsSex),  
    172.             newXElement("InsuredsSSN",  
    173.                 hL7MessageRoot.Message.Insurance.InsuredsSSN),  
    174.             newXElement("InsuredsPhoneHome",  
    175.                 hL7MessageRoot.Message.Insurance.InsuredsPhoneHome),  
    176.             newXElement("NotePlan",  
    177.                 hL7MessageRoot.Message.Insurance.NotePlan));  
    178.     }  
    179. }  
    Here’s the code snippet of anHL7MessageFormatter,takes the HL7MessageRoot object and formats the as HL7 message per the specified formatter.
    1. public class HL7MessageFormatter   
    2. {  
    3.     HL7MessageRoot hL7MessageRootObject;  
    4.   
    5.     public HL7MessageFormatter(HL7MessageRoot hL7MessageRoot)  
    6.     {  
    7.         hL7MessageRootObject = hL7MessageRoot;  
    8.     }  
    9.   
    10.     public string Format(IHL7Formatter formatter)  
    11.     {  
    12.         return formatter.FormatHL7Message(hL7MessageRootObject);  
    13.     }  
    14. }  
    Let us take a look into the main code and see how we can wire up the collector, translator and formatter. Below is the code snippet for the same. First we gather or collect the data and then we are going to translate the phone number, SSN masking etc. At last, we make a call to format the HL7 message object to XML and JSON by using the formatters that we built.
    1. static void Main(string[] args)  
    2. {  
    3.     // Collect or Gather Data  
    4.     var hL7MessageCollector = newHL7MessageCollector();  
    5.     var hL7MessageRoot = hL7MessageCollector.Collect();  
    6.   
    7.   
    8.     // Translate data  
    9.     Translate(hL7MessageRoot);  
    10.   
    11.     // Format and display as XML  
    12.     Console.WriteLine("Display HL7 message as XML\n");  
    13.     varxmlOutput = FormatToXML(hL7MessageRoot);  
    14.     Console.WriteLine(xmlOutput);  
    15.   
    16.     // Format and display as JSON  
    17.     Console.WriteLine("\nDisplay HL7 message as JSON\n");  
    18.     varjsonOutput = FormatToJSON(hL7MessageRoot);  
    19.     Console.WriteLine(jsonOutput);  
    20.   
    21.     Console.ReadLine();  
    22. }  
    Here’s the code snippet for formatting the HL7 message object.
    1. static stringFormatToXML(HL7MessageRoot hL7MessageRoot)  
    2. {  
    3.     var hL7MessageFormatter = newHL7MessageFormatter(hL7MessageRoot);  
    4.     return hL7MessageFormatter.Format(newHL7XMLFormatter());  
    5. }  
    6.   
    7. static stringFormatToJSON(HL7MessageRoot hL7MessageRoot)  
    8. {  
    9.     var hL7JSONFormatter = newHL7MessageFormatter(hL7MessageRoot);  
    10.     return hL7JSONFormatter.Format(newHL7JsonFormatter());  
    11. }  
    Below is the code snippet of “Translator”, where we are making use of PhoneNumberTranslator to translate or alter the phone number by removing hyphen’s and parenthesis. SSNMaskTranslator to mask the SSN number.
    1. static void Translate(HL7MessageRoot hL7MessageRoot)  
    2. {  
    3.     var phoneNumber = hL7MessageRoot.Message.PatientIdentification.PhoneHome;  
    4.     var phoneNumberTranslator = newPhoneNumberTranslator();  
    5.   
    6.     if (!string.IsNullOrEmpty(phoneNumber))   
    7.     {  
    8.         hL7MessageRoot.Message.PatientIdentification.PhoneHome = phoneNumberTranslator.Translate(phoneNumber);  
    9.     }  
    10.   
    11.     var ssn = hL7MessageRoot.Message.PatientIdentification.SSN;  
    12.     var ssnMaskTranslator = newSSNMaskTranslator();  
    13.   
    14.     if (!string.IsNullOrEmpty(ssn))  
    15.     {  
    16.         hL7MessageRoot.Message.PatientIdentification.SSN = ssnMaskTranslator.Translate(ssn);  
    17.     }  
    18.   
    19.     ssn = hL7MessageRoot.Message.Guarantor.SSN;  
    20.     if (!string.IsNullOrEmpty(ssn))  
    21.     {  
    22.         hL7MessageRoot.Message.Guarantor.SSN = ssnMaskTranslator.Translate(ssn);  
    23.     }  
    24. }  
    HL7 Model Class Diagram

    Diagram

    Formatter Class Diagram

    Diagram

    Translator Class Diagram

    Below is the class diagram of Translator classes.

    Note:

    The below classes are part of the sample application, the translator is optional and purely depends on the domain you are working.

    Diagram

    HL7 Message as XML

    Here’s the screenshot of how the HL7 message gets formatted to XML.

    xml

    HL7 Message as JSON

    Here’s the screenshot of how the HL7 message gets formatted to JSON.

    json