At that point, I wonder what the benefit of using rails is? Rails shines most when you do things as intended.
Even though I wouldn't have designed rails the way it is (remote forms and .js.erb views anyone?), it works really well for a large class of applications.
Of course you may eventually hit a wall, but that happens when software outgrows it's original requirements. That doesn't mean you have to build a system for millions of concurrent users from day one. Especially not, when you're making accounting software or a crm or some other run-off-the-mill webapp.
In my experience, rails "improvements" are often suggested, when a developer is faced with a large mudball of a rails application that doesn't do things according to convention. This can quickly happen, because rails is deceptively easy to learn. Many developers I know (myself included), started writing a serious app in Rails, without learning a lot about it. Heck, if you're a rails developer and honest with yourself, you probably didn't even read all of the rails guides. In addition to that, I recommend reading a book such as "The Rails Way" front to cover, joining the mailing list, following the core team's blogs and potentially even going through the code. I also DON'T recommend taking your patterns from random blog posts or stack overflow. Rails is very well documented and you can generally find what you need in the docs.
As infuriatingly dismissive as his tone sometimes is, I've actually found it best to follow dhh's advice. Not only because I generally agree with it, but also because it's likely to be the best supported design in future rails versions.
There are a few ways to answer the (definitely relevant) question of "if you're doing things this way, what does rails give you?" In some cases, the answer is "nothing", and people really just want sinatra if they really like ruby, or one of the many lighter-weight libraries that other languages have. In some cases, the answer is "actionpack is still really good at what it does, even if it's just used as an HTTP adapter". In some cases, the answer is "activerecord is a really nice ORM". Etc. But I think in most cases, it's because there are just lots of libraries written for rails, and using sinatra or padrino (last I looked) tends to result in re-writing some things that you would otherwise get for free. Although sometimes in order to use those libraries you need to be doing things "the rails way" anyway.
This feels like it would be the sort of architectural decision which would be well-served by saying "Here is the app motivating this stunning architectural purity. You will note that this app both exists and was more complicated than the 5 Minute Build-A-Blog demo."
Otherwise it's "Rails Project Day 1: Scratch-build a stripped-down ORM. Day 59: That might have been a bit more involved than I expected."
Controllers actions are allowed a single line of code
I wonder, why do these controllers exist at all?
def show
app_of_things.show_thing(rails_adapter)
end
Is code like this really useful code? It looks like it was written by a very short perl script. Why is he using rails at all at this point, rather than ditching the framework and using a simpler router which talks directly to his app endpoints? What does this controller add aside from a level of indirection which will never be useful?
The lack of an example with code running in production makes it very hard to judge where all this is leading or why you would want to do this.
Well, to separate concerns. ActionController does what it's good at (routing and rendering), and your application does what it's good at (business logic). One tangible benefit of separating concerns in this way is that it makes it possible to use the application itself as a module behind different adapters. So you can put it behind a raw tcp api, or a 0mq bus, or a command line front-end, etc., without needing to change anything but the adapters.
It is very reasonable to question whether this is really all that useful or whether doing it from the start is a premature optimization, but it isn't pointless.
Well, it's interesting. My example is partly contrived and partly not contrived. I worked on a big application where the web front end was very much the tip of the iceberg. We had the Rails monolith problems that you frequently hear decried. One thing we at least thought we would really like to do was move to an architecture with a bunch of little services talking to each other. We thought about having them talk to each other over tcp or 0mq, and we thought it would have been really nice if it were easy to take our existing application logic and put tcp or 0mq adapters on top of it. I keep saying "we thought", because we never actually did any of that, which may have been the case either because our application logic was so tied up with Rails that it was impractical to pull it apart (as some of us thought), or because it's a silly idea to begin with.
We did have a good deal of success pulling out libraries though, and separating concerns helps with that too. (So maybe I chose the wrong argument to make in my comment before.)
I've worked on two large applications that would both benefit from Clean Architecture and they don't feel like outliers, they feel like the natural progression from any MVC framework to an app of significant complexity.
The first was a very large PHP app that ran a fundraising website that processed millions of dollars in donations. It grew organically over time and there was need to change database structure or swap out whole modules of the infrastructure like the site search. It was mostly untestable and really could benefit some automated testing.
Over time the app was moved to a Ruby web API with Sinatra with a decent Service layer with great test coverage. It's been a massive improvement over what was there originally and moved towards more Clean Architecture principles over time. It never went full Clean Architecture because getting team buy-in was difficult and until you feel the pain of a complex, monolithic codebase with minimal tests and a few botched late night deploys, it is hard to feel the pain that Clean Architecture solves.
The second app I've seen that would benefit from Clean Architecture is a Rails app that is pretty vanilla Rails. Normal MVC and all that. Lots of ORM and so on. The tests ran slow and over time the required reporting complexity made the database queries slow and terrible. We've pulled things into a service layer and presenters which helps a bit, but doesn't solve everything. Now we are moving the app reporting into a very functional, immutable approach where we calculate and cache everything all at once in the background. We are only using the models for basic CRUD and for easily pulling out our data structure from the DB, which is then handed to a bunch of functions which calculate everything. It's very much a functional core, mutable shell approach. Even still, this isn't a fully Clean Architecture approach, but it's a lot closer, and it solves a lot of the problems that default Rails MVC doesn't.
Clean Architecture is probably best thought of as something people move towards over time as they feel the pain of not having it. Once you feel that pain, it is completely reasonable to want to start your projects clean and keep them that way, but good luck convincing other developers to join you if they haven't felt the pain.
Most developers using this type of architecture are working on projects for clients that wouldn't appreciate having their code slapped up on pingpongwithdhh.com
I agree with the article that rails controllers should not contain business logic. I disagree that writing another ORM layer is a good idea.
There's a difference between "controllers should not contain business logic" and "controllers must be one line". There's plenty of sane responsibilities for controllers that don't directly involve business logic - mapping request parameters to something the business layer understands, coordinating view mapping, applying decorators to models, authentication, authorization, etc.
By rigidly applying an inane rule like "you can only have one line per controller action", you're going to end up just re-implementing controllers, and leaving your controllers doing nothing but pointless indirection.
I agree with this. Statements like "you can only have one line per controller action" are just dogma. What's the point of a controller?
I also don't like ceding control-flow from the controller into the application by injecting the controller context in the form of "rails_adapter", in the name of "Tell, don't ask". It seems like a misunderstanding of the principle.
"As the caller, you should not be making decisions based on the state of the called object that result in you then changing the state of the object. The logic you are implementing is probably the called object’s responsibility, not yours."[1]
Say you have a module that performs some complex business logic: ShipOrder.perform(args). It's responsibility might be to ensure the right items are combined into a package and shipped. It might write some logging data to an injected logging object. It might tell a label-printing to output the right address label. Ultimately, it should succeed or fail, and pass that information back to the caller.
The controller's responsibility, on the other hand, is to direct the control-flow of the request and pass data between models & views (and your other non-model POROs). That's its core responsibility. Passing the controller context into your application object so the application can call-back methods on the controller just seems like a recipe for spaghetti code.
It also means you can't use this object on its own, or composed with other objects, without implementing a complex caller-object that you can pass in to receive the callbacks. If the object just returns success/failure, and exposes a limited API so you can access its internal state if required, it becomes straightforward to re-use & compose the object in places outside the confines of your controller.
Yeah I agree. I'd be interested to see what that `rails_adapter` implementation looks like. In my experience, this sort of design often just moves a lot of the tricky questions about how to pass data around to the adapter instead of the controller.
Doing rendering based on the application telling the adapter what happened does seem to work pretty well though.
I have to disagree, by returning a value and switching on it, not only are you querying state but you risk repeating the query code all over the place.
My approach allows you to write a small adapter for your delivery mechanism and use polymorphism to eliminate conditions and duplication.
Most objects cannot be used on their own as they have collaborators. My design allows me to write a trivial console adapter that can puts the results of my service object / interactor.
I agree, but I see "one line per controller action" as a guideline that doesn't need to be followed religiously. Almost every rails app that I come across has too much domain logic in the controllers. I'm refactoring an ecommerce app where the developers put 400 lines of payment processing code inside one controller.
Things like authorization should be mostly contained in the root controller and not sprinkled around your controller actions.
What kind of decorators are appropriate to apply to models from a controller action? I'm sure there are valid scenarios, but most of that should also go somewhere else.
If you had a view decorator that applied presentational logic to a model, I don't see a problem at all in including that in a controller. I wouldn't have any issue at all with a controller action that looked like:
def index
respond_with PersonListDecorator.wrap(Person.all)
end
So I'm not actually advocating writing an ORM just a data mapper. You map from one hash of data extracted form data base to something like a Struct. There's no SQL generation involved.
tl;dr show some actual working implementation of this paradigm that exemplifies its benefits over standard Rails
> Anyway, the invitation stands. Present any piece of real code and we can ping pong on it. I enjoy talking about specifics and improving real code. I detest "oh that was just a poor example, but the general principle is..." kind of debates. If you can't produce a good example, you don't have a general principle.
This is a really lazy complaint that gets reiterated when this approach is mentioned. There's no elaboration here to provide substance to the argument.
When I think stereotypical enterprise java complaints, I think people joking about AbstractFactorySingetonProxyBeanAdapterFacadeConcrete. Not this. Whether you agree with the approach or not, it's fair to say that the author is simply drawing some boundaries around his/her business logic to make it framework agnostic, decoupled, and test-speed friendly.
Oh, no I have no problem with enterprise Java - it was merely an observation. Enterprise Java exists because it works, after all. I just find it interesting that most software projects will eventually come to the same conclusions regardless of their original source.
Actual hexagonal design is useful software architecture. It may just be extremely poor presentation, but I am far from convinced that the dictates in the article, or the very little bit of very abstract sample code it provides, reflect useful architecture (Hexagonal or otherwise) -- certainly, its not a presentation that fosters understanding and intelligent application over cargo-culting.
The hallmark of enterprise java is the incidental complexity needed to implement a given abstract architecture due mostly to the strict limitations of the core language. Ruby's more powerful system allows you to implement architectures with an order of magnitude less ceremony (and some would argue safety) and without as much careful planning required to make sure every possible hook is in place up front. In the end, "enterprise ruby" architectures bear so little resemblance to enterprise java that the comparison is nonsensical.
absolutely absolutely this. Patterns don't suck. Implementing patterns in Java sucks. Ruby's got a lot of fantastic things going for it, and duck typing means you don't have all the ceremony of defining and implementing interfaces.
There is a lot that Java EE got wrong, but there is a lot in there that is probably more good than bad. The amount of ceremony and effort (and XML) required to get a basic Java EE thing is not great. But, when I used Play Framework 1.x with Java, it was quite enjoyable. I haven't used it much since, but I could see something like Play solving most of the annoyances of Java EE, while keeping some of the good parts of Java or other JVM languages.
well, JEE works but it's not pragmatic. Rails brought pragmatism and good developper experience to all other web plateforms. And Ruby is still a very pleasant language to write.
At the end of the day your product is your app running or not,it doesnt matter own much design pattern you implemented in your code.That's pragmatism.
Considering he says people should write their own data mapper, it doesn't surprise me he is so far behind everyone else he can't even turn out a sample. You can spend many years writing that alone.
Unfortunately they have all been closed source thus far.
The problem with writing good code samples is that it requires a lot of time and thought. This was very much an MVP blog post and appears demand for the sample app / code snippets is very real so I will do my best to get something together.
Isn't more fun to spend all your time arguing on HN though? :)
I'm assuming that the apps that have already been developed with this architecture have been closed source, and he's just waiting to develop an open source demo app, not that he's unproductive as a developer.
If you're operating at the level where you need this level of indirection, then you should be looking at SOA anyway. Rails is not an appropriate platform, regardless of how much architecture you add to it.
If you're not operating there, then you are introducing complexity for no reason, and failing to take advantage of the features that Rails offers.
Well Rails is a solid full-stack framework that is fairly modular, and of course ruby is extremely flexible in the architectural choices it allows, so it's very easy to take Rails in different directions by instituting your own conventions. It's probably easier to do this then to either pick a minimalist framework like Sinatra and build everything up from scratch, or find another language with a framework that suits your exact preferences.
I totally agree that Rails defaults hit a certain sweet spot. Putting a bit of logic in the controllers is fine in most apps, ActiveRecord is intended to mix persistence and business logic because many models aren't complex enough to merit separating them. Starting with a Hexagonal approach may well be premature optimization. I get it.
But lets be careful not to throw the baby out with the bath water. Rails intelligent defaults do have a tendency to leave a vacuum for apps reaching a certain level of complexity. That doesn't mean Rails isn't still useful, but just that it becomes inelegant when you don't have any conventions to deal with this and start hacking ad-hoc solutions into your codebase. I have a sneaking suspicion that partially this is inevitable in any living codebase, and that a complex system almost by definition can not be consistent and elegant since it is inevitably built over time under changing conditions. But in any case, I think Rails is a perfectly good place to experiment with ideas for managing complexity in a sizable app.
Having the indirection there allows you to take the Rails features you want with your boundary clearly defining which ones you depend on.
You could start with Rails, get ActiveRecord querying, migrations and template rendering for free and then maybe swap it out for Sinatra and the Sequel gem (I have actually done this).
Knowing your where your dependencies and technical debts are is very empowering.
It's a reference to Alistair Cockburn's "Ports and Adapters" pattern, also called "Hexagonal Architecture".[1] The only reason I ever heard of it is that it was mentioned in the book "Growing Object-Oriented Software, Guided by Tests", by Steve Freeman and Nat Pryce (a good book in my opinion).
The problem with this is that he's building an API on top of something he doesn't understand. There's no way you can look at this and think it's a good idea if you have any understanding of how rails works.
When I first read this post, I thought the guy was trolling, seriously.
Web devs hate him for suggesting that programming technique is important!
No, really, perhaps it is worth giving this a try, if only as a means of deliberate practice? You don't get better at programming by plugging in HN's coolest, most fashionable framework, you get better at it by shipping, maintaining, and learning to feel the impact of design decisions.
Don't complain you have too much to do, this is building foundational skills that are language agnostic and will outlive Ruby on Rails. Isn't that way more important than pulling up Twitter several times today?
Webby Rails types do often get very aggressive towards this kind of thinking. The point you made about this out-living Rails is a very good one. I'd be coding like this in any language with any framework, Ruby just makes writing SOLID code easy.
You can't rely on industry to teach you what you need to know. They'll put you on endless treadmills learning hackneyed DSLs fueled by hype cycles. Fact is, Rails is almost entirely composed of patterns from PoEAA, along with DHH's excellent taste. At some point, you need to read and apply those patterns yourself to really understand them.
Actually, now that I think about it, you're almost much better off self-teaching once you get to a certain competency level.
I'd reword data as in "create a clear boundary between your models and the ORM/ODM you use", which easily translates into defining class methods on the model which use the underlying ORM/ODM of choice.
For the record, with this kind of architecture, you can take the same codebase and use it for a web api, standard rails app, sinatra anything, desktop app, mobile app, or command line app. All using mysql, sqlite, postgres, mongodb, json files, or just about anything else you want for persistence. And your tests should all run in less than a second. It's pretty sweet from a purely technical standpoint. Obvious Status has done that with ruby, but I could see the same thing being done in Java, C#, or even C++.
I'm not sure why all the rails people are blogging about this kind of architecture seemingly all at once, but it's not new. It's pretty much where you end up when your standard MVC codebase gets out of hand on a reasonably complex project. Uncle Bob spoke over 2 years ago at Ruby Midwest about this and has blogged about it since then. Obvious came out last January and there were a few people talking about it since then, but not many.
The real struggle with this kind of structure is that it goes against the standard Rails patterns and ecosystem. DHH doesn't like it and frankly he's right. It goes against the whole spirit of Rails. It doesn't fit in with Rails. Sidenote, DHH originally called it the worst code he's ever seen attached to Rails, so it's got that going for it.
If you go down the path of clean architecture (in any of its forms), you are going to have to convince your team of Rails devs to stop using Rails as they know it. Most Ruby or Rails devs don't want or see the need for this kind of structure. That is okay. We had to build a lot of things to make building Obvious apps in Ruby more awesome, but it's an uphill battle. Other languages give you things like interfaces and method parameter type checking as part of the language and will even check them at compile time.
Clean Architecture is great, but I think it's going to lead developers away from Ruby and Rails, at least for the core API portion of their app. I really don't know what language most developers will land on, but it will probably have a compiler, it will probably support functional programming and immutability, it might support OOP, and it will probably be on the JVM. That's just my guess.
This, to be evaluated, desperately needs more realistic examples, as well as much better discussion of how the rules relate to the principles of Hexagonal architecture, and what benefit they provide in that context.
As it is, I can, with some effort, find some justification for some of the rules in the context of Hexagonal architecture, but as presented its not immediately clear even what part of Hexagonal architecture the various elements are relating to.
Perhaps most importantly, it needs to clarify what the application itself and the "rails adapter" look like here.
I feel much of the negative reaction to posts like these fails to grasp the main idea. My take is that you can use other architectural patterns than MVC and still benefit from Rails. In fact, you can use patterns that complement MVC. There is no conflict. Why ignore the history of design patterns and try to squeeze everything into an MVC-shaped box?
> I feel much of the negative reaction to posts like these fails to grasp the main idea.
I feel much of my negative reaction to this post and those like it -- and I am familiar with hexagon/ports-and-adapters architecture -- is failure to clearly present a main idea, or even to connect the dictates in the post to the subject in the headline.
> My take is that you can use other architectural patterns than MVC and still benefit from Rails.
Certainly, the article seems to be trying to use Rails MVC structure to force the design of the web-facing adapter in a hexagonal architecture into an MVC design, what it really fails to do is justify this as a good thing to do (either in general for building an application, or starting from the assumption that we should use a hexagonal architecture, or starting from the assumption that we should use Rails.)
Off topic, but how did you come up with the name for the blog? I ask because there's an awesome record label in my city called the audacious art experiment... wonder if they're related
Rails adds quite a lot of overhead and complexity, not to mention memory usage, so if you're going to bypass most of it, it might be better to start with something more bare-bones?
I agree with that, bringing rails in is like bringing a chainsaw to cut a twig sometimes...
The point here is not to look at the devise/router serving http but the porting of the data & business logic to an adaptable, multi-purposed api that is not tied down to the infrastructure in any way.
I've used this approach within a Sinatra driven app, CTO said i need to use Rails now for some other thing, a few hours to make the switch, not rewrite the whole thing.
Even though I wouldn't have designed rails the way it is (remote forms and .js.erb views anyone?), it works really well for a large class of applications.
Of course you may eventually hit a wall, but that happens when software outgrows it's original requirements. That doesn't mean you have to build a system for millions of concurrent users from day one. Especially not, when you're making accounting software or a crm or some other run-off-the-mill webapp.
In my experience, rails "improvements" are often suggested, when a developer is faced with a large mudball of a rails application that doesn't do things according to convention. This can quickly happen, because rails is deceptively easy to learn. Many developers I know (myself included), started writing a serious app in Rails, without learning a lot about it. Heck, if you're a rails developer and honest with yourself, you probably didn't even read all of the rails guides. In addition to that, I recommend reading a book such as "The Rails Way" front to cover, joining the mailing list, following the core team's blogs and potentially even going through the code. I also DON'T recommend taking your patterns from random blog posts or stack overflow. Rails is very well documented and you can generally find what you need in the docs.
As infuriatingly dismissive as his tone sometimes is, I've actually found it best to follow dhh's advice. Not only because I generally agree with it, but also because it's likely to be the best supported design in future rails versions.