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.
Image courtesy of nginx.com
In this diagram, each component is separate as they have separate roles:
- Load balancers
- Front-facing web servers
- Application servers
- Database servers
- In-memory data stores
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:
ApplicationRecordand lives in
ApplicationControllerand lives in
ApplicationMailerand lives in
ApplicationJoband lives in
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:
ActiveRecord::Base, you lose
ActiveRecordand its functionality
ActionController::Base, you lose
ActionControllerand its functionality
ActionMailer::Base, you lose
ApplicationMailerand its functionality
ActiveJob::Base, you lose
ActiveJoband its functionality
Rails sets up this composition for you, assuming that this base class will contain common functionality used throughout each subclass:
ActiveRecordassumes that an application will have validations on most of its subclasses, so it exposes
- Devise hooks into
ActionControllerand gives you things like
ActionMailerexposes things like
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:
- Artist Dashboard
- User Account Interface
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
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.