Recently, I’ve had an unfortunate opportunity to deal with a very unexpected issue with Kafka that had quite terrible consequences. The exact origin of the issue is yet to be discovered; nevertheless, the process leading to the final solution to the problem and the fix itself were interesting enough that I decided to write a quick blog post about it as it might be potentially valuable to someone who also encounters a similar problem.
Imagine that you are building a separate application for your e-commerce system dedicated to business intelligence. In other words, you want to calculate some stats for the orders. So you are going to create some new model, like OrderStat(s), and have a separate Postgres database for a new app. Sounds trivial so far.
I’ve been recently planning to record a couple of tutorials for my team in BookingSync, focusing mostly on monitoring and some other more advanced aspects of Kafka and Karafka. But if I was already recording something, then why not make it available publicly so that more developers could benefit from it? So, in the end, I decided to make a bit more effort and recorded this mini-course, which will be available for everyone, for free ;).
If you’ve ever had a need to implement an audit log to track all the changes that get persisted for all or at least some models in your application, there is a good chance that you’ve encountered PaperTrail gem that makes it trivial to track all the changes - it might be as easy as adding has_paper_trail to the desired models.
Imagine that you are implementing a payment processing system for the e-commerce system and discover that multiple customers were charged twice for exactly the same order. Ouch… Sounds like a real nightmare to deal with! And the next day, you see that something is not right with the credits system in which users were able to pay using special credits instead of using credit card - somehow instead of getting charged $100 in total for two orders of $25 and $75, they were charged just 25$! And to make it even more epic, it turned out that the uniqueness validation you added didn’t work at all, and now you have three users with the same email address!
Communication between two or more applications is often everyday stuff, and it might seem that there is not too much to add there as this subject has been covered pretty well in the last years. Thanks to that, multiple patterns and standards have emerged. You no longer need to think about how the response format should look like for your REST API (go with JSONAPI and stick to the conventions) or figure out the authentication/authorization protocol (go with OAuth and the security headaches won’t bother you).
Imagine that you are working on a large legacy application that also contains the dreaded ActiveRecord callbacks in the models handling most of the business logic. At some point, and under a certain level complexity, the mess caused by that choice might become hard to keep under control, the risk of introducing bugs will increase and the teams(s) working on the application will be way less productive. That will most likely lead to an attempt to find a better way of designing the application. The problem, though, might be that the scope of the application is so huge that introducing any meaningful changes to the application will take weeks, if not months.
In the first part of this series, we were exploring some potential options for communication between services - what their advantages and disadvantages are, why HTTP API is not necessarily the best possible choice and suggesting that asynchronous messaging might be a better solution, using, e.g. RabbitMQ and Kafka. We’ve already covered Kafka in the part 2, now it’s the time for RabbitMQ.
In the first part of this series, we were exploring some potential options for communication between services - what their advantages and disadvantages are, why HTTP API is not necessarily the best possible choice and suggesting that asynchronous messaging might be a better solution, using, e.g. RabbitMQ and Kafka. Let’s focus this time entirely on the latter.
Microservices, Service-Oriented Architecture (SOA) and in general, distributed ecosystems, have been on hype in the last several years. And that’s for a good reason! At certain point, The Majestic Monolith “pattern” might start causing issues, both from the purely technical reasons like scalability, tight coupling of the code if you don’t follow Domain-Driven Design or some other practices improving modularity, maintenance overhead, and also from organizational perspective since working in smaller teams on smaller apps is more efficient than working with huge team on an even bigger monolith which suffers from tight coupling and low cohesion. However, this is only true if the overall architecture addresses the potential problems that are common in the micro/macro-services world. One of these problems I would like to focus on is communication between apps and how the data flows between them.
It is sometimes required for the methods with optional arguments to be able to differentiate between its default value and the value passed from the caller. Passing nil might initially sound like a good idea since it represents “nothingness”. However, it might turn out that nil is a legit value and there might be cases where it is desirable for the caller to pass nil. In such a case, we cannot use it as a default value if we want to implement a special logic for the case of not providing that value.
Imagine that you are implementing some form object because you are fed up with treating ActiveRecord models as such, and you need some extra flexibility. You start with a straightforward implementation for a base class of a form object where you can just whitelist attributes. That could look like this:
In the typical Rails application, you can find the most of the validations in the ActiveRecord models, which is nothing surprising - ActiveRecord models are used for multiple things. Whether it is a good thing, or a bad thing (in most cases it’s the latter) deserves a separate book or at least blog post-series as it’s not a simple problem, there is one specific thing that can cause a lot of issues that are difficult to solve and go beyond design decisions and ease of maintenance of the application, something that impacts the behavior of the model - the validations.
Few months ago I wrote a blog post about ActiveRecordbefore_validation callback and how it is used for wrong reasons and concluded that in most cases this is not something we should be using routinely. However, I missed one appropriate use case for it which might be quite common in Rails apps, so this might be an excellent opportunity to get back to before_validation callback and show its other side.
Ruby has quite a lot of “exotic” features that are not used that often, but when you need to utilize some metaprogramming magic, you can easily take advantage of them. One of such features is Object.instance_exec which you might be familiar with if you’ve ever built some more advanced DSL.
Recently, I’ve had quite a popular problem to solve: count the occurences of the given item in a collection. There are few ways to solve this problem - starting from using Enumerable#inject or Enumerable#each_with_object with an empty hash as an accumulator value and writing a code looking like this:
One very useful feature of ActiveRecord is automatically defining attribute readers and writers for all the columns for given tables. For the ones with boolean type, however, there is one more addition - defining an alias of the method with a question mark. Sometimes it might be useful to override this method and add some extra requirements for a given condition. However, this might not be such a good idea.
It’s nothing new that ActiveRecord callbacks are abused in many projects and used for the wrong reasons for many use cases where they can be easily avoided in favor of a much better alternative, like service objects. There is one callback though that is special and quite often used for pretty exotic reasons that have nothing to do with the process when it gets executed - it’s the before_validate callback.
Object#try is quite a commonly used method in Rails applications to cover cases where there is a possibility of dealing with a nil value or to provide flexible interface for handling cases where some kind of object doesn’t necessarily implement given method. Thanks to try, we may avoid getting NoMethodError. So it seems like it’s perfect, right? No NoMethodError exception, no problem?
As hard as it is for me to believe, I already have over 5 years of professional experience in Ruby and Rails. Throughout all these years my attitude towards Rails has been fluctuating between going from blind love to harsh critic (ActiveRecord, I’m looking at you) ending with a bit more balanced but certainly a positive approach. Such time is long enough to have a meaningful opinion about the overall experience using any framework, so here are few points about Rails that I would particularly like to focus on in my reflections.
In the majority of the Rails applications or even Ruby gems you can find a lot of use cases where you need to memoize a result of some computation for performance benefits and to not compute it again if this result has already been computed. Seems like doing the assignment to some instance variable with ||= operator is the most commonly used solution for this purpose, e.g. @result ||= do_some_heavy_computation. However, there are some cases where it might not produce the expected outome and you should actually use defined? operator instead.
Rails 5.0 is without a doubt a great release with plenty of useful changes and additions. The most notable change was probably ActionCable - the layer responsible for integrating your app with websockets. However, there were also other additions that could bring some substantial improvements to your Rails apps, but were a bit outshined by bigger changes. One of such features is Attributes API.
Executing background jobs is quite a common feature in many of the web applications. Switching between different background processing frameworks used to be quite painful as most of them had different API for enqueuing jobs, enqueuing mailers and scheduling jobs. One of the great addition in Rails 4.2 was a solution to this problem: ActiveJob, which provides extra layer on top of background jobs framework and unifies the API regardless of the queue adapter you use. But how exactly does it work? What are the requirements for adding new queue adapters? What kind of API does ActiveJob provide? Let’s dive deep into the codebase and answer these and some other questions.
Every now and then I discover some features in Rails that are not that (arguably) commonly used, but there are some use cases when they turn out to be super useful and the best tool for the job. One of them would definitely be a nice addition to ActiveRecord::QueryMethods - extending method. Let’s see how it could be used in the Rails apps.
Have you ever wondered how it is possible that calling class methods on mailers work in Rails, even though you only define some instance methods in those classes? It seems like it’s quite common question, especially when you see the mailers in action for the first time. Apparently, there is some Ruby "magic" involved here, so let’s try to decode it and check what happens under the hood.
After publishing recent blog posts about table partitioning - its SQL basics part and how to use in in Rails application I was asked quite a few times what is the real performance gain when using table partitioning. This is a great question, so let's answer it by performing some benchmarks.
In the previous blog post we learned some basics about table partitioning: how it works and what kind of problems it solves. So far we've been discussing mostly basic concepts with raw SQL examples. But the essential question in our case would be: how to make it work inside Rails application then? Let's see what we can do about it.
You've probably heard many times that the database is the bottleneck of many web applications. This isn't necessarily true. Often it happens that some heavy queries can be substiantially optimized, making them really efficient and fast. As the time passes by, however, the data can remarkably grow in size, especially in several years time which can indeed make the database a bottleneck - the tables are huge and don't fit into memory any longer, the indexes are even bigger making queries much slower and you can't really optimize further any query. In many cases deleting old records that are no longer used is not an option - they still can have some value, e.g. for data analysis or statystical purposes. Fortunately, it's not a dead end. You can make your database performance great again with a new secret weapon: table partitioning.
If you happen to develop API for non-trivial app with complex business logic beyond CRUD directly mapped to the database tables (i.e. typical Active Record pattern) you were probably wondering many times how to handle these cases and what's the best way to do it. There are quite a few solutions to this problem: you could add another endpoint for handling given use case, add non-RESTful action to already existing endpoint or you can add a magic param to the payload that would force non-standard scenario in the API. Let's take a closer look at the these solutions and discuss some advantages and disadvantages of each of them.
Having some kind of type attribute in your models is a pretty common thing, especially in applications with more complex domain. How do you handle such cases? Is it with multiple conditionals / case statements in every method dealing with the type attribute? If you've been struggling with organizing and maintaing code for such use cases then say hello to your new friend: type delegation.
Recently I've been repeatedly asked how to get started with programming, especially Ruby and Rails. To keep things DRY and make sure I always include all great resources for learning, I decided to write this blog post. Most of people who asked me had never programmed before or had done some coding before in other languages, but are not (yet) proficient developers, so if you are a total beginner or just started learning Ruby / Rails and you are not sure what the next step is then you came to the right place.
Server setup with the entire environment for Rails applications can be quite tricky, especially when you do it for the first time. Here is step by step guide how to setup CentOS 6.4 server with a basic environment for deploying Rails applications. I encourage you to choose CentOS Linux - it is a reliable distro (well, based on Red Hat Enterprise Linux), easy to handle and doesn't require advanced Unix knowledge like Gentoo (especially while updating system related stuff).