The UserVoice developer blog posted an interesting article yesterday talking about how they solve “The Rails Problem” of complex Rails apps having obese models that stymie code re-use. The naive approach is just to make classes.
UserVoice’s approach is different: they made a DSL for describing service calls. The thing is, it’s sort of a type system - and a verbose one at that.
UserVoice’s approach is called “mutations” and it’s more than just method calls. You can specify quite a bit about our service calls, all to make the underlying logic very simple. For example, they have a “user signup” service and, in the most naive, but safe, way, it would look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
This is a very paranoid, but rock solid implementation. If you screw up calling it, you’ll know why. In Mutations, this code would look like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
This is fairly interesting, as the “business logic” (the code in
execute) is clean - it’s just the bare logic. The sanity
checking and other paranoia is handled by the framework. Likely that tests of this are simpler as well - you don’t need to
test the validations. While this is great, I can’t help thinking that “every implementation
of parameter validation in Ruby contains an ad-hoc, informally-specified, bug-ridden, slow implementation of a real type system”.
To demonstrate, here’s what this class would look like in Scala:
1 2 3 4 5 6 7 8 9 10 11 12 13
That’s it. No special DSL, no custom framework, nothing. Just the programming language. Why?
First, we assume that
null (Scala’s analog of
nil) is always a bug. Good Scala programs are designed this way, and it’s
not that bad to program without null, so a declaration like
name:String in Scala means “name is a required
Second, optional parameters use the
Option type to indicate their optionality.
Next, for validating our email, we use the type system. Instead of using a
String for storing email addresses (the
hallmark of every stringly typed application), we require an instance of
1 2 3 4 5 6 7 8
UserSignup code can be absolutely sure that it gets a valid email. Validating that email happens elsewhere, as
Finally, our callsite uses the same method that our class defines. Under mutations, you define a method called
you call a method called
run. Both just take a hash, making the callsite somewhat opaque as to what’s being passed in and
requiring you to know how the framework works in order to piece together what’s being called. In Scala, you just call the
method that you defined.
There’s no magic here, no framework, nothing other than idiomatic Scala code. I like the way it encourages us to create a rich set of types as opposed to strings and hashtables everywhere. Types allow us to encode our understanding of the system, domain, and logic - that’s what they are for. Statically checking that those types are used properly is a sanity check that we’ve correctly encoded our understanding.
Also note how not-that-verbose the Scala code is, compared to the Ruby code. The Java equivalent could not make that claim.
Anyway, I think Mutations looks like a cool library, and I plan on checking it out for writing Rails apps. I did think it was worth pointing out that the problem of separating argument validation from method logic is largely a solved problem - by statically typed languages.