8 ms·
On logic in a Rails app
- stiff 14y agoThis seems to be a new trendy design pattern: wrap a single method with a class and claim you just discovered a pinnacle of object-oriented design since you respect the "Single Responsibility Principle"...
- alisnic 14y ago> claim you just discovered a pinnacle of object-oriented design since you respect the "Single Responsibility Principle"... I never said that, why all the hate? I stated pretty clear that this is my approach on things, and I never pretended it to be the "right" one.
- mattgreenrocks 14y agoDon't sweat it! People tend to adopt different subsets of OOP and then proclaim all other subsets are wrong. Example: large-scale objects that are heavily procedural vs lots of smaller grained objects that adhere to SRP. I've written a lot of code in both styles, and determined that SRP is a good force on my designs.
- bengotow 14y agoI think what the OP is talking about is just a ruby reimagination of the Operation pattern. Most sufficiently complicated apps that implement undo / redo use "Single Responsibility Objects" to encapsulate individual user operations. Each one has a "do" method and an "undo" method, and little else. In a web service, you don't have to worry about undo server-side, so using this pattern is more an option than a necessity!
- LaGrange 14y agoBah! Poppycock! My domain objects are also logicless, and I keep all my enterprise stuff in PL/SQL. Also, TDD is for people who can't do formal methods.
- alisnic 14y agosounds fancy, can you elaborate?
- codewright 14y agoYes, for $250/hour Corp-to-corp billing.
- fruchtose 14y agoWow! Sounds expensive, must be worth it.
- michaelfeathers 14y agoThe funny thing about the argument that people have about where logic should go in an application (models, controllers, or separate objects) is that everyone assumes that there is one right answer for an application for all time. We should probably move toward guidance like: "this is when you move your logic here" and "when this happens, it's time to move it out of the controller." For what it's worth, I think it is okay to start doing things the naive way in Rails as long as you know when to refactor toward a different scheme. The bad thing is that it is usually earlier than you think.
- dlisboa 14y agoI don't think everyone assumes that at all. Most discussions on DCI have been about "in big applications, this would work better" instead of "you should do this every time". Some of the supposed benefits aren't even seen in smaller chunks of code. Discussions on modules and SRP have not been absolutist either. People have said things akin to your last paragraph: for simple projects there's nothing really wrong with the normal approach. The problem starts when it grows. The whole "movement" of Rails refactoring only began after people started having trouble moving large codebases around. The guidance thing would be interesting. A collection of situations when things start to go weary, code smells. The thing is you can't do "when this happens, do this" because the "do this" part is the many solutions people have proposed. However I think people have those code smells a bit down already and generally know what a controller should look like. The placement and structure of the domain logic is what is bugging.
- programminggeek 14y agoI think part of the problem is that if you start with the naive way, you get some bad habits and start looking at your code through the lens of ActiveRecord - fat models, persistence, concerns, etc. If your models were just plain ruby objects that validate your data for you, your code is able to do more interesting things - like multiple persistence mechanisms, multiple delivery mechanisms. Oh and they can be incredibly fast to test from day one. If you use a tool like Obvious (which I wrote), you can TDD from day one and still build things fast, while maintaining a good structure.
- calinet6 14y ago
- why-el 14y agoI can't help but see the irony in the fact that the Rails community is slowly moving back to app directories similar to those we found in Java's Spring and others. I guess people from the Enterprise world had a point.
- alisnic 14y agoThere was a quote in Ruby Rogues podcast which said that "Ruby people use factories a lot, they just don't call them like that"
- mattgreenrocks 14y agoNo language or framework can contain complexity. The best we can do is to prevent needless complexity from emerging.
- ryeguy 14y agoI have been saying this for so long. Now we just have to wait for the Ruby community to understand why people do Dependency Injection and we'll be getting somewhere.
- jpallen 14y agoWe use a very similar set up in a large Node.js app I'm working on. It works very well for the most part, but we're definitely still figuring out the best way to split things up. For example, say we want to work with a 'user' model, and we need to do basic CRUD stuff. One argument would say that we should have a module for each action: creating users (registering, hashing password, sending out welcome email, etc.), deleting users (cleaning up their data, removing them from the db), and for authentication, profile updates, and so on. The downside to this is that we end up with a dependence on the underlying data structure across lots of modules. If we want to change the property 'username' to 'login', or something more complex, we have a lot of modules to change. We also need to understand how a user is created before we can understand how they are updated or deleted, which means we need to have the CreateUser module in our head while we're working on UpdateUser or DeleteUser. Perhaps this problem is worse in our set up because we have only very thin 'models', which are basically just Mongoose wrappers around MongoDB, and so our UpdateUser models is still quite heavily coupled to our database. All things considered, I've found the schema described in this article is better than any alternatives that we've tried. Perhaps we're just floating at the equilibrium point where either more or less abstraction would make the code more complicated. Certainly we refactor as soon as we can when a single module gets too complex. As always, pragmatism rules.
- darrencauthon 14y agoIt all comes down to testing to me. If the dev is writing a test first, questions about where things go are answered pretty quickly. If the tests become painful or awkward, you're probably doing too much in the code. I think the common Rails response to painful tests is to stop testing. I've heard talk of the "diminishing returns" of testing, or debates of what deserves to be tested. I was once responsible for maintaining many Rails 3 apps that I did not write, and the story was all the same: Either there were no tests, or there were simple tests around simple logic, or there were simple tests covering one or two paths through some really complex logic. Once they felt the pain from their tests, they just stopped writing tests. What else are they going to do... challenge the "Rails Way?" And this is the community that is supposedly known for their testing. Having used Sinatra or Rails for almost all of my web work for a while, let me tell you: The state of testing in the common Rails app is no different than I've seen anywhere else.
- atomical 14y agoThe do it because it feels right (or don't do it because it feels wrong) mode of thinking is self-indulgent. When an app becomes larger and more involved so do the tests.
- amalag 14y agoThis is a very good point. If you consider from the point of view of what should be tested, then I think what the author proposes could be quite natural. We don't want to duplicate and overlap with Rails tests. For me "what to test" is the hardest question.
- alexborbely 14y agoMVC framework is just a delivery mechanism and your business logic should be outside of it. Your business logic should have no idea about web, console, controllers and routes, it should contain information only about your domain objects and rules. You should be able to extract your application logic and make it available on console, web or mobile just by changing the delivery mechanism. This is the original explanation from http://cleancoders.com http://cleancoders.com (Bob Martin's website) http://www.cleancoders.com/codecast/clean-code-episode-7/show http://www.cleancoders.com/codecast/clean-code-episode-7/sho...
- why-el 14y agoDo you know of a highly successful startup or company doing this? i.e. Same logic with different delivery mechanisms and no hassles whatsoever?
- virmundi 14y agoI tend to find this dream rather fevered. A controller knows about the model. It knows about the view. It acts as a conduit between itself, the view and model. In the web, the models are not really event based. Some platforms have tried this, but it tends to not work out well at scale. So ultimately the controller tends to get a lot of business logic embedded because it's difficult to put that logic somewhere else. The problem with this is that the controllers are very much view-centric. Take .NET for example. There are forms. Logically this is the controller. Events happen, handlers manipulate domain objects and create responses, the result goes to the view. The problem is that even .NET makes a divide between WinForms and WebForms. Entirely different namespaces. Thus your controller is view bound and blocked. Now you might want to push the logic from the controllers down into the domain objects. This is fine. But, as other talked about with testing, you will have to make sure that you're not doing too much with your domain objects. So you'll probably start to use interfaces and constructors for the domain objects to abstract away view details. You might even use callbacks (another type of interface). So you're feeling pretty good now. But eventually this type of configuration become heavy. It also might not mesh with your system. I'm not sure about Rails, but Hibernate makes is rather hard to IoC objects it makes. It's really hard to do that by having Spring create your instance variables returned by Hibernate. In the end, the difficulty of making domain objects smart and loosely coupled, but still coupled to other components while using an ORM makes the whole attempt for naught. Thus you will circle back and put the business logic in the controller or some controller adjacent code that is closely coupled with the view.
- jeswin 14y agoMy experience has been: - A file (not a pattern) is the most effective unit of code organization and navigation. - Any design, which can be split into multiple files in a logical and consistent manner is maintainable. A few years back, I might have cared about the issues mentioned in the original post. Looking back, many of them were just personal preferences.
- alisnic 14y ago> A file (not a pattern) is the most effective unit of code organization and navigation This can be misinterpreted in many ways.
- bstar77 14y agoMy problem with fat models is exactly what the author describes, data really needs to be separated from the logic. Since all of my current projects are service oriented, I generally abstract into gems, albeit with some minor overhead. That has proved to be the cleanest approach for me. Just throwing code into /lib never seemed to help the organization/readability problems.
- gavingmiller 14y agoI think someone needs to plug how awesome the Destroy All Software screencasts are (referenced in the post.) They are well worth their value, and have made me a better developer: https://www.destroyallsoftware.com/screencasts https://www.destroyallsoftware.com/screencasts
- alisnic 14y agoTotally agree.
- programminggeek 14y agoI've been working on the Obvious Architecture, which seeks to decouple your app from both the database and your web framework. Treating Rails or Sinatra or whatever as just a delivery mechanism frees you from a lot of of the potentially bad design decisions that your web framework made about how you should structure your app, the worst offender of this is how rails implies the use of ActiveRecord. You should use Rails for what it's good at - web stuff (routing, asset management, view engine integration). I've got an example Obvious twitter close/status update app in progress at https://github.com/RetroMocha/obvious_status https://github.com/RetroMocha/obvious_status Obvious is still super new and I'm working on documentation and examples. You can read more about Obvious at http://obvious.retromocha.com http://obvious.retromocha.com
- TylerE 14y agoThis sounds rather...performance killing. Too much abstraction is way way worse than too little, as well.
- tinco 14y agoNo it isn't. Too much abstraction can always be refactored down to less abstraction. (Too much abstraction means that there's abstraction that is unnecessary, thus it can be cut away.) Too little abstraction means your code is too closely coupled. When the code is fresh and small, then ofcourse it's not hard to abstract out a method to DRY something up. But when your code is big and old this will quickly approach infeasability, which is the reason many projects fail. I would go as far as saying that the science of software engineering exists because your statement is false :P
- andybak 14y agoSo you're saying that the arrow of refactoring generally points in the direction of less abstraction? That directly contradicts my experience where most refactoring introduces abstractions when they are needed.
- 14y ago
- tinco 14y agoI think many people react negatively to this article because they gloss over the idea that this is a refactoring. When coding you always make the choice between abstraction and readability. If you look at the patterns from GoF, the downside of almost every pattern is that it reduces readability for the simple cases. Thus, when you write your controllers and your models in my opinion you should start out doing it 'the rails way' which is having your business logic in your models, and the controlling logic in your controllers. Then when your app is starting to feel clunky, your controllers are gaining weight and your models have too many responsibilities, that is the point where you start refactoring. And that is also where you decide wether the logic belongs into a concern, or a policy, or whatever fancy pattern is hip right now and fits there. (But you really should make an effort to make clear where logic ends up for someone new to your code, so be consistent!) I think rapid development is one of the key features that made Rails big, don't let these concerns that big apps have drag you down in the beginning.
- jeremyjh 14y agoPeople will spew all these separate classes from the very beginning in the name of "test-ability".
- alisnic 14y agoI don't understand what is the problem of having a lot of small classes? They are there when you need them, and they get out of the way you don't, and I think that's the most important thing.
- dzuc 14y agoWhat to do about callbacks in the models?
- amalag 14y agoI suppose we don't want to test the fact that Rails does a callback, we would want to test the callback logic. We would test our domain class and then test if the model class has a callback.
- alisnic 14y agoSo far, I got a lot of feedback here and on my blog, which I didn't expect (also the frontpage). There seems to be a general attitude of "why complicating things from day 1?" towards the article I wrote. While I do agree that there is no need to isolate things from the start, I do not see the complications at all. The cost of creating a file and moving behaviour there is exactly 0, but the benefits are there from day 1.
- Firehed 14y agoEvery extra file I have to look through when programming is an added cost; abstractions add more than just CPU overhead. The highest-priced EC2 instance on Amazon is under $5/hr; you won't find a decent programmer whose time is worth anywhere near that little. This may be relatively minor on a small project, but adds up quickly. It's extra overhead when onboarding new developers, more time spent debugging (deeper stack traces, etc.) more time simply waiting for grep to run. I'll argue that you actually spend more time refactoring as well, since you're encouraged to spend more time on the upkeep of this over-abstraction for the sake of "clean code" rather than getting real work done. Probably more important is that you don't know your six-months-from-now problems now, you know them in about 5.9 months. I've wasted a lot of time building the wrong abstraction layers because I made bad assumptions about our future problems. At first, I thought that was a failure of my programming abilities; I've come to realize that was my failure of my psychic abilities. This isn't an attack on you or your style, merely a few observations about what kind of effect it would have on my own work. If it works well for you, awesome! However, it seems like most of this would only be really effective by v3 or so of a product: v1 answers "is this useful and worth investing more in", v2 covers "ok, let's turn this into a real product", and v3 is that giant scary internal rewrite (that goes great thanks to your now-high unit test coverage) that creates what you would have made from day one if you actually knew what you needed.
- agopaul 14y agoWell, basically this is the approach of Application Service pattern, isn't it?
- alisnic 14y agoI guess, but I am not informed enough to make a well-grounded statement about this. I guess this is Don't-keep-your-logic-in-delivery pattern.
- stiff 14y agoI think the importance of the Single Responsibility Principle is much overestimated by many people and introducing terms like "single-responsibilty objects" for what are essentially simple procedures is symptomatic of this. There are many properties of classes one can consider beneficial, but they might in fact conflict with each other to some extent, especially when carried out to extremes like in this case, so what you want is to find a good compromise. The "principle" itself is rather a very fuzzy guideline, so I wish it wouldn't be taught to people since it seems to do more harm than good to their understanding good OO design. On another note, I wonder to what extent Rails usage of the Active Record pattern makes "models" so clumsy in large applications. It works great to a certain point, but maybe in large applications the Data Mapper pattern (not to be confused with the DataMapper library) would help to isolate a large aspect of what currently goes in a model while keeping a nice OO interface: http://www.martinfowler.com/eaaCatalog/dataMapper.html http://www.martinfowler.com/eaaCatalog/dataMapper.html
- alisnic 14y ago> it seems to do more harm than good to their understanding good OO design. Can you provide an example?
- stiff 14y agoI heard this principle referenced in the context of objects such as presented in the post quite a few times already, for example there was a heated discussion here: http://news.ycombinator.com/item?id=4013807 http://news.ycombinator.com/item?id=4013807 I explained back then why I consider this not object oriented at all. Here I just wanted to say that if you imagine a spectrum of all the possible breakdowns of a problem into classes, with one huge class at one end and a breakdown so granular you have a separate class for each operation of the system on the other end, there is a point where making the classes have less responsibilities and having more of them is no longer beneficial to the overall design, because you start to violate many other basic principles, for example the "objects" don't really encapsulate any data anymore. I think finding the sweet spot here is the kind of skill that is learnt with experience and not easily passed on with words, hence the wording of the principle is so fuzzy.
- tieTYT 14y agoAs far as I can see, he's advocating a MVC approach. The controller is used for handling View input/output. The Model is where the data and business logic is supposed to go. For some reason most people think the Controller is where the business logic is supposed to go, but when you research The MVC Pattern, that's not how it's described. If you don't believe me, let me give you a little history. The MVC Pattern was created because of this problem: "I made this cool app with a fat client UI and now the higher ups want a way to view it from a web page. This is so freaking hard because the UI and the data and logic are all mixed together. How could I have planned ahead for this?" MVC is the answer. You take the View and the Control, make them specific to the UI and then you put your UI agnostic code (this includes data AND business logic) in the Model. That way, when the higher ups want a new UI, you just have to create new View and the Controller code.
- benrhughes 14y agoMVC is great for managing the display layer, but it doesn't really cover how to manage your domain logic. IME, MVC scales best when the "model" is a logicless DTO created by a business layer. For small scale apps this is overkill, and you can squash persistence and business logic into the model. But for some complex apps you really benefit from a multi-layer approach.
- sakopov 14y agoIt's a pretty good practice in MVC. I don't work with Rails, but in ASP.NET MVC, i put controller logic behind IBusinessTask generic interface which defines a type for input and output of its single `Execute` method. Any implementation of this interface would call the Business tier to perform its task. These implementations are then injected into controller via property or constructor injection.
- aurelianito 14y agoGiven code like this: class RatingsController < ApplicationController def create success = RatesBusiness.add_rating(params[:business_id], params[:score], current_user) render json: {success: success} end end Wouldn't it be better something like this (just to adapt to rails)? class RatingsController < ApplicationController logic = RatesBusiness end Or, even better class ApplicationController < Controller def initialize(logic): // do something end def create: // do something with the logic end end And adding some routes for each logic. But, then, you could just extract the route from the logic name (and get the rails controller back, that now it would be called "Logic") My point is that extracting code just for the fun of it seems worthless. What is the rationale in this case?
- gavingmiller 14y agoWhat you're talking about looks like (is...) dependency injection. Rails is a response to the Java FactorySingletonWidgetFactoryClass culture, so the question is does DI benefit Rails, or simply allows us to pontificate about our code? Which is what DHH is arguing against. In my experience DI has been a great pattern for writing testable code, so do you have any resources relevant to DI in Rails, and why it (DI) is more beneficial than the OPs argument of a Service based architecture? (other than a simple google search like I did?)
- aurelianito 14y agoI am just pointing out that refactoring the code he proposed, removing duplicate code, we arrive again at the Rails controller he does not like. I see duplicate code all over this "service based architecture".
- gavingmiller 14y agoI mostly agree, with the exception of the initializer, something I haven't seen put to much use in controllers. The main thrust of my question was, do you have any links/examples for a DI based rails controller? Other than your "in theory" refactoring?
- lucian303 14y ago"I look at Rails as web presentation layer, it makes my app work in the browser." Yes! YES! Not just rails. Any web app regardless of language or framework. Thank you for writing this. It's what Richard C. Martin (Uncle Bob) calls the lost years ... and about 15 of them. Reading books on good object oriented design from 1993 sheds more light on how to build proper systems (that just happen to have their delivery mechanism as the web). The interface should be able to be changed. Right now, this is very difficult in most frameworks (I do PHP but Uncle Bob uses Rails as his example ... it really doesn't matter) and it's quite sad. This isn't a philosophical or abstract discussion. This is about how to build software the right way. You're not building a web app. You're building an app whose delivery mechanism happens to be the web. See Uncle Bob's talk: http://vimeo.com/43612849 http://vimeo.com/43612849 Period.