Designing a Monolith: Understanding Code Architecture (Part One)

When web developers hear the word architecture, more often than not they first think of a server setup.

Server architecture example
Image courtesy of nginx.com

In this diagram, each component is separate as they have separate roles:

As your application scales, these services be scaled independently of each other. Your application may have a larger database instance and a smaller Redis instance. It may have multiple Redis instances for different components of the application that scale differently than the rest of the application as well.

How does this relate to your application?

Out of the box, Rails treats its each part of the MVC stack the same way, providing you with a base class to inherit from:

Rails does a really good job at providing these conventions out of the box. They are pieces of architecture as they will be long-lived bits of core code. Without them, you lose a core feature:

Rails sets up this composition for you, assuming that this base class will contain common functionality used throughout each subclass:

This method of composition is a critical part of developing architecture for your application.

As your application scales, it develops its own patterns. Some of the common ones in Rails applications include decorators for handling view logic, serializers for building objects used directly in your response body, and a service layer for handling extraneous business logic. Rails makes it easy for a developer to put this logic in our app folder and have it automatically be available to the application provided it is named according to its expected naming conventions.

Because we give special treatment to each component of our code architecture with its own folder, why don’t we do the same with core features that lay on top of our traditional MVC stack?

I bet you already do this in at least one feature of your application: the admin dashboard.

If you’re Spotify, this is can be:

The Artist Dashboard is always going to call current_artist. The User Account Interface is always going to call current_user. These features have no reason to share a namespace.

For some reason, this doesn’t sit well with me:

class ArtistsSettingsController < ApplicationController;
class UsersSettingsController < ApplicationController;

Instead, I prefer:

module ArtistDashboard
  class SettingsController < ApplicationController; end
end

module UserDashboard
  class SettingsController < ApplicationController; end
end

Or even better:

module ArtistDashboard
  class BaseController < ApplicationController; end 
  class SettingsController < BaseController; end
end

module UserDashboard
  class BaseController < ApplicationController; end 
  class SettingsController < BaseController; end
end

This doesn’t stop at controllers, either. This is applied to each component of these features that are specific to just them: the mdoels, the mailers, the serializers, the objects in the service layer, all the concerns and everything in between.

The idea is that if one day we need to remove this feature, it’s a clean rm -rf and some manual cleanup instead of hunting down files.

As a developer, this reduces cognitive overhead because all the important parts of these individual features live next to each other. If they don’t, it’s a core concept like a User. It encourages separation of concerns via directory structure and it’s cost is a mere mkdir.

Thinking about features in terms of pieces of architecture makes an application easier to maintain, iterate upon, and test. While it requires prior preparation, the cost of doing so is low in comparison to long-term sustainability.

In part two, I’ll start deconstructing Spotify and implementing it as if it were a Rails application, following the same ideas as discussed here.