KM SOFT

Appartment renting platform

Implementing features, maintaining, refactoring

Duration: 2016.09 - 2018.08 (2 years)
Technologies: Ruby, Rails, MySQL, JavaScript, React, HTML, CSS
Methodology: Scrum

Overview

As a software engineer at TheMasters software house, I was assigned to work with Wimdu, one of our prominent clients. My role as a Ruby on Rails developer involved collaboration with Wimdu’s Berlin-based development team. Although I primarily worked remotely from Wrocław, I visited the Berlin several times for direct team engagements.

This project was my first experience with a fully implemented Scrum framework, including structured sprints, planning poker planning sessions, retros, etc.

The project had its challenges, particularly in terms of leadership, as the team had recently lost some of its most experienced members. Also initially our team didn’t have a dedicated QA role. I advocated for integrating quality assurance into our process, which resulted hiring a dedicated QA person.

Maintaining an Ruby on Rails monolith

Our primary objective was the maintenance and enhancement of a Ruby on Rails monolith. My contributions spanned several areas such as: improving dates parsing, implementing 3D secure to the payment processes, optimizing database queries, integrating with external offer provider via REST API, and improving input params validations — the latter deserving further detail.

The application suffered due to poor input params validation.

The project was build initially as a classic Rails Way MVC:

Rails MVC
Rails MVC

With most of the logic concentrated within the model (in Rails Way the model is an ActiveRecord object, vs DDD model separates concerns into few building blocks, such as: entities, value object, aggregates, repositories, factories, services). However, this approach struggled to scale. To mitigate this, the team began transitioning some logic into includable modules (mix-ins). Yet, this solution does not provide encapsulation (the code is just moved to other files), and it also degenerate clarity, as as the class’ methods and the mix-ins can overwrite themselves).

Before I’ve joined, the team have already made much refactoring efforts. The architecture was already a pretty complex monolith in which the struggles to modularize could be seen. Some logic was organized into Plain Old Ruby Objects (POROs) within namespaces. Also the patterns such as service objects, value objects, and presenters were used (at the time this article had some influence).

And so a typical request would look like:

Rails MVC with service objects
Rails MVC with service objects

For example, ServiceA might handle fraud checks, while ServiceB could register a user. Although the architecture wasn’t perfect, some parts were reasonably structured.

From my perspective, a critical area that was still lacking was input validation. In previous projects (whether in PHP or Rails applications using Grape) I tend to validate params at the application boundary as soon as they were received (Grape includes a built-in validation layer even before the controller). BTW, this approach is also mentioned in the article as form objects. But the form objects weren’t adopted. Some parts of the application used ActiveInteraction, which includes a validation layer before processing data, but it wasn’t common. Typically the controller would call params.permit, and pass the params to services, allowing poorly validated data to propagate through the system, leading to various problems and frequent application crashes at unexpected points. params.permit also does not do any coercion and so each service would need to do it’s own coercion on the params hash (hard to maintain, and developers often forgot to do this).

My idea was to add a thin layer of data validation and coercion (ActiveInteraction overkill, dry-validation better):

Rails MVC with service objects should validate params as they enter the controller (validation in AR models is too late)
Rails MVC with service objects should validate params as they enter the controller (validation in AR models is too late)

This validation layer would prevent invalid data from propagating through the service layers, thereby improving the reliability of the application. I advocated for the implementation on the crucial controllers, which we did and the new controller were also build this way.

BTW this practice is also useful when it goes for processing the query parameters, such as those used in search results. See my notes on the topic.

Big refactor

And I also participated in a big refactor of already existing checkout page functionality. Which was a bad ides, never finished, bad experience.

Designing and implementing a microservice

At some point, the organization needed another application to store and moderate a sensitive personal data for a KYC process. I was asked to design the application and lead the implementation process. The application was an admin panel for moderators with a REST API for communication with the main application.

I designed the architecture using Ruby on Rails for the backend, Bootstrap 4 for the front end, and PostgreSQL for the database. We (me + 2 other developers) utilized dry-validation and other dry-rb libraries to ensure robust data handling and validation. For API documentation, we used rspec-rails-swagger (Grape would have been a better choice due to its superior documentation generation). I was against the use of the ActiveAdmin gem (it would cause legacy code and hacks too soon). After some time, we were tasked with implementing an additional feature to log data access, specifically tracking who viewed the data and when.

Summary

Working on this Rails monolith taught me a great deal about the architecture limitations of Rails in general. It motivated me to explore better design patterns. My research led me to adopt elements of DDD (ddd-lite), dry-rb and, later, The Doctrine of Useful Objects.

Fat model, skinny controller - dangerous concept if implemented in Rails Way, that is where model is an ActiveRecord object (mix-ins also count as part of the object). Compared to non Rails Way model as in DDD (eg. PHP Symfony framework) where model is build from many building blocks such as entities, value object, repositories, factories, services; with separation of concerns.