Hexagonal Architecture Guidelines For Rails
theaudaciouscodeexperiment.com
theaudaciouscodeexperiment.com
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.
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."
I agree with the article that rails controllers should not contain business logic. I disagree that writing another ORM layer is a good idea.
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 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.
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.
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.
def index
respond_with PersonListDecorator.wrap(Person.all)
endEven adding a presenter, as I prefer, serializing your data should be handled by your service layer.
I'm not advocating writing an ORM, just some simple data translations from the raw database row data to a persistence-free domain object.
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.
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.
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.)
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.
https://news.ycombinator.com/item?id=7335211
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.
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.
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.
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? :)
ARE YOU GETTING ENOUGH OXYGEN, architecture astronauts?
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.
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.
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.
When I first read this post, I thought the guy was trolling, seriously.
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?
Actually, now that I think about it, you're almost much better off self-teaching once you get to a certain competency level.
Wait while I make this edit ....
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.
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 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.)
Ben Lane, the founder of The Audacious Art Experiment was one of my closest friends growing up, we were also in a band together.
Well, instead of fighting rails, you could just use the lower level stuff it is based on which does the bits you want:
Routing - https://github.com/rack/rack
Templates - http://www.ruby-doc.org/stdlib-2.1.1/libdoc/erb/rdoc/ERB.htm...
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?
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.