Saturday, April 14, 2012

bottleneck through a parent

Visualize how the "contact us" form at a vanity web site might bind to this:

using System;
namespace Bottleneck.Core
{
   public class UserSubmission
   {
      public string FullNameOfUser { get; set; }
      public string UserEmailAddress { get; set; }
      public string UserPhoneNumber { get; set; }
      public string UserComments { get; set; }
      public DateTime SubmissionDate { get; private set; }
      
      public UserSubmission()
      {
         SubmissionDate = Clock.CurrentTime();
      }
   }
}

 
 

Alrighty, what are some things we might do with our object that records the values a user entered for his or her name, email address, phone number and thoughts (UserComments)? We might:

  1. create a lead in SalesForce
  2. alert the our sales team with an email
  3. send an email response back to the user
  4. save a record at our database

 
 

Hmmm... all four are calls to external dependencies, this is starting to sound like a Saga process. Well, yes, but, I don't have the chops to write a Saga just yet. Also, I want to keep this example simple, because I wish to illustrate something different altogether. (The Saga post will have to come another day.) Let's just assume that we are just going to send our model to a static method called Save() in a static class called UserSubmissionProcessing which will have four lines of code for the four acts:

  1. CrmSubmission.Submit(item);
  2. InHouseEmailAlert.Send(item);
  3. OutboundEmailAlert.Send(item);
  4. Persistence.Save(item);

 
 

And now...the point! What if you were maintaining this site and it started to grow as the startup you worked for started to do better and better? What if you hosted free events at your facility and wanted to manage the registration yourself instead of using Eventbrite. If the events are free and there is no credit card processing, there is really no reason why you couldn't have a user give his or her name, email address, phone number and thoughts at a form not unlike the "contact us" form. However, more tricky, the way the form would be processed would have to vary. You might want to send a different confirmation email or not send a confirmation email at all if the event was full up. Beyond this let's get more complicated and have it that the same web site lets visitors download whitepapers, request sales appointments, and occasionally raffles off prizes. The different varieties of form submissions naturally must be handled differently while nonetheless have many needs in common. It seems reasonable to have UserComments be a parent to a smattering of child objects that will give it variance. Our Save() method in UserSubmissionProcessing will still consume a UserComments object, but will vary what it does with the object based upon the nature of the child. I've seen a pattern like this before. It adheres to the DRY Principal (Don't Repeat Yourself) and thus seems superior to having five different Save() methods for the five varieties of form submissions mentioned above. Child objects of UserComments will get up upcast to a UserComments object which will be handed to UserSubmissionProcessing and then the "childish nature" will get rediscovered with the Save() method and considered on the other side of the parental bottleneck.

 
 

At first glance you might think the extra bits of information associated with a child might get lost in upcasting like so:

WhitePaperDownload whitePaperDownload = (WhitePaperDownload)userSubmission;
whitePaperDownload.UrlForDownload = "http://www.example.com/";
whitePaperDownload.WhitePaperName = "How To Tie Your Shoes";
UserSubmissionProcessing.Save(whitePaperDownload);

 
 

...when the Save() signature looks like so:

public static void Save(UserSubmission item)

 
 

The child data does not get lost. This is one of the interesting things I found reading the O'Reilly book: C# 4.0 in a Nutshell by Joseph Albahari and Ben Albahari. Pages 78 and 79 touch on this. I will show how the child information is recovered. First, here are our children:

  1. using System;
    namespace Bottleneck.Core
    {
       public class EventRegistration : UserSubmission
       {
          public Guid EventId { get; set; }
       }
    }
     
  2. using System;
    namespace Bottleneck.Core
    {
       public class DrawingRegistration : UserSubmission
       {
          public Guid DrawingId { get; set; }
       }
    }
     
  3. namespace Bottleneck.Core
    {
       public class WhitePaperDownload : UserSubmission
       {
          public string WhitePaperName { get; set; }
          public string UrlForDownload { get; set; }
       }
    }
     
  4. namespace Bottleneck.Core
    {
       public class RequestForAppointment : UserSubmission
       {
       }
    }
     

 
 

What follows is UserSubmissionProcessing. It is now much more than four lines. I do not suggest this is great code, but it does exposition what I wish to exposition. The use of the keyword "is" is the most important thing to see below. Note that a couple of the methods used in UserSubmissionProcessing have had their signatures changed so that they too may handle a greater variety of user submissions.

namespace Bottleneck.Core
{
   public static class UserSubmissionProcessing
   {
      public static void Save(UserSubmission item)
      {
         
//1. submit to SalesForce
         if(item is RequestForAppointment)
         {
            CrmSubmission.Submit(item, SalesPriority.High);
         } else {
            CrmSubmission.Submit(item, SalesPriority.Medium);
         }
         
         
//2. send email alert to internal sales manager
         InHouseEmailAlert.Send(item);
         
         
//3. register user for raffle or free event if applicable
         bool? going = null;
         if (item is DrawingRegistration)
         {
            Registration.RegisterForDrawing((DrawingRegistration)item);
            going = true;
         }
         if (item is EventRegistration)
         {
            going = Registration.RegisterForEvent((EventRegistration)item);
         }
         
         
//4. send response to user
         EmailResponse email = new EmailResponse();
         if (item is WhitePaperDownload)
         {
            WhitePaperDownload request = (WhitePaperDownload) item;
            email.SubjectLine = "Here is your whitepaper!";
            email.TextOnly = TextTemplatesForEmail.HereIsYourWhitePaper();
            email.Html = HtmlTemplatesForEmail.HereIsYourWhitePaper();
            email.LinkWithinEmailBodyUrl = request.UrlForDownload;
            email.LinkWithinEmailBodyVanityName = request.WhitePaperName;
         } else {
            if (going == null)
            {
               email.SubjectLine = "Thank you for your response!";
               email.TextOnly = TextTemplatesForEmail.ThankYouForYourResponse();
               email.Html = HtmlTemplatesForEmail.ThankYouForYourResponse();
            }
            if (going == true)
            {
               email.SubjectLine = "Thank you for registering!";
               email.TextOnly = TextTemplatesForEmail.ThankYouForRegistering();
               email.Html = HtmlTemplatesForEmail.ThankYouForRegistering();
            }
            if (going == false)
            {
               email.SubjectLine = "Sorry, but our event is full.";
               email.TextOnly = TextTemplatesForEmail.SorryButOurEventIsFull();
               email.Html = HtmlTemplatesForEmail.SorryButOurEventIsFull();
            }
         }
         OutboundEmailAlert.Send(email, item);
         
         
//5. save record to database
         Persistence.Save(item);
      }
   }
}

 
 

Page 87 of the book I'm reading shows how to get at child specs by way of reflection. Here is the UserSubmissionProcessing refactored to use reflection. This code is a little worse because it uses object at the method signature and then assumes that the object is a UserSubmission or the child of a UserSubmission.

namespace Bottleneck.Core
{
   public static class UserSubmissionProcessing
   {
      public static void Save(object item)
      {
         
//1. submit to SalesForce
         if(item.GetType() == typeof(RequestForAppointment))
         {
            CrmSubmission.Submit((UserSubmission)item, SalesPriority.High);
         } else {
            CrmSubmission.Submit((UserSubmission)item, SalesPriority.Medium);
         }
         
         
//2. send email alert to internal sales manager
         InHouseEmailAlert.Send((UserSubmission)item);
         
         
//3. register user for raffle or free event if applicable
         bool? going = null;
         if (item.GetType() == typeof(DrawingRegistration))
         {
            Registration.RegisterForDrawing((DrawingRegistration)item);
            going = true;
         }
         if (item.GetType() == typeof(EventRegistration))
         {
            going = Registration.RegisterForEvent((EventRegistration)item);
         }
         
         
//4. send response to user
         EmailResponse email = new EmailResponse();
         if (item.GetType() == typeof(WhitePaperDownload))
         {
            WhitePaperDownload request = (WhitePaperDownload) item;
            email.SubjectLine = "Here is your whitepaper!";
            email.TextOnly = TextTemplatesForEmail.HereIsYourWhitePaper();
            email.Html = HtmlTemplatesForEmail.HereIsYourWhitePaper();
            email.LinkWithinEmailBodyUrl = request.UrlForDownload;
            email.LinkWithinEmailBodyVanityName = request.WhitePaperName;
         } else {
            if (going == null)
            {
               email.SubjectLine = "Thank you for your response!";
               email.TextOnly = TextTemplatesForEmail.ThankYouForYourResponse();
               email.Html = HtmlTemplatesForEmail.ThankYouForYourResponse();
            }
            if (going == true)
            {
               email.SubjectLine = "Thank you for registering!";
               email.TextOnly = TextTemplatesForEmail.ThankYouForRegistering();
               email.Html = HtmlTemplatesForEmail.ThankYouForRegistering();
            }
            if (going == false)
            {
               email.SubjectLine = "Sorry, but our event is full.";
               email.TextOnly = TextTemplatesForEmail.SorryButOurEventIsFull();
               email.Html = HtmlTemplatesForEmail.SorryButOurEventIsFull();
            }
         }
         OutboundEmailAlert.Send(email, (UserSubmission)item);
         
         
//5. save record to database
         Persistence.Save(item);
      }
   }
}

No comments:

Post a Comment