00:00:09.270
Welcome everyone to the next session of Rails Conference Remote Edition 2020. I will be your host for this next talk about lifecycle events.
00:00:14.530
The topic is 'The Circle of Lifecycle Events' by Nelson Wittwer.
00:00:20.890
A little about myself: I currently work at a company called MX, where we write banking software, APIs, web apps, and mobile apps for banks, credit unions, and fintechs.
00:00:27.460
We serve some of the biggest names in those categories and currently have over 20 million users, growing rapidly, even in today's environment.
00:00:36.309
It is great to work in a scalable Rails environment, and I am grateful for the opportunity to explore how far Rails can leverage itself.
00:00:48.910
I dislike the memes that claim Rails doesn't scale, because I can assure you, it does. I've also worked with Rails in many startups; I've tried my hand at the startup lottery a couple of times but haven't yet had a major success.
00:01:11.890
So, what is a lifecycle event? Returning to the topic of our talk, rather than diving straight into technical jargon, I will utilize visual aids and memes.
00:01:17.340
As a visual learner, I find that familiar concepts are a great way to explain more complex subjects.
00:01:32.350
If you weren't able to pick up the clues from that video, let me explain.
00:01:38.500
When something happens in your platform, such as creating a new user, there's likely a chain of events you want to trigger within your application.
00:01:43.869
For example, events can happen on user creation, update, or destruction. There are important actions you'll typically want to perform in response to those events.
00:01:54.850
Lifecycle events can be incredibly powerful and essential to get right. However, they are also one of the easiest ways to cause issues in your application.
00:02:11.400
I thought I would share some of my experiences at both large scale with millions of users and at the smaller scale of trying to get my startup off the ground.
00:02:29.700
Rails has a reputation as the go-to framework for startups, and there are a couple of reasons for this.
00:02:35.670
Firstly, it is very easy to prototype. You can quickly run 'rails new' and create a basic application in no time.
00:02:54.840
In particular, the backend layer of Rails pairs well with new products being prototyped or iterated on. The speed of development is facilitated by Active Record, which simplifies relational database management.
00:03:21.000
Active Record’s model associations make it easy to tie related data together, for example, a blog post can have many comments or an author can write a blog post.
00:03:43.650
This makes building beautiful views on top of these associations simple and enjoyable. As you start expanding your application's features, writing callbacks that trigger during lifecycle events of those models becomes crucial.
00:04:08.010
In my startup experience, those two tools and a user-friendly interface performed most of the heavy lifting. Instead of dealing with abstract concepts, I want to pitch you my next startup idea, which is going to be huge.
00:04:40.590
I’m calling it blockchain credit, with credit scores on the blockchain. Because why not? In 2020, we should absolutely have peer-verified credit reporting!
00:05:02.000
And if that wasn't enough, we will incorporate machine learning and AI, so it’s already a unicorn! You have to get in on the ground level while you still can.
00:05:39.960
But wait, there's one more thing I can do to simplify this pitch. Instead of calling it blockchain credit, let’s just drop that and clean it up.
00:05:50.880
Now, when users sign up for our platform, we’ll need a few things to happen: We need to create a credit score for the user because what’s the point of a blockchain without something to score?
00:06:19.200
We also want to create an alert profile to track user preferences on how they receive alerts—whether by email, text message, tweet, or TikTok. And of course, we want to send a welcome email.
00:06:34.900
Let's review how this user creation flow might work. An administrator on our platform in Rails wants to create a new user. We can create the user, establish a profile, generate a credit score, and send the welcome email.
00:06:52.890
This flow is reasonable for a web application. But what about all these steps? Where’s the chapter in the Rails tutorial that covers those?
00:07:06.760
This is where we introduce ActiveSupport callbacks. They can appear complicated, but they provide a simple way to tie lifecycle events with model interactions.
00:07:39.880
My naive implementation in early startup days might look something like this: After creating a user, we would create their alert profile, send a welcome email, and generate a credit score. Easy peasy!
00:08:06.910
If this is your first encounter with Rails callbacks and lifecycle events, I want to discuss documentation briefly.
00:08:19.200
The first two steps here are the functions that trigger the callbacks underneath them. For instance, if we say 'user.save', that initiates a sequence of callbacks.
00:08:37.360
After validation is complete, we often deal with 'before_save' and 'before_create', which are triggered before we save a new record. These callbacks are frequently more common and useful.
00:09:02.860
The purpose of 'before_create' could include logging or auditing who created the user or gathering additional information. These lifecycle events are significant, as they can impact user interactions and outcomes.
00:09:43.810
After creating something, we may notify sales channels of a new user signup. These represent several practical applications of callbacks where one can define any custom logic at various stages of the lifecycle.
00:10:01.480
The pros of using callbacks are that they are easy to use, work well with validations, and can be tested in isolation. Moreover, they run in all database operations within Active Record.
00:10:22.460
However, the downside is that they run every single time, which can create undesirable behaviors in cases like bulk imports where actions can cause unintended consequences.
00:10:53.680
Callbacks can get complex, especially when relying on certain callbacks to execute in an expected order. Issues can arise from race conditions and complicated conditional logic.
00:11:17.490
To summarize, callbacks should only modify the model they are defined on. Creating associated records within a callback leads to potential future debugging problems.
00:12:04.770
To visualize this, Active Record stands atop a relational database, managing data separately. If callbacks modify multiple associated models, they create intertwined responsibilities.
00:12:17.050
This defeats the purpose of clearly defined models. Each model should manage its state, not its associations. When callbacks improperly intertwine responsibilities, it cultivates challenging debugging scenarios.
00:12:44.930
Additionally, consider scenarios where needed behavior varies across user types or specific cases. Complex callbacks can lead to significant code maintainability issues, leading to unnecessary complexity.
00:13:04.810
When discussing user creation, the single responsibility principle is critical. That means a callback should only alter its associated model, not others.
00:13:41.010
Callbacks should solely enable modifications to the model they are attached to, which helps maintain clean application logic.
00:13:53.310
The primary takeaway is that calling associated records within a callback can create a convoluted code structure that generates conflicts and eventual bugs.
00:14:06.570
Instead, use read-only associations within callbacks. You can check the status of associated metrics without modifying them.
00:14:35.700
For example, checking a user’s credit score status after a transaction assists in decision-making without altering that score directly.
00:15:09.900
This allows for conditional checks while maintaining associations cleanly. Much of this logic finds application within the lifecycle of user interactions.
00:15:51.900
Consider the implications of updating a credit score upon user transactions. Allowing callbacks to determine new scores based on changing user profiles can lead to messy and inconsistent results.
00:16:18.370
For every level of complexity you introduce, you foster debugging challenges, especially when tracking the flow of logic.
00:16:35.260
Thus far, we acknowledge that callbacks should directly modify the model they correspond with. Unbounded logic can complicate the management of various actions across your application.
00:17:04.630
As a solution, leverage Active Record transactions to maintain dependencies between models during lifecycle events. The transaction blocks help precondition all changes to holistic correctness.
00:17:42.400
This means if creating an associated model fails, your entire action can roll back, preserving data integrity.
00:18:02.600
Ideally, throughout this example, users should not exist within your platform without necessary dependencies, such as alert profiles.
00:18:25.830
Consider a user signing up through a Rails application, subsequently creating related models. Active Record transactions ensure all dependencies exist or none do.
00:18:51.510
This balance upholds user data hygiene regarding credit scores and alert notifications.
00:19:08.500
Typically, encapsulated logic resides within service objects that manage user lifecycle events, resolving intricate relationships.
00:19:33.430
The service object design pattern promotes segregating logic that doesn't fit well into the CRUD model.
00:19:48.630
Utilizing service objects, you can encapsulate complex workflows, validating and processing user registration.
00:20:08.730
In this example, service objects could streamline user registrations by gathering input and triggering the compound processing step.
00:20:22.600
By maintaining a clear boundary for registration logic, you facilitate ease of testing and functionality iterations.
00:20:59.030
Moving forward, you can enforce that all related records exist without tying user logic throughout its callbacks, failing to address the separation of responsibilities.
00:21:40.520
Throughout our example, we're maintaining our single responsibility relationship for each model while enabling complex behavior mapping through service objects.
00:22:14.790
We are not modifying models directly through callbacks, instead approaching this through dedicated service classes, ensuring maintainability.
00:22:41.880
By placing these workflows in service objects or procedural style approaches, we can maintain flexible registrations while reusing logic across programs.
00:23:16.270
Augmenting APIs also retains integrity while enabling versions for existing registrations, thereby allowing improvements without disrupting existing clients.
00:23:58.770
To reiterate, managing the complex behavior of lifecycle events effectively entails crafting code within transactions rather than callbacks without proper context.
00:24:38.760
Distributed architectures impose new challenges wherein lifecycle management occurs in microservices, elucidating the need to focus on reliability with associated services.
00:25:24.450
In distributed environments, relinquishing control over the timing and order of message processing is essential. By sending lifecycle messages, we allow other services to determine their state based on received updates.
00:26:12.370
Platforms like RabbitMQ or Kafka enable seamless integration and messaging, ensuring vital functions persist independently, even under failure conditions. This segmentation fosters resiliency, allowing systems to recover efficiently.
00:27:01.250
By standardizing message structures through JSON payloads, services can effectively respond to user events regardless of their operational status.
00:27:53.390
In this way, various systems operate autonomously, enhancing their functionalities universally while contributing to the overall lifecycle management paradigm.
00:28:40.530
However, challenges persist regarding data synchronization within interconnected models, which may require browser robust logic to maintain consistency.
00:29:38.580
Effectively, decoupled messaging requires conscious planning, requiring consumers to be idempotent—to ensure consistency through repeated message processing.
00:30:03.480
Moreover, meticulous thought must be devoted to message flows where transactional data consistency plays a role in lifecycle event handling.
00:30:29.990
Ultimately, maintaining services in a decoupled environment can be rewarding, enhancing reliability across systems while preserving the independence of functional boundaries.
00:31:36.730
In summation, lifecycle management requires intentional coding of lifecycle events—whether through callbacks, service objects, or pub/sub messaging strategies.
00:32:01.040
Callbacks are invaluable for setting straightforward fields but should not modify associated records, and Active Record transactions can ensure cohesive state management without interdependency.
00:32:45.760
Service objects serve as the bridge for complex logic, validating and processing user lifecycles as necessary, ensuring that business rules are aptly adhered to in your application.
00:33:33.270
Additionally, distributed messaging in microservices architectures requires planning around consistency, ensuring that systems effectively decouple and react independently.
00:34:34.870
This setup allows flexibility while enabling version control across user lifecycles while alleviating some complexity typically lodged within monolithic architectures.
00:35:17.920
We've tackled significant themes today and navigated substantial information related to lifecycle event control in applications. Thank you all for your time!
00:35:41.740
If you enjoyed this presentation or laughter during this, please reach out to me on Twitter. I'm grateful for the positivity and support from the Rails community that has shaped my career.
00:36:03.560
I appreciate everyone flying with us through this session. Thank you again, and I will see you next time.