RailsConf 2020 CE

The Circle Of Lifecycle Events

The Circle Of Lifecycle Events

by Nelson Wittwer

In the video titled "The Circle Of Lifecycle Events" by Nelson Wittwer, the speaker discusses the importance and complexity of managing lifecycle events in Rails applications as they grow in scale and complexity. The central theme revolves around how effective handling of lifecycle events can simplify software development while avoiding common pitfalls such as orphaned associations and misfired callbacks.

Key Points Discussed:
- Introduction to Lifecycle Events: Lifecycle events are critical moments in an application's existence, such as user creation, updates, and deletions, that trigger a series of events and callbacks.
- The Joy and Pain of Callbacks: While callbacks are powerful in the Rails framework, they can lead to unexpected behaviors and bugs if not managed carefully. The speaker emphasizes using visual aids and relatable examples to explain complex concepts.
- Prototyping and Rails: Rails is popular among startups due to its rapid prototyping capabilities, particularly through Active Record, which offers simple model associations.
- Better Patterns for Callbacks: A key recommendation is to ensure that callbacks should only modify the model they are defined on. Modifying associated models within a callback can lead to difficulties in maintenance and debugging.
- Use of Active Record Transactions: The speaker explains how to use transactions effectively to manage multiple model changes atomically. If one operation fails, the entire transaction rolls back, leaving the system in a consistent state.
- Service Objects: When lifecycle events become complex or require multiple dependent operations, implementing Service Objects can greatly enhance maintainability and readability. This separates complex logic from the models and controllers.
- Handling Lifecycle Events in Distributed Systems: In distributed systems, using a pub/sub model (via tools like RabbitMQ or Kafka) allows for better management of lifecycle events across different services without creating tight coupling.

Conclusion and Takeaways:
- Successful lifecycle event management requires careful planning and understanding of the Rails framework's capabilities.
- Utilize service objects to encapsulate complex business logic and maintain consistency across model operations.
- Implementing a pub/sub model in distributed architectures helps maintain the single responsibility principle while ensuring resilience against downtime.
- Always validate that callbacks and lifecycle events are managed in a way that doesn't lead to fragile and tightly coupled codebases.
- Nelson Wittwer emphasizes that effective lifecycle management and simplicity in coding can lead to happier development experiences and greater application reliability.

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.