25 comments

[ 1039 ms ] story [ 1172 ms ] thread
In my experience, projects written in frameworks like Rails an Django have a tendency to end up with business logic sprinkled everywhere:

    Persistence + Business <-> Frontend + Business
You end up with business logic in ORM code, POST validation, Javascript form validation...

You usually want:

    Persistence <-> Business <-> Frontend
That way you can extract a business API and be transparent whether it's making SQL queries, talking to a webservice, a file, or something else.

Another mistake IMHO is going crazy on OO and having, e.g., an `Order` object encapsulate everything like validation, persistence, reporting, serialization, etc... You end up with an object that retains so much state that it's hard to test. By coupling data and function you also end up in a corner trying to do bulk operations efficiently, falling into an awful idiom of iterating objects and calling a method on them, leading scalability issues with N*M queries, and so on. The testable/scalable route is following a functional approach and designing your business API from the start around functions that deal with sets and take all the necessary state as a parameter, or data structure that represents the current state.

It's a general problem with poor programming practices. When you see people subclassing controllers to modify behaviour, it's clear something went wrong. On the other hand, splitting out your business logic (which should take clean data structures) and putting your persistence system in its own layer, you get a clean, decoupled system, which you may or may not reuse elsewhere, but that is certainly easier to understand and test than when "good design" means accessing the database in a controller or in a view.

Framework or no framework, you'll get the same design from programmers who stare blankly when somebody says "separation of concerns".

In my experience, projects written without frameworks like rails or Django have a tendency be worse than those that used a framework.
Half? More like 1/5th or even less. Ruby is the real horse power.
I know your point is that Ruby is where it's at, but do this:

In the top line of config/boot.rb above EVERYTHING else, put this:

    require 'tracer'
    Tracer.on
Now, start Rails. Wow that's a lot, huh? Stop again. Now try replacing those two lines with:

    set_trace_func proc { |event, file, line, id, binding, classname|
      printf "%8s %s:%-2d %10s %8s\n", event, file, line, id, classname
    }
Start it again. Lot going on there...
Are you implying I even use that pile of turd?
I find it really interesting and awesome that Rails and PHP are both (at the same time) in the process of (re)discovering the architecture described in Evan's DDD and Fowler's PEAA.

What's old is new again! Rails has existed long enough to have a need (and a vocal chunk of the php community has matured enough) to think of apps architecture with a longer term in mind.

I think devs are seeing frameworks as a tool to implement an application instead of being the application itself (as originilly conceived before Rails revolutionized RAD).

Great!

> I find it really interesting and awesome that Rails and PHP are both (at the same time) in the process of (re)discovering the architecture described in Evan's DDD and Fowler's PEAA.

uh? It has nothing to do with PHP or Rails. everything to do with mediocre developers,you must be born yesterday. And you can even do that in JS...

It's been over a year since Obvious Architecture came out and over a year before that when Uncle Bob gave his talk on Ruby Midwest. People seem to just now be catching on.

I've gone really deep down the rabbit hole on these kind of highly decoupled design patterns in Obvious, clean architecture, functional programming, TDD and all of that and I realize now that Ruby is not the optimal language for this style of development and DHH and Rails' purpose is not the same purpose of clean architecture.

I go back and think about the original build a blog in Rails demos, which are great BTW, and I feel like Rails' strength is standing up an application quick and getting stuff done. Yes it can do TDD and clean architecture and all of that fun design stuff, but to be completely honest, it doesn't fit the community or the language as much as it should.

Ruby gives you a lot of amazing tools, but there are a lot of things that make clean architecture way nicer that you end up bolting on to Ruby. Stuff like immutability, type checking, and strong interfaces are not the norm in Rubyland. That is perfectly fine, but by the time you build all those tools, it's hard not to look at a language like Scala or Kotlin and see a lot of those things built in to the language along with a compiler and nice IDE support.

I love Ruby and I use it every day. I live in a Rails app most of the day and I've found ways to make to make the code beautiful, but fundamentally I don't think the Ruby community will ever truly get behind clean architecture. It really flies in the face of the reasons why most people use Ruby in the first place.

I think what you say about Rails is true. Rails isn't a framework, it's a lifestyle.

I also started to question whether Ruby's type system was just too loose for serious work and was yearning for something as pleasant as Ruby but with at least some type safety. Then yesterday I read this relatively old post https://www.ruby-forum.com/topic/217617 about adding static typing to Ruby. There's a lot of recognisable names in the thread, but the posts from Eleanor McHugh nearer the bottom really helped me understand the power and utility behind the apparent simplicity.

I would love a compiled, statically typed Ruby, but I don't think the Ruby world wants it. I think Ruby is a fine tool in its current state.

I think there is a need for a different language that makes clean architecture natural, well accepted, normal thing. Ruby is great, but trying to change the community and language doesn't seem to be the right approach.

I've thought the same myself but reading that post made it clear to me that such thing couldn't really have much in common with what we call Ruby today.

The solution for the performant/distributed/concurrent/data-intensive problems of today and the future seems, to me, to be in the functional space. I know it's trendy to say that but it's the smart people - Rich Hickey, Simon Peyton Jones, etc - who are driving this evolution. A language with a GIL and zero immutable primitives just doesn't seem to have much to contribute. The idea that Twitter was at scale as a Rails app once upon a time is just absurd to me now.

Incidentally I creeped on your bio and saw you created obvious. I read about Obvious a while back and, although I must admit I haven't used it directly, it really had a strong influence on how I think about structuring Rails applications.

What I would really want from a language wouldn't have much in common with Ruby. It would look like ruby, but be mostly functional, compiled, with great support for immutable data structures, and named parameters. The closest I've found to this is Scala or Kotlin. I haven't had much time to dig deep into either though.

I'm glad Obvious influenced you, but I'm not surprised you didn't use it directly. To be completely honest a lot of what I built in the obvious gem, other languages give you just as language feature. For example comparing similar entity and contract structures in Ruby/Obvious, Mirah, and Scala: https://gist.github.com/brianknapp/6178930 I made Obvious work in Ruby because Ruby provided a great platform for figuring out how to make this work well. However, most Ruby devs either don't get it or don't like it because it's not very Ruby or very Rails.

The best Rails code I've ever written is basically using ActiveRecord to pull in a big data tree from the database and then using that to populate and do calculations on a bunch of immutable data structures using Hamsterdam::Struct. The code is really clean and beautiful and very easy to test. The final computed data structure is then used to populate an immutable presenter that helps generate the view. It is very much in the vein of a functional core and mutable shell concept that Gary Bernhardt advocates. My Obvious apps tend to follow a similar pattern, tho I haven't updated the Obvious site to mention functional core and mutable shell, but the code's kind of always been that way.

You can write great code in Ruby and Rails, but to me it's always going to feel more like a prototyping tool than something that will feel great to maintain for long periods of time.

Interesting you should choose Kotlin over Go. What's your take on it? I'm a big Jetbrains fanboy but haven't played with Kotlin yet.
I've played with Go and I like it well enough, but it doesn't offer anything interesting in the way of immutability. In fact, it doesn't so much offer it at all from what I've seen. Also, I'd like whatever language I land in to support named parameters.
> I would love a compiled, statically typed Ruby

http://infraruby.com/ is a compiled, statically typed subset of Ruby, compatible with Ruby interpreters.

Is this actually type safe, or type safe up until you include an untyped gem and the whole thing becomes indeterminate? Does the typing actually improve performance over straight jRuby?

Can it handle method_missing and *_eval?

Also why is it closed source?

edit: sorry I didn't mean to sound aggressive. please interpret the stream of questions as extreme curiosity.

> Is this actually type safe

The type system is roughly the same as in Java, and, as in Java, there are ways around the type system.

> or type safe up until you include an untyped gem and the whole thing becomes indeterminate?

The compiler requires type annotations; you can't include an untyped gem! InfraRuby syntax is compatible with Ruby interpreters, so you can still use your code while you write type annotations for it.

> Does the typing actually improve performance over straight jRuby?

Yes. The compiler is written in Ruby, and uses JRuby to bootstrap. The performance of the compiled compiler compiling the compiler is about 7x JRuby.

> Can it handle method_missing and *_eval?

No. The subset of Ruby supported by InfraRuby excludes metaprogramming and reflection.

> Also why is it closed source?

Because 1. give away your work 2. ??? 3. profit! is not compelling :-p

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
One of the great things about Ruby at the moment is the number of Ruby implementations around, including several Ruby like languages which attempt to do things slightly differently.

Crystal is one language with Ruby like syntax and the goal of compiling to efficient native code. ( http://crystal-lang.org )

Mirah is another statically typed Ruby like JVM based language, although its development is a bit slow. (http://www.mirah.org)

I've done Scala in the past and I think I'd be able to handle it better now. Honestly though, there is such a small footprint of the language that I would even care to use that I'm not sure it's worth learning. I'd probably stick to the immutable value objects, functional constructs like ruby's equivalent to map, select, each, etc., and use traits to enforce contracts on jack/plug objects. A lot of the other things in Scala don't appeal to me. This is one reason Kotlin is looking more interesting from where I sit. It has the features I want, IDE support, and a decent syntax.

It is entirely possible that I'll check out Scala again and fall in love with the language. Who knows.

Not sure I agree with the idea that `Rails == UI`. A lot of the time I'm trying to fight Rails on this as decoupling HTML means refactoring HTML which isn't fun.

I use Rails as a thing that controls boundaries, a glorified CGI really. It's a router, input sanitizer, and general plumbing that spurts out JSON every now and then. It's a mid-level wall in a fortress: server + OS deal with TCP, Rails + Rack handle HTTP, Rails handles raw user input, proxy objects handle Rails, the actual business logic handles proxy objects.

This is the only workflow that allows me and Rails to be long-term friends:

When a request comes in to the API (say, change password) the controller will sanitize and confirm presence of the right params and collect things like the current user. It'll then pass these off to a proxy object e.g. `CustomerPasswordUpdater.update!(user, new_pass)` which will validate length, entropy, etc and perform any necessary crypto. Then that data gets passed off to another proxy object which abstracts over ActiveRecord e.g. `UserPersister`. There's literally nothing inside the model itself. The core `Customer` object is nothing but a decorated window on the relevant data (accessed through unambiguous proxies e.g. `UserReader`, never directly through the model).

This may seem a bit framework-inside-a-framework but the result of this approach is that specific chunks of logic - your logic, the stuff you write on a whiteboard - is clearly identifiable in these small unambiguous classes.

The grand upshot is that I can plan my application on a piece of paper, write all my logic in plain old Ruby with plain old RSpec and plain old gems at breakneck speed, and there's no stupidity like tests hitting the DB or 40 minute test runs in sight.

Once all that's done wiring it up to Rails takes a day or two. At any point I could write a few new proxy objects and drop the whole thing in to Sinatra&Sequel/ Sinatra&Neo4j/ Sinatra&PStore/ Whatever&Whatever. At no point does the logic that defines and encapsulates the actual business problem that needed solved need to be touched.

Don't restrict yourself to MVC. It's a phenomenal start to any given project but ultimately your going to have to do what real engineers do. Example, lets say I'm building a game. The underlying framework is object oriented and adheres to a type of MVC pattern to manage view states. We use a component entity system to manage game entities and logic. The same goes with Web stacks it's the framework, but not limited to the underlying pattern. Extend your domain logic out from the MVC. It's not rails job to do everything for you. I feel the developer is at fault more than rails. Buck up and break the pattern it's not beyond reason.