Today I was playing around with some code to show people how Scala traits (more specifically stackable traits) make it much easier to decompose your ‘problem’ into generic and reusable building blocks.
One of the issues we recently looked at was retrying a query which failed for some reason; which might not sound like something you’ed want to retry.. but believe me; in some cases it makes sense.
So, say we have a very simple repository definition to access a database:
We can now ‘implement’ a couple of dummy repositories to play with:
As you can see we have three different repositories, with their own unique characteristics. Calling the first two repositories would result in exceptions. The second one does however work when we call retry the call a couple of time. Now, enter the Retrying trait:
As you probably guess from the name: this trait will add retrying functionality to the class which it is mixed in to. So, how does it work:
- The abstract modifier on the override of the withConnection allows us to change the behavior of the method we’re overriding
- The super.withConnection call in the retry function is still referring to the actual implementation of the class the trait is mixed in to (well, or the first override in the inheritance chain)
We can mix this trait into a repository in the class definition, or… more interesting… at instantiation time:
The above code will print something like:
number of tries left: 3 number of tries left: 2 number of tries left: 1 number of tries left: 0 failed result: None number of tries left: 3 number of tries left: 2 number of tries left: 1 success result: Some(should fail first, succeed later) number of tries left: 3 success result: Some(should not fail result)
Surely this is only the tip of the iceberg. But as you can see, this is a very powerful mechanism to create a very modular set of very specialized components; each with their very specific purpose. High cohesion… low coupling!
Ow… and yes I notice my retry function is not tail recursive; still working on that…