March 29, 2018
This is a story of how a day-to-day developer overcame his fears and came out of his comfort zone. Of how he and his team got into a project which code was so bad they cried a little when they saw it for the first time. Of how they took care of it and made it great. Of how they made clients happy and left the whole team with a “mission accomplished” feeling. Of how they finally succeeded.
Several months ago, Magda (my dev colleague) and I were informed about a new project starting in our company. That’s always exciting. We were supposed to dive into it and analyze its primary requirements. We learned that the client requested implementation of two main features into the product—payments and some kind of analytics. Enough to compliment the MVP and making it production-ready. Easy-peasy, we thought.
The reality was much, much darker… The existing styles were completely off and everything was overlapping. Server logs were stored in the repository. Data of some views were hardcoded. We even found a method 73-lines-long, where the 10-15 is a good practice. Login form was accepting email and password but the controller was only fetching User by the former and creating a session. Passwords were stored as plain-text.
We came to a decision—we didn’t want to implement any new features in the application so harmful to end-users. There was only one thing we could have done: perform an audit and summarize it in an accessible way to our client suggesting improvements to the existing product. It was, in a way, a whole new experience for me, but with a support of my colleagues completely feasible.
We divided our work and document into three parts:
A lot of good came out of this.
Conducting the audit helped us quickly understand most of the business logic behind the project. We could also provide our client with a complete report of existing problems and prepare a plan to kickstart the implementation using good practices and new patterns.
Based on audit’s outcome together with the client, we decided to rewrite the app from scratch. As client spent some time and money on the project already we had to make it as efficiently as possible to catch up.
As initially, I was alone in the project for some time, we went with the easiest setup to minimize the risk of mistakes. Basic Rails app (non-SPA) for backend + Webpacker with Vue as our front-end library. We use standard Rails controllers and server-side rendering for static data. Vue for reusable parts (components!) with Ajax calls to API in more interactive parts of the app. After several months of development, I must say it works pretty great.
Along the way, the project gained one BE developer, one FE developer, a QA expert, and a designer.
Client clearly saw that initially broken project can be reforged to something great. The product started to look good both visually and code-wisely. Thanks to our weekly in-house “Software Architecture” meetings and following SOLID principles, we were able to introduce some neat patterns to our app. Goodbye, bloated controllers and models! Goodbye services
catalog with over 9000 do_everything.rb
files! Welcome, use cases and repositories! Welcome new abstraction layers, single responsibilities, and cleaner code!
Repositories helped us move all ActiveRecord calls into single files (per model or context).
# repository
class UserRepository < BaseRepository
class << self
def scheduled_emails
# ActiveRecord or SQL calls to retrieve some data
end
end
end
# usage in code
scheduled_emails = UserRepository.scheduled_emails
Benefits? Thin models. Reusable code.
Every time we need to change some returned data, we go to one place. Every time we find a nasty N+1 query, we change it in one place. That also made writing tests a lot easier. We only check if a repo received a call. The only specs that hit the database are those for repos.
# code
scheduled_emails = UserRepository.scheduled_emails
# spec
it { expect(user_repo).to have_received(:scheduled_emails) }
This sped up our tests. A lot.
I know that this is not exactly repository pattern “by the book”. That they should interact with the database directly. Here, it’s still model’s responsibility. Actually, those repositories are only another layer on ActiveRecord. But it will be easier to come back to them, get rid of AR (or even Models entirely?) and use just the adapter as we’re not accessing DB in any other place. It’s never too late to learn and try new things!
As for use cases - those are our layer of abstraction between data access and representation. A clear description of user’s tasks.
Benefits? Thin models and controllers. Code organized in a way when someone would look at our use_case
catalog he could probably describe wireframe of our business logic.
# use case
module UseCase
module Email
class Update
def initialize(params, callbacks)
# initialize params
end
def call
return callbacks[:success].call if email_updated?
callbacks[:error].call(emails_errors)
end
private
# other needed methdos
end
end
end
# controller
def update
success = -> { redirect_to emails_path, notice: “Yay” }
error = ->(error_message) { redirect_to emails_path, alert: error_message }
UseCase::Email::Update.new(email_params, success: success, error: error).call
end
Again, testing is easier as in controllers' specs we only check if UseCase got called. In use cases' specs, we only check if repos got called and... the rest you know. Fast and clean!
I had little experience with performing the first release. In the past, I had been either working on an existing projects or with technical clients who would take care of it themselves. Here—no such case. As the project was going pretty fast, we decided to host staging and preprod environments on Heroku. Production was supposed to reside on AWS.
So I went: into the AWS console, created a domain on Route53, new EC2 instance. Logged in through SSH and set up the environment using some step by step guides from the web. Fired up Capistrano docs (as this was the solution I knew from past projects) and started writing proper scripts to deploy the master branch from GitHub. Aaand... it worked!
What I didn’t know then, the setup was so prone to failures… it failed.
There was no monitoring, no automatic services restarts, everything was set up on a single instance so no horizontal scaling.
I reached out to our ops guys and they introduced me to a brave new world of infrastructure as code. I won’t delve into the topic as Jan already discussed Terraform remote backend problem, but three good things came out of this:
You know how all that god-forbid-long motivational posts can be summed up with just do it? Well, I’m not gonna do that to you. You see
I never asked for this.
— Adam Jensen
But it was so worth it.
In my career as a web developer, I was mainly orbiting around my own pull requests. Sure, I was active in meetings, making suggestions, and code reviews. I always cared about the projects I was involved with doing a good job. But bigger responsibility has never laid on my shoulders. I was comfortable where I was.
Until this one. When Magda was pulled out to another project, I naturally became a tech-lead. I could decide what technologies we’re gonna use, choose libraries and patterns. I played it safe on some fields (setup) but also had a chance to try new things (mainly with software architecture). I did make some mistakes and was stressed sometimes. I know I WILL make new mistakes but what I learned is mine (and now yours too). 🎉
architecture as code
scripts.
Act Four—See You On The Flip Side
Two days after project freezing to stabilize, increase the user base and find investors I was suggested to write about it. About what I learned from the experience. The post, I think, came out a little chaotic (as the whole adventure was!) but that’s actually another thing I’m learning here—to write such blog posts.
Personally, the project was a combination of “first times” for me.
First time writing an audit.
First time performing the whole-project estimation.
First time choosing all technologies.
First time leading a project (a lot of prioritizing, compromises, creating technical debt, reducing technical debt).
First time applying new patterns from the ground up.
The first release of a whole new app.
First major failure (and many minors).
First post-mortem.
First one-to-one calls with a non-tech client.
This chapter ends here. The things I learned are invaluable but the most important is the fact that the app is live and gaining users every day. The fact that client used word “encouraging” so many times when going through the MVP it made us fly. The fact that we succeeded 🎉.
Node.js Development PHP JavaScript
Why Node.js Developers Are in Higher Demand than PHP Programmers