Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at cf.Objective() 2012 (Minneapolis, MN) with: Nathan Strutz
Ben Nadel at cf.Objective() 2012 (Minneapolis, MN) with: Nathan Strutz@nathanstrutz )

The Beauty Of Modular Code: Adding Different Transports To StatsDGateway

By Ben Nadel on
Tags: ColdFusion

Earlier this week, I released StatsDGateway, which is a set of ColdFusion components that facilitate the sending of messages from ColdFusion to a statsD server. When I first wrote the library, it was a single component. However, after watching Corey Haines presentation on Simple Design, I went back and refactored the single component into multiple components that each have a much more focused responsibility. The beauty of this is that I can now easily swap in-and-out different implementations to achieve different behaviors. And, this is exactly what I've done with the transport implementations.

View the StatsDGatway.cfc project on my GitHub account.

When you construct a StatsDClient.cfc instance manually (ie, not using the factory in the StatsDGateway.cfc), you have to use constructor-injection to provide the client with a transportation implementation that adheres to the following interface:

  • sendMessage( message )
  • destroy()
  • isDestroyed()

The actual mechanism of the message sending is totally hidden and left up to the given transport implementation. As such, I can easily change the type of transportation to use UDP or HTTP or even to prevent message-sending altogether and just keep the messages in an internal queue - CaptureTransport.cfc (which is great for unit testing).

The trasport components end up being composable as well. So, I can wrap one transport, such as UDPTransport.cfc, inside of another transport like BufferedTransport.cfc, that will buffer messages until they reach a certain size and then send them all as one concatenated string.

  • <cfscript>
  •  
  • // Create a statsD client that uses a transient UDP socket (one that is created and
  • // closed for each message, which prevents the need for explicit clean-up).
  • client = new lib.client.StatsDClient(
  • new lib.transport.UDPTransport(),
  • new lib.sampler.RandomSampler()
  • );
  •  
  • // Create a statsD client that uses a persistent UDP socket (one that will remain
  • // open for the lifetime of the statsD client, until .destroy() is called).
  • client = new lib.client.StatsDClient(
  • new lib.transport.PersistentUDPTransport(),
  • new lib.sampler.RandomSampler()
  • );
  •  
  • // Create a statsD client that will buffer messages until they reach a max length
  • // of 500 characters, at which point they will sent out as a \n-delimited list.
  • client = new lib.client.StatsDClient(
  • new lib.transport.BufferedTransport(
  • new lib.transport.UDPTransport(),
  • 500
  • ),
  • new lib.sampler.RandomSampler()
  • );
  •  
  • // ---
  •  
  • // Create a statsD client that doesn't actually send messages to statsD. Instead,
  • // the messages will be captured in an internal queue that can be used to test the
  • // throughput of the statsD client.
  • transport = new lib.transport.CaptureTransport();
  •  
  • client = new lib.client.StatsDClient(
  • transport,
  • new lib.sampler.RandomSampler()
  • );
  •  
  • // ... send messages.
  •  
  • // Check to see that the client actually sent messages.
  • writeDump( trasnport.getSentMessages() );
  •  
  • </cfscript>

This might seem really obvious to mature programmers; but, I have never felt very confident in my object design. So for me, breaking a component up and then seeing how much the modularity facilitates extending the functionality is an extremely rewarding process. For the first time in my life, I think I'm starting to understand the Single Responsibility Principle, which is of course, one of Uncle Bob Martin's SOLID principles.



Looking For A New Job?

100% of job board revenue is donated to Kiva. Loans that change livesFind out more »

Reader Comments

Hi Ben,

Sorry for the pedantry but should 'trasports' be 'transports'?
I am rubbish at spelling though, so I'm probably wrong, or misreading.

Adam

@Adam,

Ugg, you are totally right :( Sometimes, I am baffled at how my spellchecker doesn't catch these things... or how my brain selectively ignores them :D