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.
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 )
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.
Sorry for the pedantry but should 'trasports' be 'transports'?
I am rubbish at spelling though, so I'm probably wrong, or misreading.
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
Fixed the title :) Thanks for the catch!
typo line 42 transport