Friday, December 30, 2011

Rails is still cool

There are many discussions recently on things that suck in Rails. Specifically, it's about how people misuse Rails. I think we need more OOP in Rails.

Let me explain.

Disclaimer: I started using Rails in 2004, now I run a Rails consultancy and I've been more or less involved in about 30-35 Rails projects. I'm also involved in building a"social games for brands" product called GameBoxed, which proudly runs Rails 3.1 under the hood.

Rails history and background


When Rails was released in 2004, I don't remember that DHH was saying it was perfect for everything. He showed us Rails and said: "look, this solves many problems of a typical web app". He was right. Somehow, it was assumed that the Rails way is the only way. This led to some people complaining about how Rails doesn't fit well into their more complex apps - I was one of such complainers, shame on me.

Rails is awesome for starting an app


Let me repeat - Rails is great for starting an app. Do we want it or not (I want) we will see new Rails apps being created every day. The mainstream future is still before Rails, because it's really easy to start and new people will keep coming in.

Why do you complain about Rails?


It's not complaining about Rails, it's complaining about the current "best practices" and how we (me included) use Rails. In every project I worked on, choosing Rails as the platform was a good decision at the beginning. It's just that at some point we not always realized that we should find our way, not the Rails way to implement the requirements. We had a hammer, so we looked for nails everywhere. It doesn't mean that we should drop Rails. Projects evolve. What starts as a typical Rails app, evolves into something bigger, more complex at some point. Maybe it should be 5 small Rails apps cooperating together with some APIs?


Why don't you write your own framework?


Someone asked me this question recently and I realized that my Rails critics is presented in a way that put Rails in a bad light. It's not true. Rails is awesome. It has almost everything right. I don't want to create a new framework. It's too much work and there's no point in it. What I want, though, is looking for ways how to improve the apps that were misusing Rails at some point. In our company we sometimes inherit projects from other teams and that's my main area of research - how to maintain and refresh legacy Rails projects.
Another argument is that I haven't seen such an awesome community anywhere else. It's a community extremely open for new, good suggestions.


But you criticized ActiveRecord so often, what about that?


ActiveRecord is a great persistence layer. It was never my favorite part of Rails, because I never really liked the persistence part of projects. It's kind of an implementation detail to me.
Somehow, I misused Rails in this area. Instead of treating ActiveRecord as a persistence layer I started treating it as a domain layer as well. It was so tempting to do so...
Actually, at the beginning of a project it can be fine, to make AR you domain layer as well. Just for quick prototyping. But later I think that you should have a separate OOP domain model. I will come back to this point.


ActionController is the problem


There's a valid argument that Steve Klabnik and others talk about. The way Rails handles the communication between controllers and views by default is awful. I suppose you all know what I mean. Note that Rails doesn't force you to use it this way. You're free to make it better. My suggestion? Use cells and research the presenter-based gems for views.


ActionView is the problem


The problem is not ActionView itself, it's how some people misuse it. Rails community does a lot to discourage putting logic inside views and I think it goes well. We could go with Mustache or other logic-less templating systems, but it's assuming that we can't trust programmers. Welcome back to Java? BTW, stop hating Java ;)


MVC is the problem


First of all, Rails is not MVC. Second, the Rails MVC (Model2) is fine for monolithic apps that are (yay!) going to the past. The future is in viewless, API-based backends with rich clients like mobile apps and CoffeeScript apps.


Rails for bigger apps


The first thing to remember is that Rails is not your application. Rails helps you as a delivery mechanism. Once it's bigger, you should consider:

  • breaking from the monolith application into backend+frontend
  • implement a separate (from AR) domain layer

It all assumes that you have tests for your project. If not, then you're screwed anyway. There's no chance for refactoring if you don't have tests.


Future?


I see the future in separating backends from frontends. This alone makes the architectures simpler and easier to maintain.
Extract your domain layer. It's something that I'm experimenting with. It's hard, but it's not about Rails making it hard. Surprisingly it's hard because of the way we deploy Rails apps.

Domain layer


The point in separating the domain layer is that we want to implement some classes that have nothing to do with ActiveRecord classes. Yes, they may be similar. Maybe some names will be duplicated, but that's another layer. It should be framework-free, pure objects. PORO.

The deployment way leads to the question - What's the point of building an object model in memory, just to run one use-case and it all disappears? It's a waste. That's what happens when you build a domain layer based on database (ActiveRecord) data in a typical mongrel/passenger/heroku environment.

I'm experimenting with running Rails in a threaded environment (Thin allows it). It means that Rails is run in a single process and all requests come as threads. It's promising and very liberating - you can have your own Ruby objects, without being restricted by the ActiveRecord problems. I will blog more about it, soon.

There are problems, though. This approach requires a different scaling mechanism (but it also move the scaling need further to the future by speeding things up - your data is now in memory). Another problem is that you need to synchronize your domain layer with the ActiveRecord layer.

Despite the problems, I think that this approach has some potential. Contact me, if you would like to help me with this research.

Avdi also has some interesting ideas about the ActiveRecord separation in his book - "Objects on Rails".

Use cases

The synchronization part can be done using the use-cases layer. Read about it here: http://blog.firsthand.ca/2011/12/your-rails-application-is-missing.html

The use-case object can synchronize the two parts together, but you need to remember about keeping it consistent (either by transactioning it or by having a synchronizg thread).

Use cases alone are an awesome idea. Imagine that all the changes to your system that are triggered by a user or another actor come through a use-case object. This way, you're nicely exposing all the actors playing together and you can expose your real architecture.

ActiveRecord is changing in Rails 4


Look at this commit: https://github.com/rails/rails/commit/00318e9bdfc346a57cab34b2ec3724f3e9605ac1
It seems to be a small change, but it will allow extending your objects with the persistence responsibility runtime with DCI. Wouldn't that be cool?

DCI


You knew that I would mention DCI, didn't you? :) Yeah, I'm a big fan of DCI, just for the use-cases alone. They call it contexts, but it's the same meaning. The idea of DCI spreads nicely in the Rails world, but what worries me it's only the idea of extending objects runtime that is being spread. Runtime extending can easily bring you to a mess if you're not controlling who can extend any object. It's the context. If you're extending model objects inside other model objects, then you're doing it wrong, I'm sorry. Lessons learnt, trust me.

Read more about DCI on my blog posts:
http://andrzejonsoftware.blogspot.com/2011/12/dci-for-dummies.html
http://andrzejonsoftware.blogspot.com/2011/02/dci-and-rails.html

You should also read "Lean architecture" book which is a deep introduction to the DCI way of thinking.

Summary

  • Rails is cool, if combined with classical OOP. 
  • Remember about TDD and testing. 
  • Separate backend from a frontend. 
  • ActiveRecord is not your domain layer. 
  • Learn DCI. 
  • Think in use cases. 
  • Attend the wroc_love.rb conference in Wrocław, Poland. There will be many Rails OOP related talks. 
  • Send us your ideas for talks (you have my vote if it's Rails OOP related :) ) - I'm one of the organizers.

Bonus


Uncle Bob talk from RubyMidwest
http://confreaks.net/videos/759-rubymidwest2011-keynote-architecture-the-lost-years


It's one of the most important talks I have seen recently. You must watch it. It will change the way you think about Rails apps.

Thanks!


 If you read this far you should Follow andrzejkrzywda on Twitter and subscribe to my RSS.

9 comments:

Luiz Costa said...

Great Post.
Currently, I'm working on some more complex projects with rails.
In these projects I am using a technique very similar to this.
My domain model is completly separated from active record. I dont start from migrations.
I am starting to create my domain model with simple ruby objects (POROs) and using a Test Driven approach this
domain model emerges from use-case needs. This is a very efficient way to work for me.
The main problem with this way, as you said, is to mantain the AR layer and Domain Layer sync.
I have strongly used the advices from Eric Evans in his classic book 'Domain Driven Design".
My domain model is broken in contexts (Bounded Countexts) and this contexts touches with persistence layer using a repository abstraction.
It'i'm not sure if this is the best way, but it has worked for me in the last two projects, allowing the reuse part of a domain model between this projects.
I am very interested in this topic, and I would like to hear more from you about this"

Unknown said...

Andrzej, thank you for your post. Good software architecture is an art and a skill that takes time to learn and master without regards to the platform. Rails is certainly a great platform to work with. It is still up to the developers to make the right choices for their applications.

I agree that the future is going back to client-server patterns with clean separation between presentation applications and the backend application.

Its a lot easier to do in theory though than in practice. Especially when working on paid projects for clients who do not have a long term roadmap beyond launching their website or their app.

Again, great post :-)

lukemh said...

Just wanted to thank you for the link to the Uncle Bob talk from RubyMidwest . Was an awesome eye opener. I always thought there was a better way above MVC.

Could you suggest how you would name your rails controllers when interacting with various 'Interactors'?

I had an interactor called CreatesOrder and another that is called AddItemToOrder would you name your controller cart#add ?

Thanks,

Luke Holder
@lukeholder

Pavel Mitin said...

Hello Andrzej,

thank you for the post.

The most interesting part for me - the lessons learned during your DCI-based rails project. You express a warning against "extending model objects inside other model objects". But the "Lean architecture" book describes hybrid Context-Domain objects and recommends to use them. Could you comment on these contradictions.

Andrzej Krzywda said...

@Luiz

Great to hear that you also separate AR from the domain model!

I need to refresh my knowledge about DDD. Did Evans suggest anything about synchronizing the object model with the persistence layer?

I'm going to write more about my research, thanks for your comment - it's motivating me!

@Frank

Good to see that the backend/fronted is now getting obvious to many people. That alone will improve our architectures.

As for clients, yeah, I know how it is :) However, our customers are smart people, it's just an art of explaining them which way works best in a short run and which is serving us better in the future.

The nice thing is that a nice architecture is not a binary value. You can get closer it with every little refactoring step.

@lukemh

Uncle Bob rules :)

There is a problem with terminology, as always.

Interactors, services, contexts (DCI), habits (DCI), use-cases - they are all similar things.

I call them use-cases, however often they mean habits. I still have Rails controllers, but they just delegate most of work to the use-case object, like AddToCartUseCase (it should be habit, really).

Resources are at different level. Layer above. Delivery mechanism. cart#add sounds good to me, as is #cart_items/create

@Pavel

It's a mixture of different problems.
1. Using DCI at the level of ActiveRecord classes is wrong. We felt into this trap. DCI makes more sense at the OOP level. That's why I'm focusing so much about separating the two. One problem we've had was that, even though we extended some objects in "contexts", the same objects retrieved from AR again (without Identity Map) didn't have the roles. Thus the need for extending them again, deep in the models.


2. Extending should be at the context level. If you have domain/context objects (any example?) then it sounds good to extend it there.

3. DCI without contexts/use-cases is useless. We were not disciplined enough to have contexts everywhere and problems appeared quickly.

4. If not all of your team is truly convinced to DCI, then don't start with that. Even one person in a team who doesn't get DCI can make it very hard by using it in a wrong way. As with every "new" thing, it requires more education.

Unknown said...

Hi @Andrzej

Evans doesn't talk about synchronizing layers in his book. In DDD style, persistence layer is an implementation detail.
From Evan's book, i have used Bounded Contexs for maintain the domain model complexity controlled.

When you design your domain model, you define all attributes for these classes or use the AR for this?
I am defining all atributes in my domain model, but it seems that have a duplication here. Despite, my domain model have different classes from my AR model.

Luiz Costa said...

sorry, forgot to identify myself in the last post.
I am Luiz.

MattB said...

"the future is going back to client-server"

@Frank - Wait, what? I thought the future was going back to thin-client?! :-)

Kris Leech said...

Do you have an examples of Rails projects which use these techniques?

I think one of the main issues with adoption of these ideas is currently there are no real examples for people to look at. It would be nice to see PORO and DCI examples in the context of a Rails app.