

Introducing Mutations: Putting SOA on Rails for security and maintainability - cypriss
https://developer.uservoice.com/blog/2013/02/27/introducing-mutations-putting-soa-on-rails-for-security-and-maintainability/

======
gingerlime
Having transitioned from Django to Rails nearly a year ago, this post reminded
me of django forms[1]. When I used django, I didn't think much about them, but
moving to Rails, I felt something was missing. Why do validations live
only/primarily on the model? Doesn't it make more sense to do validation
higher-up the chain to filter mistakes and potentially harmful input?

Also a good point about the `attr_accessible` concept. It always felt like a
bit of a crippled way to perform authorization. That said, I'm not sure this
comment is completely valid:

 _> attr_accessible suffers from context blindness: you’re frequently going to
have an end user UI and an admin UI. You want admins to have access to more
fields._

Whilst it's not the most elegant, you can (and should) define `attr_accessible
:x, :y, :z, :as => :admin`

but you have to remember to use something like `MyObj.create({x: 'a', z: 'b'},
:as => :admin)`

[1] <https://docs.djangoproject.com/en/1.5/topics/forms/>

~~~
cypriss
You're right about the attr_accessible statement not being entirely accurate.
Rails 3 does indeed have the concept of contexts. And, we're still on Rails
2.3 :). It also seems like Rails 4 will being going Strong Attribute route.

------
jhund
I really like this approach. I use something similar for my work, however this
has a much nicer API.

Often these patterns are treated and discussed as an either/or decision, which
I don't agree with. There are many places in an app where the default and
simple Rails way is the right choice. However as apps get more complex, there
is also a need for approaches like the one presented here.

My rule for choosing the right approach is this:

If it's just a simple CRUD controller where it receives input from a simple
form and writes attributes to a single model, with no AR callbacks involved,
then omakase is perfectly acceptable. In that case I don't want to have to
jump through all kinds of hoops and overcome unnecessary friction added by
enterprisey patterns.

However, as soon as I have to use AR callbacks, add more lines to my
controller action, or touch more than one model, that's a clear indication
that a service object makes sense.

Mutations looks like a great solution for the latter case. It brings in some
DCI goodness as well. The key is that some operations require complex context
(object graphs, permissions, dependent operations), and that context should be
treated as a first class citizen.

------
snikch
I'm a big fan of this pattern. We use a smaller contextual form process that
includes ActiveModel::Validations and provides some simple defining methods,
and does a very similar thing. Since most services / mutations are generally
bound to a form, it's just called BaseForm, and its children are LoginForm,
TweetForm etc.

------
bdittmer
I'm glad to see rails devs are finally coming to their senses and exploring
other patterns for building applications. SOA, command objects, etc. have been
a pretty common pattern in "enterprise-y" frameworks for awhile. This is
making rails look more like grails, which I think is a good thing.

~~~
thibaut_barrere
It's not new in Rails apps actually; it's only new if you've been reading
vocal blog posts (including the ones a few years back bashing design patterns
etc).

At least for the apps I've been maintaining as a freelance, quite often they
had services of some kind and other classic patterns.

Many Rails developers I know come from a Java and/or .Net background, so this
is not really a surprise :-)

------
jbverschoor
Is there a reason why it was not created with multiple commands / methods in
mind? :

    
    
      class UserService
      service :signup do |config|
        config.required do end
        config.optional do end
        config.execute
            # signup stuff here
        end
      end
       
      service :delete_account do |config|
        config.required do end
        config.optional do end
        config.execute
            # signup stuff here
        end
      end
      end
    
    

This would also make it possible to do service description and even expose a
json or xml api using just this.

But I guess the controllers are created for this. DHH says controllers are
responsible for mailing and stuff, which is more correct than in the models
with callbacks.

~~~
cypriss
It seems like what you've done there is create two mutations (signup and
delete_account) within a single namespace. You can certainly do that with the
Mutations gem.

What I think you're getting at is: can you just _replace_ controllers with
mutations? That's an interesting concept that I've thought a bit about. In
practice, we have found controllers to still be useful in collecting some
information and doing 'wiring'. They're also nice because your SOA shouldn't
really be concerned with the presentation of the response.

Finally, where do you put your mailings? You're right that controllers >
models, but I'd also say that mutations > controllers > models.

------
sandstrom
This seems like a great idea! I have a few thoughts that comes to mind,
perhaps others can chip in:

\- How can this be used in tandem with strong_parameters; should ensuring the
structure of params be a responsibility of the controller or the mutation (I'm
leaning towards the former)? [1]

\- How can validation duplication between models and mutations be avoided? In
the example 'string :email, matches: EMAIL_REGEX' was used, but presumably one
will still validate the email in the model. I'm guessing there are solutions
to this, perhaps the existing model validation can somehow be used?

[1]: <https://github.com/rails/strong_parameters>

~~~
cypriss
We're using this in Rails 2.3 without Strong Parameters. Mutations whitelist
input just like strong parameters do. I don't really see why you would use
both, although you certainly could.

The duplicate of validations between models and mutations is interesting.
Currently at UV, we have some duplication -- however, in other cases we're
relying entirely on the validations of the Mutation. I do think it's possible
to rely entirely on validation from mutations -- I need to get some more real-
world experience with this to know how well it will work out :)

------
nchuhoai
How would Mutations compare with Concerns, which seems to be DHH's preferred
method?

~~~
bemurphy
One big difference would be encapsulation.

With mixins (sometimes called Concerns), you have a shared state between the
module and the class including it.

With a real class, you have your own space and (probably) don't have to deal
with state and method collisions.

Pros/cons to each surely, and a lot is a matter of taste at that.

------
nchuhoai
What are other approaches/patterns to more SOA to manage more complex code
bases with Rails?

------
zpe
Looks like typed function to me.

~~~
cypriss
That's exactly the analogy I was going for. You don't want your general ruby
code to be typed-checked, but it's actually fairly important to make sure your
user inputs are checked, especially until Rails battens down its hatches.

------
timrosenblatt
I heard DHH hates SOA, but the only reference I can find to this is an old
interview from 2006. Anyone have a link to anything recent?

------
alxndr
Web site breaks page zoom. Argh.

------
vaprem911
I almost fell off my chair laughing when I saw "Rails" and "Security" and
"Maintainability" in the same sentence.

A quick search here itself on HN will show you how immature and security-hole
ridden Rails is. Built for, and by, HIPSTERS!

~~~
gnufied
Really? Did you read the article? It argues that using this pattern can reduce
security risks because conformity of input data is more thoroughly tested
before passing off to ActiveRecord.

