This is the chapter 1 of a series of blog posts about some pieces of advice, tools, and tips you can consider when you arrive to a Rails project considered as Legacy.
Imagine that you landed in a "new" project (new for you, but it’s actually an 8-year-old project). Suddenly you find yourself into a lot of spaghetti code and a monolithic application (which is not bad at all). You can also tell that a small battalion of software engineers has marched through the project.
Has something like this happened to you before? Does it sound familiar?
No worries. In this Approaching Rails Legacy Systems series I'm going to give you hints for when you arrive in a Rails project that can be considered as Legacy.
In this chapter, we take a look at the Project Anatomy. So, it's important for you to understand your project structure, type of files, and more.
What's a Legacy System?
In computing, a legacy system is an old method, technology, computer system, or application program, "of, relating to, or being a previous or outdated computer system," yet still in use. Often referencing a system as "legacy" means that it paved the way for the standards that would follow it. This can also imply that the system is out of date or in need of replacement.
Check your domain knowledge
ls to look for clues on what the domain knowledge looks like:
$ ls Brewfile Rakefile engines Capfile app lib Dockerfile bin log ERP_DEPLOY_RUNBOOK.md circle.yml package.json Gemfile client public Gemfile.lock config script Guardfile config.ru spec Procfile db tmp Procfile_no_npm doc vendor README.md dump.rdb webpack.config.babel.js
As you can see above, we found a
Gemfile. Did you see it? Awesome! Looks like this is a Rails Application, right? Well, then you can see that there is a
Woohoo! There might even be something to test the project. At this point, we are getting some good signs about this "new" app. So. it's time to take a look inside our
app folder and see which other patterns we need to take into consideration or worry about in the future.
$ ls app/ assets mailers pdfs serializers views controllers models presenters services workers helpers overrides repositories uploaders
Oh, I see!,
overrides, so interesting. More things to consider 🤔🤓. I expect you to be taking notes already.
presenter - acts upon the model and the view. It retrieves data from repositories (the model) and formats it for display in the view.
services - service objects that talks to external entities.
Martin Fowler describes a repository as follows:
repositories - A repository performs the tasks of an intermediary between the domain model layers and data mapping, acting in a similar way to a set of domain objects in memory. Client objects declaratively build queries and send them to the repositories for answers. Conceptually, a repository encapsulates a set of objects stored in the database and operations that can be performed on them, providing a way that is closer to the persistence layer. Repositories, also, support the purpose of separating, clearly and in one direction, the dependency between the work domain and the data allocation or mapping.
uploaders - 😱 Something we’ll need to take care in the future.
overrides - 😨 Another pattern that we’ll have to consider later in the future.
It feels like we understand our project better now 🤓, right?
How much of what?
ag to have a quick search and
wc (short for word count) to check how much Ruby code do we have.
So far it looks like Rails and Ruby are the core of this app, but how much Ruby? Let's see:
$ ag -l 'class|module' --ruby | wc 1873 1873 96753
How much Ruby in our
$ ag -l 'class|module' --ruby app lib |wc 1104 1104 48996
Wait a minute, why is this important? Details matter, right? But also the big picture is important, so I think is adequate to have in mind this kind of information to have a general idea of the size of your project.
Now, let's see. How many file types are there?
$ find . -type f \ > | sed -e 's/.*\.//' \ > | sed -e 's/.*\///' \ > | sort | uniq -c | sort -rn| wc 1768 3536 74117
Amazing, so many files to deal with! 🏃 Let's use
head to get the top counts per file type:
$ find . -type f \ > | sed -e 's/.*\.//' \ > | sed -e 's/.*\///' \ > | sort | uniq -c | sort -rn 4687 cache 1544 rb 593 erb 477 png 440 jpg 199 js 180 scss 145 html 124 deface 72 yml 59 svg 53 rake 46 coffee 36 gif 13 pack 13 idx 11 ttf 11 rabl 11 csv 11 css 10 sample 10 prawn 10 ejs 9 woff 9 markdown ...
There are 1544
rb files. 💫💥 And if we look closely, there are 477
png files... Maybe, just maybe, we could consider using an S3 bucket and CDN to solve this. 🍻 Remember, performance is king!!!
tree to look deeper:
$ tree lib/ lib/ ├── 3d_coupon_updater.rb ├── 3d_importer.rb ├── assets ├── data_warehouse │ └── order_field_converter.rb ├── devise │ └── custom_failure.rb ├── errors_app.rb ├── google_tag_manager_data_layer.rb ├── kmts.rb 14 directories, 109 files
The Rails guide says:
app/ - Contains the controllers, models, views, helpers, mailers, channels, jobs and assets for your application. You'll focus on this folder for the remainder of this guide.
lib/ - Extended modules for your application.
Understand your boundaries
One way to help you understand the architecture is to understand the boundaries. Let's start with your Gemfile
$ cat Gemfile source 'https://rubygems.org' gem 'rails', '~> 4.0.13' group :assets do gem 'sass-rails' gem 'coffee-rails', '~> 4.0.1' # TODO: remove all .coffee + this gem gem 'uglifier', '~> 2.5.0' end # Assets gem 'active_model_serializers', '~> 0.9.0.alpha1' # 0.10 introduces breaking changes gem 'autoprefixer-rails', '~> 6.0.3' gem 'bootstrap-sass' gem 'bourbon', '~> 3.2.1' gem 'fancybox2-rails', '~> 0.2.8' gem 'mustache', '~> 0.99.5' gem 'sass-mediaqueries-rails' gem 'ui-rails' gem 'paper_trail', '~> 4.0.0' gem 'css_splitter', github: 'madebymade/css_splitter', branch: 'use-railtie' gem 'route_downcaser' gem 'stringjs-rails', '~> 2.1.0' # small util, mostly for unescapeHTML in cards gem 'jbuilder' # what is this can we remove? gem 'ahoy_matey' gem 'kmts', '~> 2.0.0' gem 'activeuuid', '~> 0.5.0' gem 'ga_cookie_parser' gem 'google-tag-manager-rails' gem 'savon', '~> 2.11.1' gem 'netsuite', '~> 0.4.0' gem 'sift', '~> 1.1.7'
I recommend checking every dependency you don't know much about, look at the documentation of those you don't understand what the hell are doing, and at this point you should have the big picture of your boundaries. Remember your notes.
- Draw how the boundaries look like. Yes, take your notebook and draw:
- You don't need to follow a specific methodology, just draw.
Let's try Rubrowser
Rubrowser is a visualizer for ruby code (rails or otherwise), it analyze your code and extract the modules definitions and used classes/modules and render all these information as a directed force graph using D3. The idea is that the project opens every .rb file and parse it with parser gem then list all modules and classes definitions, and all constants that are listed inside this module/class and link them together.
There are a lot of tools that I enjoy using it. For example, let’s try rails-erd
This creates a quick entity-relationship diagram of your models, but you can also use the database manager of your choice.
So far we have looked at the anatomy of this project, but we still need to go far and beyond!
I hope you find this blogpost useful somehow. In the following chapter, we are going to use GIT as a tool to approach legacy systems and a couple of tools around this distributed version-control system that you might not be aware of.
Thanks for reading!
Oh, before saying goodbye, please share this on Twitter so your peers will know about this Approaching Rails Legacy Systems series:Take a peek at some pieces of advice, tools, and tips you can consider when you arrive to a Rails project considered as Legacy. Click To Tweet