software solutions / project leadership / agile coaching and training

Ruby on Rails and the Single Responsibility Principle

Posted on October 19, 2010

Several people took issue with my last post about Ruby on Rails saying that my example violates the Single Responsibility Principle. Here’s where I stand on this.

The SOLID principles are principles, not hard and fast laws of software development. When applying the SOLID principles, common sense should rule. There are times when violating the textbook definition of the principles is OK (regardless of what language you are using). The ultimate goal of all design principles should be reducing the cost of change. Reducing the cost of change comes in many forms:

  • Being able to get things done faster because you have to write less code
  • Being able to get things done faster because it’s easier to figure out code and what you need to change
  • Being able to get things done faster because code is wrapped in tests so you can change it and not worry about breaking things
  • Being able to get things done faster because you have automated tests which decrease the amount of manual testing that you or your QA team needs to do
  • Being able to get things done safer by having abstraction layers in place so that changing code in one place doesn’t break something else
  • Having well defined requirements so that you don’t waste time on rework
  • Having business sponsors available to answer questions quickly
  • Many more reasons that I’m not thinking of

With this in mind, all of my design decisions, including how I apply the SOLID principles, need to reduce the cost of change.

Also, keep in mind that the SOLID principles were written with static-typed languages in mind (C++, Java, .NET). While a lot of the same principles apply in dynamic languages, you can’t assume that everything is the same. In fact, the “I” and “D” of the SOLID principles arguably don’t apply to dynamic languages at all.

Several people said that my Ruby class violated SRP because it mixed class behavior, database mapping, and validation all in one class. Again, this is where I would take issue with your application of SRP. Here is the Ruby class in question:

class User < ActiveRecord::Base
belongs_to :user_status
has_many :roles, :through => :user_roles
validates_length_of :name, :maximum => 100
named_scope :active, :conditions => ["user_status.id = ?", UserStatus::ACTIVE]
end

On my last .NET project, I had domain model classes like this:

public class User : Entity
{
public virtual long Id { get; set; }
[Required("Name"), Length(50)]
public virtual string Name { get; set; }
public virtual IList UserRoles { get; set; }

public virtual bool IsInRole(string role)
{
// code here
}
}

I was using Fluent NHibernate as my ORM on this project, so I had a database table that looked like this object. So in this case, I have class behavior (IsInRole method), validation (the attributes on the Name property), and implied database mapping (using Fluent NHibernate conventions). How is this any different than my Ruby class?

Some people might argue that the textbook definition of SRP would say that I shouldn’t put validation attributes on properties like that. What is your other alternative? Write a line of custom code in an IsValid method or in another validator class, I assume. Is that reducing the cost of change? If I write custom code, I have to write a test for it. If I write boilerplate stuff like “[Required("Name")]” or “validates_presence_of :name”, that is so simple that I’m not going to write a test for it because I don’t think I gain anything from doing so. If I can add a whole bunch of behavior in just one line of code, then potentially violating the textbook definition of SRP is OK with me because it’s way easier to do it that way and it just makes sense.

Back to my Ruby class. My class knows how to load, save, and delete itself. In .NET, I would say this is a bad thing. First of all, I wouldn’t be able to stub out the database in a test (which I can in Ruby because I can stub out anything). Second, if you have multiple entities that do things a certain way (e.g. multiple entities that need to be cached when loading, saving, or deleting them), you would have no easy way to do that without duplicating some code (in Ruby, I can create a module and include it in a class, and in that way I could override a method like “save”). Third, I would have a direct dependence on the database, which violates the Dependency Inversion Principle (which doesn’t apply in Ruby, because all I’m depending on is some class out there with a method with a certain name, as opposed to .NET where I’m depending on a method in a class in an assembly in a .dll with a specific version). So as you can see, your domain model takes on a much different shape in Ruby vs. .NET. Both ways have their pluses and minuses, but each way is the best way for the language that you’re using.

There is one area where I see an interesting clash of Ruby and SRP. Because I don’t have to use dependency injection and because I can stub out any method in a test, it makes sense to put all methods relating to an entity object into the entity class itself. This just makes sense, and TDD would lead you in that direction. Doing this will lead you into having some really big domain model classes though.

Would this violate the textbook definition of SRP? I would say yes. You run into some of the issues that you have with big classes (harder to read). In .NET, we would argue that we should move a lot of this behavior into domain service classes, which would be smaller and easier to use in other classes (by taking the interface into the constructor of the class that was going to use it). In .NET, you have to pull things out of the entity object in order to be able to stub it out in a test, so at that point you might as well go the rest of the way and make small classes. But if you had the flexibility of being able to stub out anything in a test and you had Ruby-style mixins in .NET, would you really do it this way? One could argue that the .NET way of doing things in domain services is an example of Martin Fowler’s anemic domain model anti-pattern.

In both languages, you are making a tradeoff. In .NET, we are saying that testability is more important that potentially having an anemic domain model and having model behavior scattered around in domain services. In Ruby, we are saying that we would rather have all of the behavior in the entity class itself because it makes sense to have it there (we don’t have to worry about testability because everything is testable), even though we could end up with some big classes. Neither way is necessarily wrong, it just depends on the language you’re working in. In some cases, the .NET and Ruby communities have generally agreed on their own “best practices” and ways of doing things, and that’s just how people do things in those languages, often because the languages and frameworks make it easier to do it that way.

In summary, it depends, and use common sense. Remember what your goal is — to deliver working software. Your architecture is not the deliverable, and it’s not what makes money for the business. Flexibility, design patterns, abstraction layers, and the like often come at varying levels of cost. It’s up to you decide if it’s worth it in your situation.

7 Comments »

  1. TLDR Version, .NET kills unicorns

    Jimmy Trowbridge — October 19, 2010 @ 3:31 pm

  2. “Remember what your goal is — to deliver working software.”
    When we’re talking about the SOLID principles, I guess the objective is to deliver maintainable working software.
    It’d be interesting to hear if using the ActiveRecord pattern in Rails has turned out to have a positive or negiative impact on code maintainability.

    David — October 19, 2010 @ 7:18 pm

  3. “It’d be interesting to hear if using the ActiveRecord pattern in Rails has turned out to have a positive or negiative impact on code maintainability.”

    Answer: positive

    I honestly don’t understand why people think that code would be less maintainable this way. But I’ve been programming in Ruby for 5 years now and haven’t written compiled code since Y2K, so I could be missing out on something.

    Ben Wagaman — October 19, 2010 @ 10:14 pm

  4. Jon,

    Excellent point about common sense over rigid rules. I was too rigid myself with my previous comment. In fact, I do believe that rails are fantastic when you want to build a forms-over-data site. Putting class behavior and data persistence in one place is fine as long as you don’t have much of either. When it grows, however, do you have an option for separating these things?

    Re: testability. Many folks have mentioned that we don’t do DI and stuff for testability purposes. Rather, testability (often) guides us towards an (arguably) better design. With profiler-based mock frameworks I *am* able to stub out the database even with the AR pattern (although in a data-centric app you’d rather not). So, testability is still another guideline, and shouldn’t prevent you from using AR in the .Net world, what you think?

    ulu — October 22, 2010 @ 6:10 am

  5. I’d like to see this where you have a physical tier separation using a services based approach – do you have the same serialization of object graph issues, etc…

    ie. I’d like to see a setup in Ruby similiar to the old ’3 tier’ approach of web server -> DTO -> service layer -> data layer on 3n boxes

    The point of ‘domain services’ at least in terms of, ie WCF RIA is to achieve that.

    If you care to shed some light on how you’d do that in Ruby on Rails I’d like to learn more!

    Steve — November 16, 2010 @ 10:32 pm

  6. Hi, could you elaborate on why the “ID” of “SOLID” don’t apply to dynamic languages? I’m been struggling with this since moving to some Javascript after a life of static languages? I know you mentioned the D part in the past, but I didn’t really get the explanation. Thanks!

    William Mays — July 4, 2011 @ 2:24 pm

  7. “Also, keep in mind that the SOLID principles were written with static-typed languages in mind (C++, Java, .NET).”

    This is flatly incorrect. Do some research on Smalltalk, the invention of OO, and when those languages were developed.

    Kevin Stevens — May 8, 2013 @ 11:06 am

Leave a comment





SERVICES
SOFTWARE SOLUTIONS
I have over 10 years of software development experience on several different platforms (mostly Ruby and .NET). I recognize that software is expensive, so I'm always trying to find ways to speed up the software development process, but at the same time remembering that high quality is essential to building software that stands the test of time.
PROJECT LEADERSHIP
I have experience leading and architecting large Agile software projects and coordinating all aspects of a project's lifecycle. Whether you're looking for technical expertise or someone to lead all aspects of an Agile project, I have proven experience from multiple projects in different environments that can help make your project a success.
AGILE COACHING
I believe that Agile processes and tools should be applied with common sense. I've spent the last 6 years working on Agile projects as a consultant in many different environments, both in leadership roles and as a practitioner doing the work. I can help you find out how Agile can work best in your organization, not just apply a prescriptive process.
TEST DRIVEN DEVELOPMENT TRAINING
TDD Boot Camp is a hands-on, three day, comprehensive training course that will teach you all of the skills, tools, frameworks that you will need to use test-driven development to develop real world .NET applications. If you're not looking for something that intensive, check out the the half-day version.
Have any questions? Contact me for more information.
PRESENTATIONS
The Business of You: 10 Steps For Running Your Career Like a Business
From CONDG 2012, Stir Trek 2014
From Stir Trek 2013, DogFoodCon 2013
From Stir Trek 2012, QA or the Highway 2014
(presented with Brandon Childers, Chris Hoover, Laurel Odronic, and Lan Bloch from IGS Energy) from Path to Agility 2012
(presented with Paul Bahler and Kevin Chivington from IGS Energy)
From CodeMash 2011
An idea of how to make JavaScript testable, presented at Stir Trek 2011. The world of JavaScript frameworks has changed greatly since then, but I still agree with the concepts.
A description of how test-driven development works along with some hands-on examples.
From CodeMash 2010
From CodeMash 2010