When faced with real life complexity there rarely is an easy way out or a silver bullet solution. Here I will describe the model I’ve come up with during years of trial and error. The solution presented here is as far as I know not a mainstream one and it could be considered somewhat controversial.

Object Oriented Approach

Here we’re living in OO land. Each and every problem can be broken down to objects - we test them out both individually and jointly. We combine them in beautiful ways and then they produce the result we aim for.

Pros

purist

allows for modular design

Cons

can be slow

complicated for complex cases

ravioli code syndrome

additional layer of complexity by wrapping raw data into other structures

Database Driven Approach

Also known as writing big SQL queries. Here we rely on SQL to capture business requirements and we ignore objects altogether. Our aim is to join multiple database entities based on their relation and business rules. What we end up with is just the data we that we’re looking for - already prepared.

Lets look at an example: Imagine we are building a new programmer marketplace (but better than existing ones) and we need to list users who have applied for our platform during last week, who want to take the Java test and who haven’t applied for any other tests but have logged in at least 3 times from Canada). This is trivial to fetch directly from database but could be pretty complex otherwise.

Pros:

SQL is 4GL

using the database directly is usually the fastest (performance-wise)

shines when used with a properly built ER data model

Cons

occurrence of massive SQL queries possible

effort needed to isolate from the rest of application

needs an adapter so the rest of the application can use it

What to choose

It is not a question of ‘what to choose’. It is a question of ‘when to choose’

If the problem is mostly based on data we store inside of the database then I would definitely go with the database approach. The way I see it - get it working, find any possible holes in the data model (happens quite often with new functionality) which could prevent the building of solution and then if it looks like a bad fit I can always switch to a different approach (which in turn happens very rarely). This allows me to do a reality check without investing too much in “beautiful” OO structures that are needed otherwise.

If the problem is algorithmic and needs extensive testing I mostly go with the OO approach. This gives us the shortest route to the solution. In all the projects that I’ve worked on so far the ratio was somewhere around 90% in favor of the database-driven approach

From my experience 9 out of 10 times I was better off with using SQL.

Example

I’ll show a real life example from our GymTrainer application which is at the time of writing still being built. We are trying to figure out the weight being lifted during workouts for our visualization.

class ProgressBusinessObject def self . weight_graph_data ( user_id , category ) #express logic in 4GL language #easy to reason about #easy to change and adjust progress_sql = <<- SQL SELECT Date(logged_at), Max(logged_weight) AS weight, workout_items.unit, workout_set_groups.set_group_name AS category FROM workout_items INNER JOIN workout_set_groups ON workout_set_groups.id = workout_items.set_group_id WHERE user_id = #{ActiveRecord::Base.connection.quote(user_id)} AND logged = true AND workout_set_groups.set_group_name = #{ActiveRecord::Base.connection.quote(category)} GROUP BY Date(logged_at), workout_set_groups.set_group_name, workout_items.unit ORDER BY Date(logged_at) SQL #put results in object for the rest of application to seamlessly use ( ActiveRecord : :Base . connection . execute progress_sql ) . map { | i | WeightGraphItem . new ( i ) } end end

In a sense we just get the view of the data that we need. In order to use this more or less normally I am wrapping SQL query based results to a “data view” object named WeightGraphItem

#weight_graph_item.rb class WeightGraphItem < KodiusPoro #attributes and types, hydrated from business object attr_accessor :date , :weight , :category , :unit define_types date : Date , weight : Float #helper method def prepare_for_highstock_charts [ date . to_time . to_i . to_s . ljust ( 13 , '0' ) . to_i , weight ] end end

This is a tangential remark but this KodiusPoro is here just to initialize attributes without manual assignment and to ensure correct data types (date, float etc..)

#kodius_poro.rb class KodiusPoro def self . define_types ( type_hash ) @@types = type_hash end def initialize ( h ) @@types ||= {} h . each do | k , v | if @@types [ k . to_sym ] != nil tip = @@types [ k . to_sym ] v = v . to_f if tip == Float v = tip . parse ( v ) if tip == Date end public_send ( " #{ k } =" , v ) end end end

Conclusion

I don’t see any monumental downsides to this approach compared to the OO approach. It can certainly look a tad odd and perhaps slightly “unclean”, but as far as I’m concerned this is more of a superficial issue and has more to do with existing prejudices against the approach rather than something substantive.