Startup Ruby
Separate things that change from things
that stay the same
Program to an interface, not an
implementation
Prefer composition over inheritance
Delegate, delegate, delegate
You Ain't Gonna Need It (YAGNI)
I’m Mike Subelsky
@subelsky
I’m Mike Subelsky
@subelsky
This is what I have learned while building
What’s unique about
startup programming?
Facing unknown problems & unknown solutions
With scarce time and
resources
What advice would I give myself
to thrive in these conditions?
Design thoughtfully before implementing
Don’t make these specific mistakes
Separate things that change from things
that stay the same
Program to interfaces, not implementations
Prefer composition over inheritance
Delegate, delegate, delegate
You Ain't Gonna Need It (YAGNI)
Design for Change
"Separate code for general functionality from
code for specialized functionality"
Isolate design decisions in their own modules
First Use of SQS
SQS =
RightAws::SqsGen2.new(access_key_i
d, secret_access_key], { :multi_thread
=> true })
queue =
SQS.queue("#{RAILS_ENV}_#{queue_name
}")
First Use of SQS
SQS =
RightAws::SqsGen2.new(access_key_i
d, secret_access_key], { :multi_thread
=> true })
queue =
SQS.queue("#{RAILS_ENV}_#{queue_name
}")
Then both of these changed
Designed for Change
queue =
QueueFetcher.fetch(queue_name)
queue.send_message({ :user_id =>
user_id }.to_yaml
Isolates how we connect to the messaging system
Isolates naming convention for the messaging system
class QueueFetcher
def self.fetch(queue_name)
SQS2.queue("#{queue_env}_#{queue_name}")
end
private
def self.queue_env
APP_CONFIG['queue_context']
end
end
class QueueFetcher
def self.fetch(queue_name)
SQS2.queue("#{queue_env}_#{queue_name}")
end
private
def self.queue_env
APP_CONFIG['queue_context']
end
end
More isolation
I don’t like this
class QueueFetcher
def self.fetch(queue_name)
SQS2.queue("#{queue_env}_#{queue_name}")
end
private
def self.queue_env
APP_CONFIG['queue_context']
end
end
More isolation
Business Logic
class Envelope
def deliver_first_message
new_mailbox_name = Mailbox.new_mailbox_name(recipient)
mailbox = Mailbox.create!(:user_id => user_id, :name => new_mailbox_name)
prepare_delivery_for_spam
prepare_delivery_for_forwarding(mailbox)
self.first_message = true
return deliver(mailbox)
end
Program to
interfaces, not
implementations
Program to general types
"Don’t call it a car if you can get
away with calling it a vehicle"
Program to
interfaces, not
implementations
Easy for us to do with duck-typing
class Message < ActiveRecord::Base
end
class ArticleMessage < Message
end
class SmtpMessage < Message
end
class SentMessage < Message
end
class Message < ActiveRecord::Base
end
?
class ArticleMessage < Message
end
class SmtpMessage < Message
end
class SentMessage < Message
end
Prefer composition
over inheritance
Equip objects with references to
other objects which implement
common behavior
module S3MessageContent
private
def head_from_s3(filename)
S3.head(APP_CONFIG['message_bucket_name'],filename)
end
end
class Attachment < ActiveRecord::Base
include S3MessageContent
belongs_to :message
before_create :put_attachment_on_s3
before_destroy :remove_from_s3
end
Delegate, delegate,
delegate
Objects express certain
outward behavior
but actually delegate responsibility for
that behavior to another object
Delegate, delegate,
delegate
Really good for ActiveRecord
relationships
Delegate, delegate,
delegate
Really good for ActiveRecord
relationships
class ExternalEmailAccount < ActiveRecord::Base
belongs_to :external_email_server
delegate :server_info, :mail_server,
:to => :external_email_server
Delegate, delegate,
delegate
Forwardable and Delegate modules
also make this easy
YAGNI
YAGNI
We love solving cool, new problems
with cool, new toys
YAGNI
We love solving cool, new problems
with cool, new toys
So sometimes we look into the future
for opportunities
YAGNI
This is a fatal instinct in startups
YAGNI
I’ve built things to be super-scalable that
turned out not to be core to the product
YAGNI
I’ve built things to be super-scalable that
turned out not to be core to the product
YAGNI
In the early days, focus on
learning not performance*
YAGNI
In the early days, focus on
learning not performance*
Concentrate on 80% solutions
YAGNI
In the early days, focus on
learning not performance*
Concentrate on 80% solutions
*startuplessonslearned.com
YAGNI
Be a duct tape programmer
YAGNI
Be a duct tape programmer
“...any kind of coding technique that’s
even slightly complicated is going to
doom your project.”
http://www.joelonsoftware.com/items/
2009/09/23.html
Don’t make these specific mistakes
Plan to move
everything out of the
web request
Plan to move
everything out of the
web request
ar_mailer, delayed_job,
EventMachine, SQS,
beanstalkd, etc.
Plan to move
everything out of the
web request
But remember YAGNI
Make careful use of
concurrency
Make careful use of
concurrency
Prefer processes communicating
via message bus
(SQS, Starling, delayed_job,
Rabbit MQ, etc.)
Make careful use of
concurrency
Check out Unicorn
http://tomayko.com/writings/unicorn-is-unix
Make careful use of
concurrency
Threading: EventMachine is your friend
Make careful use of
concurrency
Threading: EventMachine is your friend
EMH.safe_defer do
begin
UserMailer.deliver_verification_email(@user, @email)
rescue StandardError
logger.warn("Unable to deliver signup verification
to #{@user.login} due to #{$!.message}")
end
end
Consider your RDBMS
relationship
Consider your RDBMS
relationship
Avoid touching the DB when storing
non-critical data
Consider your RDBMS
relationship
Avoid touching the DB when storing
non-critical data
Don’t use an RDBMS for
things it’s not good at
Consider your RDBMS
relationship
Avoid touching the DB when storing
non-critical data
Don’t use an RDBMS for
things it’s not good at
We rely heavily on AWS
Consider your RDBMS
relationship
Storing large text blobs (S3)
Messaging system (SQS)
Logging events (SimpleDB)
Caching dynamic text (S3)
Consider your RDBMS
relationship
We use data_fabric gem to make
master-slave transparent
Consider your RDBMS
relationship
We also just used it
to shard our DB
Avoid Boolean
Columns
Often want to know what time
something changed
Or you later end up needing more
than 2 states
Bundle complex view
logic into Presenters
Bundle complex view
logic into Presenters
class RefreshController < ApplicationController
before_filter :signin_required
def index
render :text => JSON.generate(AdvancedRefresher.new(params).to_hash)
end
end
Maybe don't test all
the time, at the
beginning?
Maybe don't test all
the time, at the
beginning?
Maybe don't test all
the time, at the
beginning?
Can slow down exploratory
programming
Maybe don't test all
the time, at the
beginning?
You’ll probably throw away
half the stuff
you write at the beginning anyway
Maybe don't test all
the time, at the
beginning?
You’ll definitely change
the names of things!
Maybe don't test all
the time, at the
beginning?
On the other hand, tests can
be a design tool (as with BDD)
I wrote our SMTP code this way
Maybe don't test all
the time, at the
beginning?
Would be interesting to see how
many Rails Rumble teams use tests
0 comments
Post a comment