May 12, 2012 by Alexander Lang
The following is a list of ideas I pulled out of my head over the course of today. While it was slapped together rather spontaneously it is based on six years of writing my own web apps, looking at others’ code and basically a lot of headscratching and crying.
None of these ideas are new, others have written about them over and over. Still most of the world around me is stuck in uncomprehensible code, coffee breaks for running tests and banging their head against the wall.
The intention of this post is not to piss on anyone’s coding style but to lay a foundation for writing better apps in the future – for myself and hopefully for others, too. The code examples are in Ruby but most of it should apply to other OO languages as well.
Your business models are the core of your application. They model your business domain and nothing else.
They don’t extend a framework base class like ActiveRecord::Base
.
Instead they use delegation. This decouples them from your persistence framework and makes them unit testable.
Don’t do this:
class User::ActiveRecord::Base
def update_profile(attributes)
...
save
end
end
Instead do this:
class User
def update_profile(attributes)
storage.load(user_id).update_attributes(attributes)
end
end
No Callbacks. The various lifecycle callbacks most persistence frameworks now offer make your life easier if you have four models with 10 lines of code each. In larger apps they lead to unpredictable chains of code execution and tight coupling.
Don’t do this:
class User
after_save :create_welcome_message
def create_welcome_message
Message.create! text: 'Welcome User!'
end
end
Instead do this:
class Signup
def complete
user.confirm
Mailbox.create_welcome_message_for(user)
end
end
Instead they offer an API for others to use.
Don’t do this:
Comment.create author: user.name
Instead do this:
user.sign comment
If other objects want to access that data they have to ask the respective model.
Don’t do this:
class Comment
def create
...
storage.load_user(author_id).comment_count += 1
end
end
Instead do this:
class Comment
def create
...
user.notify(:comment_created, self)
end
end
Don’t be afraid to use more than one. Don’t use a relational DB for full text search. Use a full text search engine. Don’t store logging data in your main database, use a logging service. And don’t kill -9
your MongoDB.
Some of you data may be write heavy, other read heavy and in need of caching. Research your options, don’t just rely on what’s the default. It will make your life easier and your app faster.
The internet is meant to be connected. Data must be free. Apps need to communicate. You want to build your service on top of others. Others will want to build on top of you.
A proper RESTful JSON API is easy to explore and understand. REST scales, it’s what the internet is made of.
Your human facing front-end is either a client side JavaScript app or another server side app.
OAuth2 is easy and open. It has security built in. It builds the basis for others to use your API.
This is 2012.
As in real unit tests, no database access in tests. There will be many, they must run fast.
If they are slower make them faster. If you can’t make them faster split your code base. There is not enough going on on Twitter and Hacker News to wait for slow tests. More importantly your customer shouldn’t pay you waiting.
Rails is great, but do you really need all of it? Since 3.0 it has been split into smaller parts. Maybe Sinatra + ActiveRecord will do the job? A 50 line Gemfile
may be impressive but it sure hosts a ton of bugs. Less code means faster, simpler, error free-er.
I can’t promise you anything but these are the effects I’m trying to achieve.
By decoupling objects they can be truly unit tested individually. Your tests will be easy to write and they will run fast.
Your main app being an API means you can add mobile apps and other clients easily. Your components being independent means you can swap backends or frameworks and even programming languages.
Fast tests, easy to understand relations, small code base – pain free coding, short development cycles, bring out the unicorns.
I have tried these ideas out to variying degrees. I will continue to do so over the next months. Maybe I’ll start crying again. Then it’ll be time for another post. Until then, let’s move forward, let’s write better software. For the unicorns.
Follow me on Twitter: @langalex, Github: https://github.com/langalex