Ivan Nemytchenko

From Rails-way to Modular Architecture

wroc_love.rb 2015

00:00:12.559 So, let me not agree with Peter because, formally, it is the number three, and yes, it’s the hardest one. I hope you had a good party yesterday, but I hope you don’t feel like this guy. So, drink your water or whatever and let’s start.
00:00:28.080 My name is Ivan Nemytchenko, and my Twitter handle is animation. I’m originally from Russia, but for the last five months, I’ve been living in Serbia. I have to say that I found Polish to be much closer to Russian than Serbian. However, guys, you did something wrong with your numbers; it's completely wrong.
00:01:04.320 A little bit about me: I came to Rails from PHP a long time ago. I co-founded two agencies, co-organized two conferences, and did a lot of management work as well. But currently, I’m just a Ruby and Rails developer, working as a freelancer. My biggest contribution to open source is the idea for a gem called Tequila. The idea was to have something like Hummel, but for JSON. Later, the author of Rabl was inspired by this gem and created Rabl, which is now a great tool for that. I hope this counts as a contribution to open source. Anyway, let’s start.
00:01:46.960 I was a Rails developer and was pretty amazed by it when I switched from PHP to Rails. It was kind of simple; you know where to put your stuff, and I was sure that I would follow Rails conventions and apply its best practices. I thought that would be enough to build complex applications.
00:02:07.680 However, from time to time, I noticed a pattern. Imagine this: our application is like Ruby, and Rails is the timeline of our development process. At the beginning, our application is clean and shiny, levitating on Rails, and can go forward very fast. You add some gems and functionality, and it fits perfectly. Your application starts to look even more beautiful and can go forward even faster.
00:02:39.760 But then you keep adding stuff because customers ask for it, and some of those additions don’t fit as well. You might start using some glue and nails to stick things together to make them work together. At this moment, this construction can’t go as fast, and you start to notice it. If you keep doing this, you might find yourself in a situation where you’ve added so much stuff that this construction can’t go forward without very significant effort.
00:04:05.760 Statement number one: knowing how to do it the Rails way is not enough to build really complex web applications. When I realized this, I started to research and explore the subject. I quickly found out all those concepts like SOLID principles, design patterns, and refactoring techniques. While this was definitely useful, I sometimes found myself watching videos from conferences, being amazed by the author’s code and the beauty of it, but not knowing how to apply these concepts in my own code.
00:04:38.560 So, statement number two: knowing about something doesn’t mean knowing how to apply it. This is where my story actually begins. The story begins with a client who came to me with a problem. The problem is very similar to what I showed you in the previous picture. The client has a team stuck on a project, a monolithic Grails application with a MySQL database containing 70 tables.
00:05:45.120 The client decided to split the front end from the back end and already had a team of AngularJS developers. My role was to build an API on Rails. One important thing was that we had to keep the existing database structure because the old application and the new one needed to run in parallel for some time. I liked this idea of separation.
00:07:06.120 When we started to work on the new application, we began with the models. The models can be simple, but when dealing with a legacy database, we needed to add additional attributes and associations to our models. Sometimes, things can get really complicated, but once you overcome that, you can access your data and work with it, and that’s okay.
00:07:55.200 Next, I decided to use Rabl to avoid sending unprepared data and to not replicate the complexity of the database to the client. That worked pretty well, and we were ready to implement some features. Feature number one: user registration. The client gave me a task, and I realized that in our system, we have three different types of users, each with different registration forms, but stored in a single table.
00:09:03.360 This is a common situation, and we had a solution for that in Rails—conditional validations. However, I faced a small problem which was difficult to explain fully. Looking deeper into the issue, I discovered we were going to build a model that knows how the data is saved and how the three different forms should be validated. At that moment, I remembered about SRP (Single Responsibility Principle). This served as a trigger for me to think about using form objects.
00:10:14.640 These form objects fit perfectly into my application. They are simple and almost pure Ruby objects. You only need to include active model validations to be able to validate your data. I used Virtus to make my life easier with type corrections and similar tasks.
00:10:36.080 In the controller, you simply put your parameters into these input objects, check if they’re valid, and create your user record if everything is okay. This approach allowed me to remove validations from my Active Record models completely. It sounds scary to some, but it worked perfectly.
00:11:19.520 The advantage here is that we now have four separate small objects instead of one complex one. However, with this simple approach, you might run into problems when trying to work with nested forms. In those cases, you might want to consider using rare forms or maybe some other solution. For me, this approach worked well.
00:12:47.680 The next feature was a bit more complex: redeeming a bonus code. In this scenario, I received a bonus code that I needed to provide to the system, which should add a certain amount to my account. It sounds simple, but we needed to perform checks to ensure the bonus code exists, hasn't been used, and that the recipient exists.
00:13:30.240 Once everything was confirmed, we processed the request, marked the bonus code as used, and increased the balance. If everything is fine, we return the new balance to the client. However, again you notice a mix of responsibilities: there is business logic and some controller responsibilities, like rendering responses. It was once told to us that we should have skinny controllers and fat models, which led to the question where to put this logic?
00:14:38.320 After some pondering, I realized this is a good case for using a service class or use cases. These might have different names, such as interactors. So you see, our business logic is contained in a Ruby class. If something goes wrong, we raise an exception and, if everything goes well, we return a value. The controller will simply know which use case to call, what values to pass, and how to handle exceptions.
00:16:04.160 Now, looking at the full picture, whenever there is more complex data, it goes to the input class; if it’s not complex, it goes straight to the use case. The use case either raises an exception or returns a value through the Rabl template to the client. We are no longer mixing everything into one place, and this separation is beneficial.
00:17:13.560 That being said, we still had to deal with a legacy database. This database structure forced me to manage additional models for the data. In Rails, your models are just a mapping of tables. But if you think in terms of domain-specific objects, you might notice that these tables don’t make much sense in terms of domain logic.
00:18:02.560 It became clear that I needed to separate my concerns for data persistence using repositories. The idea here is that all persistence logic exists on the repository layer. By doing this, we can create entities that don't concern themselves with persistence logic.
00:18:39.919 In conclusion, the projects I built were liberating because they were not bound to Rails anymore. Additionally, I was able to move some functionality to Sinatra without any pain. I recommend two tools to you: SQL, which is great for learning, and Virtus, which makes life easier with data type corrections. Additionally, I suggest checking out tools like ROM.rb, which can help you map fields without creating your own repositories.
00:19:53.440 Lastly, I found that learning from blogs, specifically those by Arkansas and Adam Hawkins, enriched my development experience. If you have any questions, please feel free to ask. Thank you.