June 10, 2011 at 3:13 pm
filed under Uncategorized
The happy path design pattern involves pushing down improbable but important worst-case scenario execution paths to the deepest part of your application so they are only executed in the exceptional circumstances at which they occur.
I wrote this because I’m unaware of it being written anywhere else.
Developers are trained to code cautiously. Errors and exceptions are predicted and trapped, databases are read before insertions and updates happen, we check caches before we attempt to purge their items. Our code is littered with conditional constructs to determine whether or not we should to do something.
The problem also extends to business analysts. Functional specifications are full of edge cases, ‘a user can’t do this if they’ve already done such and such.’
Your code probably has a lot of this going on :-
if (thing.exists === false)
thing.create
else
thing.update
end
Each execution cycle is pessimistic, assuming the worst will happen (that the thing exists and we will try to create it again rather than update it) and so you are, understandably, trying to prevent that and avoid your boss/colleagues/clients from thinking you are bad at your job (by building things that don’t result in error states).
This approach doesn’t always scale.
In many circumstances the chosen path is overwhelmingly going to be one or the other. Lets call this the happy path, the other paths being edge cases, 1/1000 events that will break your system but hardly ever happen.
Here’s an example of our simple code sample presented as a registration system where a user object is queried with a unique id (Eg, an email address) and then created if it doesn’t exist, otherwise updated.
if (user.exists(uid) === false)
user.create(uid)
else
user.update(uid, telephone)
end
How many times a day does somebody attempt to register with the same email address as someone else or bypass your systems security to update a user that doesn’t exist? Both these requirements are essential but may be edge cases in some systems.
In this case we make a minimum of two calls to the user object for every registration/update - a read (exists) and a create or update.
Twice doesn’t sound too bad, and it’s often not of course. But let’s say the user object is talking to a web service in a different data centre, each function (exists, create, update) maps to a HTTP method (GET, PUT, POST) on stateless, RESTful API. Let’s also say your site is popular to the tune of 1000’s of requests a second at certain times of the week.
You’ve just doubled the network traffic and operations on that database because of an edge case.
Lets look at this problem again. We are wrapping edge cases around our optimal path. Is there a better way we can solve this? What would our code look like if we assumed the best-case scenario usually happens?
In the following example we pass the error handling down to the deepest application layer and the handle the exceptional case if and when it occurs. It works nicely when you know that one path is far more likely to happen the the other.
// assume something will blow up underneath if bad, Eg. mysql
// duplicate primary key error) and then catch that exception
try
user.create(uid)
catch(e)
user.update(uid, telephone)
The above example is crude but came about from high-transaction survey software that I was helping design and build at work.
We found while load testing we could tear out many cautious calls (mostly reads, naturally) to sub-systems like memcached that would throw graceful exceptions when, for example, we tried to create something that was already there. Likewise our HTTP API would send back 409 code when tried to update something that had gone stale.
It’s a simple trick that had a significant impact on our performance.
It’s interesting to me as a developer because knowingly causing lower level errors/exections is counter-intuative to what we’ve been trained to do.
RSS /
Comments are closed.
Anthony Green
The examples you’ve cited strike me as ant-patterns. The first two are examples of the kind of defensive coding I frequently encounter when the author hasn’t approached the design in a methodical manner through test-driven development and lacks confidence in the behaviour of the system. The branching makes the method take on dual responsibilities and is less deterministic. The approach of using error handling as flow control is also considered an anti-pattern. Exceptions should be used when the system behaves unexpectedly and you need to determine whether to continue or exit. I certainly wouldn’t use them to change state.
Adrian
Like the blog mate. It was interesting reading this one. I remember reading a similar article years ago when I was coding.
It is funny how some things just keep going round and some lessons keep getting learnt :o)