Tackling God objects in Ruby applications

Part 4 in Domain Drive Design series following on from form objects.

The worst advice I have followed

Fat Model, Skinny controller

This advice is only good advice in that a fat model is slightly preferable to a fat controller. When creating projects with Rails I would have a single model for a single database table. So as a database table grows then data in the model representation grows. Object Oriented Programming (OOP) is about packaging behavior with data, as the data in the model increases the behavior on the model tends to follow this increase. Quite soon you end up with a fat model and shorty afterwards a God object. God objects are objects that seem to know and act upon all parts of the system. They are an antipattern that are in violation of the single responsibility principle.

Recently I have seen people blaming Rails and particularly ActiveRecord (the library) for encouraging God objects. ActiveRecord is a beast but also a fantastic piece of software. Most importantly it is a tool and doesn’t encourage or discourage God objects. ActiveRecord does, however, facilitate making over sized objects if you are not disciplined. What does encourage creating God objects is simplistic advice such as ‘fat model skinny controller’.

So what is good practise? Well I think that it is small, well defined, single responsibility objects everywhere.

The persistence problem

Handling the data in a single database table, sounds like a single responsibility so why not a single object. That is a valid starting point but for anything beyond a trivial application it is too simplistic. To break apart our model let’s look at some of the key functionality we need when handling a database table.

Building a solution

First we want to separate querying, this functionality acts on the whole database table while the other two parts need to act of individual entries. To handle querying we will introduce repositories in the next post.

To split serialization and behavior I employ the decorator pattern. I have a core Record object that formats the data saved to it. Record objects are just dumb data buckets. Business logic is built into entities - each entity wraps a single record and has no state other that the record object it is decorating.

Records

These are the only objects anywhere in my system that know how data is stored in the database. We often don’t think about databases as out of our control but we did not build them and so the API is not of our design. Therefore it most likely will not be the best representation of data in our domain. For this reason Record objects act as gatekeepers to information from the database in a way analagous to form objects normalising input from user input.

The only logic a Record object should have should be packing domain objects into the database and initializing domain objects from entries in the database.

Talking to a database is a solved problem and I use an ORM, either ActiveRecord or Sequel. The benefits of using an ORM library is that I can write Record very quickly. However both ORMs have many features that allow them to act as far more than a data bucket. It is only by being disciplined that the functionality of a record remains serialization only.

class UserRecord < Sequel::Model(:users)
end

Entities

These objects encapsulate business logic and so have very little in common with each other, or across projects. The only consistent feature is that the are initialized with a record and that they cannot have any state on themselves. They must always update the record if something changes.

Multiple entity classes can be created for a single record class. These entities are separated by business concerns and there is no limit to how many can be made for a single record. Now the behavior can be built up of small manageable parts no matter how large our database table grows.

Other parts of the program should never need to interact with a record directly but only with an entity that is decorating it. Good DDD code should communicate in a language understood by the client. They will understand user but not ‘user entity’ or ‘user record’ for this reason the user is represented by the user entity and the user record is just an implementation detail

Examples

A very typical example would be similar to the following. A user represents the information kept in the users database table. There is a good amount of meta information associated with the account such as authentication credentials.

Following good design principles we have dedicated value objects for email, password and name. This means we have moved validation away from our entities. Our record layer will happily dump and load these value objects to the database. However features continue to be added and our user needs a combined full name and also the ability to authenticate. These methods are not part of the same set of responsibility and so we look for a way to separate them.

Authentication is often a good candidate to be separated to a single purpose entity. In this example we have done just that and named the extracted class ‘Credentials’.

class UserRecord < Sequel::Model(:users)
  # Turns database friendly representations of values in to rich objects specific to our domain
  plugin :serialization

  serialize_attributes [Email.method(:dump), Email.method(:load)], :email
  serialize_attributes [Password.method(:dump), Password.method(:load)], :password
  serialize_attributes [Name.method(:dump), Name.method(:load)], :first_name, :last_name
end

class User
  # Enities make no sense if they don't also have a record to preserve state
  def initialize(record)
    @record = record
  end

  attr_reader :record
  private :record

  # Behaviour that the user can handle itself.
  def full_name
    "#{record.first_name} #{record.last_name}"
  end

  def full_name=(full_name)
    first_name, last_name = full_name.split ' '
    record.first_name = first_name
    record.last_name = last_name
  end

  # Behaviour that the user delegates to a dedicated credentials object
  def authenticate(submitted_password)
    credentials.authenticate submitted_password
  end

  # The credentials object also uses the same record
  def credentials
    @credentials ||= Credentials.new record
  end
end

class Credentials
  def initialize(record)
    @record = record
  end

  attr_reader :record
  private :record

  # Logic for authentication can grow and is cleanly separated from the main user object
  def authenticate(submitted_password)
    return nil unless correct_password? submitted_password
    record_login
    self
  end

  private

  def correct_password?(submitted_password)
    record.password == submitted_password
  end

  def record_login(date_time = DateTime.now)
    self.last_login_at = date_time
  end
end

Testing

Testing records is done in much the same way as you would have tested methods on any ActiveRecord model. The main difference is that there should be much fewer tests and these tests should be simply setting a value and testing the same value is retrieved.

Tests that hit the database are slower because of their interaction with the database. I don’t stub the database out at this level as a record is only about talking to the database. These tests are few enough that slower tests are not a problem.

class UserRecordTests < MiniTest::Test
  # My helper to refresh the database after each test
  include DatabaseTesting

  def test_can_save_first_name
    # create domain object
    first_name = Name.new 'Peter'

    # set record entry
    record = UserRecord.new first_name: first_name

    # assert against domain object
    assert_equal first_name, record.first_name
  end
end

Testing for entities can easily be separated from the database by providing them with a stand in record. My tests all initialize the entity with an openstruct to stand in for the record. There are then two kinds of tests. First setting values on the openstruct and testing the entities behave correctly. Otherwise, manipulating the entity and asserting the correct values are in the openstruct

class UserTest < Minitest::Test
  def record
    @record ||= OpenStruct.new
  end

  def user
    @user = User.new record
  end

  def teardown
    @user = nil
    @record = nil
  end

  def test_user_has_correct_full_name
    # Setup record
    record.first_name = 'Peter'
    record.last_name = 'Saxton'

    # Check behavior
    assert_equal 'Peter Saxton', user.full_name
  end

  def test_user_sets_first_name_on_record
    # Manipulate user
    user.full_name = 'Peter Saxton'

    # Check record
    assert_equal 'Peter', record.first_name
  end

  def test_user_sets_last_name_on_record
    user.full_name = 'Peter Saxton'
    assert_equal 'Saxton', record.last_name
  end
end

Conclusion

This is my take on entities and records. I encourage you to give them a try as multiple entities can be an effective cure for god objects. This is not quite the whole persistence problem solved as we still need to pull the correct records out of our database. That is my next post and it’s on repositories.

Resources