11 ms·
Objectify: A Better Way to Build Rails Applications
- cletus 14y agoFWIW there's already a library called Objectify that's a Java-based ORM (lite) for AppEngine: http://code.google.com/p/objectify-appengine/ http://code.google.com/p/objectify-appengine/ If you want your project to be found easily (on search engines) I always suggest: 1. Prefer a made up word to a non-English word (or a word in any live language for that matter); and 2. Don't choose a name that something else in the same or similar space shares. They've been around longer. It's just going to make your life harder. YMMV.
- stickfigure 14y agoFurthermore, it's the top Google hit for 'objectify'.
- iamgilesbowkett 14y agoDo you really think the OP had SEO as a priority? Likewise I disagree with the idea that these two projects share the same space. A Java ORM and a Rails metaframework for avoiding ORM are not things which have a lot in common. I think the last time I observed a Rails hacker saying anything about AppEngine was 2007 or something.
- stickfigure 14y agoIt will certainly cause confusion. This is just decidedly uncool. As the lead developer of the Objectify project, and someone who makes a living through consulting related to Objectify, I am highly annoyed. I'm also talking to a trademark attorney to find out my options.
- iamgilesbowkett 14y agoJesus, dude, why not just talk to OP instead? Any lawyer will tell you that if you can resolve your differences peacefully out of court, you should. This seems to be some kind of hair-trigger litigiousness thing you've got on going here.
- stickfigure 14y agoRelax, it's not like I'm setting a court date. I just prefer to know where I stand before I open my mouth, and I happen to work with lawyers who specialize in this kind of thing. Research before action, no?
- aantix 14y ago>One in particular is something I've been thinking about and >refining for a while now [3]. In this approach, persistence >objects remain extremely thin, and business logic is >encapsulated in lots of very simple objects known as >“services” and “policies”. Not all objects in this >methodology will fit in to one of those two categories, >but they are two of the most important concepts. I've seen this division of labor outlined in this DestroyAllSoftware screencast : https://www.destroyallsoftware.com/screencasts/catalog/fast-tests-with-and-without-rails https://www.destroyallsoftware.com/screencasts/catalog/fast-... As can be implied by the title, one of the side effects of keeping your service logic in their own separate plain Ruby classes is that you don't have to load Rails as dependency which means tests can run _really_ fast.
- regularfry 14y agoYeah, services are one of the types that have leaked out of DDD (and probably leaked into it from somewhere else).
- brunoc 14y agoI don't get why these models get bloated. If there's a language that makes it east break up classes into any number of components, surely that's Ruby! Mixins, open classes, modules, etc..
- jamesgolick 14y agoMixins/modules and open classes do not separate your class in to multiple components - merely multiple files. As I explain in the article, for separate components to be useful, they have to be "protected" from each other - encapsulation, in other words.
- brunoc 14y agoNo doubt - That's just good OOP. Looks like this gem will help people achieve that.
- aphyr 14y agoMixins (well, as they're supposed to be used) do provide encapsulation; specifically, they enforce the boundary between instance scope (e.g. access to instance vars) and method interface. I view them as a part of a coupling continuum: [instance methods] --- [composed subclasses] --- [mixins] --- [the outside world] But Modules provide more than that. module_function allows for unattached namespaced functions which can be referenced by full name, relative name, or imported into the local scope. Great way to express functions which are decoupled from instance state--much like the services and policies you're advocating for. It's an under-appreciated aspect of the language, I think.
- jes5199 14y agoI find that ruby projects end up breaking things up into modules, but fail to gain much benefit from it, since there's a tendency for the logic from the different "modules" to be tightly coupled. It's actually really hard to refactor ruby, since because it's so dynamic and so much state is shared, it's very hard to make safe changes.
- regularfry 14y agoCan't say I'm a fan of single-method classes. They always make me ask "why isn't this just a method?"
- jamesgolick 14y agoBecause of SRP.
- rbxbx 14y agoAt first I thought this was an attempt at trolling, but then I noticed that Mr. Golick is the commenter so therefor I must assume serious intent. Care to give an answer better than "some book some java dudes wrote 20 years ago said so"? While I generally agree with SRP, when that single responsibility can be expressed in a single function, I don't see the win with have a class dedicated to it. Smells of pedant OO nonsense. Is not instantiation etc a cross-cutting concern that violates SRP in this instance?
- jamesgolick 14y agoI gave an answer better than that. Did you read the post?
- manuscreationis 14y ago> Is not instantiation etc a cross-cutting concern that violates SRP in this instance? Right. Which is why hes also recommending the use of DI. It's hard to strike a balance between a strong design which takes a lot of time to work within, and one that just allows you to get things done, but once your project begins to grow in size and complexity, and testing becomes even more important, these things really do start to matter. It's difficult to internalize until it's bitten you on the ass, hard. That said, if you have a large amount of single method classes, you might need to take a look at why you need them and find a better approach (which will vary from project to project).
- jamesgolick 14y ago
- jordo37 14y agoDoes this actually perform faster by being on top of rails or would we need a new Ruby framework to get the most speed optimizations from the services and policies?
- amalag 14y agoThis replaces the controller with classes for each action, a Service? Then responders are automatically called and they do renders. Sounds nice, would be fine with it as a default, but not sure it warrants doing a big change for me.
- jamesgolick 14y agoYou can move over slowly by building new components using objectify, and leaving the rest of your legacy app the way it is, if you like. It's designed specifically to be able to coexist with what you've got.
- kreek 14y agoIsn't this the command pattern, except instead of 'execute' you use 'call'? Update: not saying that's a bad thing I used to be a Flex developer and all the frameworks used MVCS (Robotlegs being my favorite). Controllers are all commands and Services are used to retrieve data.
- manuscreationis 14y agoWhen I was first trying to learn about rails, I asked a friend who had been developing in it for a while about things like DI, and separation of concerns. He told me that those were things you used in Java and .Net because the languages just weren't as... I'm struggling to remember the exact term he used, but essentially "open" or "un-restricted". He echoed the phrase I've seen elsewhere "Ruby just doesn't need that". I disagreed with him then, and still do. People like to claim that OOP breeds cargo cult programming and overly cumbersome abstractions upon abstractions (and it can), but some times you really do need that kind of approach to make a large scale project testable and maintainable. I'm glad to see there are Ruby devs who can see the value in using these kind of approaches. I honestly believe it's the kind of thing you think is a major waste of time, until you are shown the benefit first hand, and you experience the change. Then you start to understand that by spending a significant deal of time up front building your infrastructure, you can save a lot of time down the road. I know that's how it was for me years back.
- jes5199 14y agoHe probably used the word "expressive", which is how we used to excuse ruby's problems. It's pretty clear, by now, that we (the ruby community) were wrong about how to maintain projects over time without getting mired in complexity.
- aamar 14y agoProbably worth bringing up Steve Yegge's 2006 "Execution in the Kingdom of the Nouns": http://steve-yegge.blogspot.com/2006/03/execution-in-kingdom-of-nouns.html http://steve-yegge.blogspot.com/2006/03/execution-in-kingdom... I don't think there is a single right answer that is applicable in all cases. But over time, I've come to believe that as a default, code works well when it resembles how you'd ideally explain the service to an extremely intelligent, non-technical person with domain experience. This makes for code that is easy to understand, collaborate around, and modify given changing objectives. That means creating "Policy" and especially "Service" models--extra nouns--very sparingly. For example, an Elevator was once a nounification of a verb, but people understood it, so it makes sense as a class. ElevatorDoor's good too, but ElevatorDoorOpeningService is probably not good. This is absolutely at odds with strict application of the Single Responsibility Principle.
- aculver 14y agoThe "Ubiquitous Language" is the label I've seen applied to the idea you're describing. For example: http://martinfowler.com/bliki/UbiquitousLanguage.html http://martinfowler.com/bliki/UbiquitousLanguage.html This idea of having an ubiquitous domain language and keeping applications and APIs RESTful have been two of the most beneficial ideas I've incorporated into my development in recent years.
- judofyr 14y agoSomeone has to say it: Damn, this looks fucking ridiculous! First we have the false modularity. You see a bunch of separate stuff (classes, methods, whatever) and think "Wow, nice, now we can reuse this all over the place!" The truth is that these policies/services/renderers are going to be complected; you're going to create them in triplets as you implement your application. Moving things around doesn't make them modular. They can only be modular if the author actually implements them in a modular way (and you can do that in Rails today). The DI-framework can't handle change at all: class CurrentUserResolver def initialize(user_finder = User) @user_finder = user_finder end # note that resolvers themselves get injected def call(session) @user_finder.find_by_id(session[:current_user_id]) end end What if I later need the cookies/params to detect user session? If I changes the #call method, every single test breaks. And then there's the added complexity. Now I need to keep track of the route-mapping, default policy, route policies, the route service and the route renderer. Which are now in several different files. And there's not even a clean class-to-file mapping to help me locate it. Oh, and the premise of testability. Well, this makes it very easy to test units itself, but the more you split your projects into smaller units, the more bugs occur when you mix the units. Sure, you can get your precious fast test runs (and a nice green test), but in reality you're only testing a single unit of a complected app. EDIT: This reminds my of Rick Hickey's quote from keynote at RailsConf2012: ""We can make the same exact software we are making today with dramatically simpler stuff—dramatically simpler languages, tools, techniques, approaches. Like really radically simpler—radically simpler than Ruby which seems simple.""
- nicholasjhenry 14y agoWhen you start a comment off with a sentence like that, you lose all credibility.
- primatology 14y agoI don't necessarily think so. He draws you in with said outrageous remark and proceeds to raise some good points.
- 14y ago
- stiff 14y agoThis is not object-oriented at all, unless you learned about object-orientation from Java and its monster frameworks, this is basically a form of data-flow programming. OO is about message passing, here the "objects" do not pass any messages at all, the supposed "objects" _are_ the messages, and the routing of those messages takes place in the configuration in routes.rb, so the objects do almost no message-passing between themselves at all! It is ironic to see this advertised as "better OOP practices for rails apps". Having said this, I respect the attempt, the problem it tries to solve certainly does exist to some extent, and the solution cannot be completely dismissed very easily. But please, please, do learn both A) basic history and B) basic theory of the field you are working in. If you try to reinvent everything you decrease your chances of contributing something new manyfold.
- jamesgolick 14y agoI've read at least a dozen papers on the history of OO, not to mention books and papers on current practices. This is what I came up with. I made what I think is a pretty coherent argument for why in the article. I'd love to hear your refutation of the points I made in there.
- stiff 14y agoYou have to think a bit more about what OO really is about. How would properties of your solution differ if you instead of classes split all the code into many very small global methods? The essence of OO is objects freely passing messages between themselves, if you fix all the message routing and put it in a central place you are not doing OO anymore, you loose the most fundamental properties, for example: http://en.wikipedia.org/wiki/Dynamic_dispatch http://en.wikipedia.org/wiki/Dynamic_dispatch You also don't have encapsulation anymore, since to be able to perform the majority of the work in the services, the services have to operate directly on data owned by other objects. How can you combine the policies in ways other than a simple logical AND? How do you combine services? Others have also already pointed out other issues. Please don't be offended by the above. Software engineering is unfortunately a field where some valuable content is buried underneath loads of BS, since it is so easy to "philosophize" about it even with just basic programming experience and this leads other people learning the field to easy misconceptions. I do not want to discredit the work you put into this and it might be that in some form something valuable will come from some of the ideas. It is an interesting problem to work on and for me it was an interesting solution to think about, I just don't think you are there yet.
- _pius 14y agoRelevant: http://weblog.jamisbuck.org/2008/11/9/legos-play-doh-and-programming http://weblog.jamisbuck.org/2008/11/9/legos-play-doh-and-pro...
- aculver 14y ago"Single responsibility principle (SRP): ... ActiveRecord::Base already has a responsibility: persistence." Wrong. I'm hearing this more and more, but let's be clear: Active Record as a design pattern, before Rails, has always been a joining of business logic and persistence. (http://martinfowler.com/eaaCatalog/activeRecord.html http://martinfowler.com/eaaCatalog/activeRecord.html) It's always been in violation of the single responsibility principle. If you want to avoid that, don't use Active Record! Use a Data Mapper, which specifically separates Mappers and Finders from Domain model classes. (http://martinfowler.com/eaaCatalog/dataMapper.html http://martinfowler.com/eaaCatalog/dataMapper.html) I'm not saying you can't move logic out into reusable modules or whatever is being recommended here, but please don't make it out like people are in the wrong when they're using the library or framework the way it was designed to be used.
- jamesgolick 14y agoThe point I was making was that you can use the library in a different way successfully.
- akmiller 14y agoWhy are people building frameworks to sit on top of yet another framework to try to write cleaner more maintainable code??? What is wrong with writing your core application logic physically removed from any of these frameworks. Why do people insist on fitting their application into the Rails framework or any other web framework. Write your core application code in Ruby (or your language of choice), package it in another gem, use tools and patterns we've had all along to plug that code into a dependency for persistence (if needed)...communicate with it through a standard API that you define for your delivery mechanism (i.e. Rails). I just don't get this idea of trying to contain my application code within the confines of a specific framework.
- iamgilesbowkett 14y agoI have a prejudicial assumption that if you don't know the problem Objectify solves, you haven't worked with a Rails app which has A) a large code base B) a large, active user base and C) a bunch of different features. The vast majority of people who scale Rails sites (as far as I can tell) do so by breaking their apps into services. ActiveRecord god-objects do not make that step in an app's life cycle easy, and separating persistence from business models is likely to be your first important step when refactoring for scalability. Fragmenting a User model bloated beyond all hope of sanity is almost a rite of passage at this point. I often think the only Rails developer who hasn't found ActiveRecord bloat to be an irritation and an obstacle to scaling is DHH, and although that makes his opinion on the subject even more valuable than it would normally be, he doesn't talk about it enough in my opinion. Pretty much everyone else, as far as I can tell, responds to Rails scaling issues by creating services in Sinatra and/or divorcing business modeling from persistence logic. I want someone to challenge my prejudicial assumption here, most of all because I appear to be saying "Rails can't scale," which is BS, but also because I'm very tempted not to take this discussion seriously at all if it doesn't address this point. It's just the crucial point in my opinion. I like the whole OOP thing but to me the decision to use something like Objectify is all about scalability. That doesn't just mean performance; it also means retaining readability when you have a lot of code. Single Responsibility Principle makes code readable.
- bcrescimanno 14y agoFWIW, I'm not a Rails developer; so take my comments as you will. I don't think the problem people seem to have with Objectify is that they see it as a totally invalid solution to a problem--and I agree with your assessment that many people see this problem as very real. My own take is that I find it disingenuous to call it an Object-Oriented solution to the problem; it's not. That doesn't make it bad, invalid, or wrong. It rightly raises a few eyebrows to talk about effectively calling namespaced procedures as if it were the pinnacle of Object-oriented design.
- stiff 14y agoI worked in Rails for the last 6 years professionally, and for the last 4 years on a single application. There are lots of ways to tackle this problem, but it really depends what precisely you have inside those models and there are certainly no universal silver bullets. Lots of people who complain about this underuse Rails features, external plugins etc. I think two good ways to deal with the problem are: - Splitting out logical pieces of behaviour into separate modules, especially the ones less central to the core responsibilities of the class. If you look at Rails itself (it's great Ruby code, I highly recommend really reading it), this is the way it is structured, ActiveRecord::Base provides tons of functionality as a single class, yet it still is very neatly laid out into many well-separated modules. See e. g.: https://gist.github.com/1014971 https://gist.github.com/1014971 http://api.rubyonrails.org/classes/ActiveSupport/Concern.html http://api.rubyonrails.org/classes/ActiveSupport/Concern.htm... https://github.com/jakehow/concerned_with https://github.com/jakehow/concerned_with http://blog.waxman.me/extending-your-models-in-rails-3 http://blog.waxman.me/extending-your-models-in-rails-3 Deciding what things would fit well into external modules and what modules to create is a new skill to be learned, but I think it can work well once you do learn it. - Things that aren't part of business logic but just handle some more technical matters should be extracted into plugins and not be directly part of the models (for example: special kinds of validations). - There is the whole world of OO techniques and patterns that can be applied here just like anywhere else. For example you can extract complicated algorithms into separate classes or use value objects: http://api.rubyonrails.org/classes/ActiveRecord/Aggregations/ClassMethods.html http://api.rubyonrails.org/classes/ActiveRecord/Aggregations...
- deleted 14y ago[deleted]
- drumdance 14y ago"The more responsibilities an object has, the more complex its behaviour becomes, and is therefore more difficult to prove and reason about." Emphasis mine. I don't think this has to be true. Yes, models get fatter, but the responsibilities will have to be implemented somewhere and to me it's simpler and easier to debug when it's as close to the domain model as possible instead of in some abstract/generic model. Of course, if you have policies and services that apply across domains, then by all means break them out and recompose as necessary in the the models
- perlpimp 14y agoNot quote Donald K. but I've seen my share of optimizing rails apps that points to the fact that each class added is extra 2-4 megs of RAM. This sort of discouraging rule, looking ahead writing well tested app that does follow SRP and other nifty "literate" practices might cost you big time, tell me if I am wrong.
- nyrb 14y agoObjectify is a nice framework, but after reading the examples in README made me feel like I am required to take extra iterations: create new responder files, write more codes for each Responder class, etc. This approach is little tedious. I personally think that writing reusable libraries using Decorator/Presenter patterns or Modules with ActiveSupport::Concern is simple approach. Avdi's slides: https://speakerdeck.com/u/avdi/p/making-little-classes-out-of-big-ones https://speakerdeck.com/u/avdi/p/making-little-classes-out-o... I highly recommend Objects on Rails book: http://objectsonrails.com/ http://objectsonrails.com/