List

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.

The Circle Of Lifecycle Events by Nelson Wittwer

"To the new software developer wanting to build a web app with Rails, callbacks and associations are gifts from the gods. As your application grows in complexity however, you may notice your favorite tools have actually turned on you. New users suddenly aren't getting welcome emails and you've somehow orphaned associations. Come learn proven patterns for managing lifecycle events for associated records in a way that sparks joy."

__________

Nelson has spent most of his career working within Rails typically building new startups/products but has also maintained and iterated on platforms at scale supporting tens of millions of users. Nelson is a personable guy who loves to make people laugh. Nelson currently lives in the Raleigh/Durham area and works for MX.com building banking/fintech products.

RailsConf 2020 CE

00:00:09.270 welcome everyone to the next session of rails conference remote edition 2020 I
00:00:14.530 will be your host for this next talk about life cycle events the circle of life cycle events by nelson Bradley
00:00:20.890 would were a little about myself Who am I I currently work at a company called
00:00:27.460 MX Weser we write banking software api's web apps mobile apps for banks credit
00:00:36.309 unions and fintechs so we serve the biggest names and in all of those categories have over 20 million users at
00:00:42.579 the moment and growing like mad even with the the world as it is the way that
00:00:48.910 it is today with the virus and all of that it's it's great to work in an environment of rails of scale and I'm
00:00:54.850 really grateful for the opportunity to see how far out rails can leverage
00:01:00.100 itself I hate all the memes that say the rails don't scale because I tell you
00:01:05.110 they do that it does so I've also worked at rails in a lot of startups so I tried my hand at the startup lottery a couple
00:01:11.890 times haven't have yet to really cash out him on that sucker yet but I believe
00:01:17.340 so what is a life cycle event and in going back to the topic of our talk
00:01:25.180 today right rather than I think just diving into a bunch of technical jargon I'm a really visual learner so you're
00:01:32.350 gonna use me see me use a lot of visual aids memes and the like as I as I feel
00:01:38.500 that it's a great way to take something that's familiar to to explain a concept
00:01:43.869 complex subject
00:02:04.140 let me explain so when something happens in your platform a new user is created Simba is born there is likely a chain of
00:02:11.400 events that you want to write sit within your application for something to happen okay so these things could happen on a
00:02:16.830 user create on an updated to destroy there's likely some things that you'll you were writing your software based off
00:02:23.460 of those events and these life cycle events are a super powerful and important thing to get right and it's
00:02:29.700 also one of the easiest things to shoot yourself in the foot within my experience so I thought I would share
00:02:35.670 some of my experiences both on a scale on the millions and scale of the I'm trying to get my start up off the ground
00:02:41.430 how does this work a type of contexts speaking of startups so who rails has this brand of being
00:02:49.350 like the go-to framework in startups even today years and years after its
00:02:54.840 initial launch and there's a couple of reasons why startups in particular gravitate towards rails it's very easy
00:03:02.459 to prototype on something you can open run rails new and run a couple scaffolds
00:03:07.800 and have a very basic up and running very quickly but there in
00:03:13.860 specific there's two things that I think that really help the backend layer of rails marry super well with a brand new
00:03:21.000 product that you're trying to prototype a release or iterate on and it's it's
00:03:26.190 what makes it very fast to develop with and that's the way that rails sits on
00:03:31.560 top of postgrads any sort of relational database today the active record can tie
00:03:37.080 into the way that it super simply handles model associations so a blog
00:03:43.650 post can have many comments right or an author can write a blog post and so you
00:03:49.470 can have these these relational models that are super easy to work with tie
00:03:54.989 together and draw associations with and build a beautiful view on top of which I
00:04:01.440 super love and then tied within here as you start to expand on the behavior of
00:04:08.010 your applications writing callbacks that that trigger during a lifecycle event of any
00:04:13.590 of those models is a super powerful way to build build an app and in fact that's what back in my in my startup life those
00:04:22.680 two tools and a super simple UI did most
00:04:27.780 of the heavy lift lifting so I thought I would rather than dealing it with in the
00:04:33.300 abstract diving into the content of this talk my favorite my favorite tech talks are the ones that have a shared domain a
00:04:40.590 shared problem that we're trying to solve so since I have talked about my startup experience before I thought I
00:04:47.100 would you know pitch you my idea here so let me pitch you my next startup it's
00:04:53.640 gonna be huge and I think you're gonna love it I'm gonna call it the blockchain credit
00:05:02.000 or did you credit score's on the blockchain because why not it's 2020 we
00:05:08.730 should absolutely have credit reporting with on a blockchain that is peer verified so and if that wasn't enough
00:05:16.680 for you we're also gonna do machine learning and there's gonna be a hell of a lot of AI in this thing so we're good
00:05:22.470 it's it's a unicorn already you've gotta so you got to get on the ground level while you still can right but you know
00:05:27.480 what there's one more thing I can do to simplify this pitch you know what you know what yeah just just drop to the blockchain credit I think it's a lot
00:05:33.720 cleaner okay so it's here's how I'm
00:05:39.960 gonna pitch the technical side of use of this to you so there's a couple of things that we want to have happen when
00:05:45.090 users sign up for our platform so number one we would need to create a credit score for this user because what's the
00:05:50.880 point of having you having the blockchain if there's nothing to score the test or honor right we want to
00:05:56.310 create an alert profile so something to track hey do I prefer alerts on my email for my credit report do I want to in
00:06:03.090 text message a tweet I take talk or whatever right and then we want to send
00:06:08.880 a welcome email welcome to the beautiful land of blockchain credit very reasonable expectations right so let's
00:06:16.380 take a look at how this flow might work so let's say we have an administrator in our in our platform in rails and they
00:06:22.890 want to create a new user okay that's pretty simple we can create one user but created a profile created
00:06:29.050 create a credit score and then we're gonna return that back and and we're also in a set of this welcome email like
00:06:34.900 right like that flow very reasonable for a web application so if I were to spin
00:06:40.720 up a rail scaffold that part's pretty simple but what about all these steps
00:06:47.500 right here what were those go where is the chapter on the rails tutorial about that right and that's where we introduce
00:06:54.370 our first actor activesupport callbacks
00:07:21.780 looks like a Christmas tree this is how
00:07:27.970 I felt about it like I said you take you take models and you give me a Z way to
00:07:32.980 tie into all those lifecycle events that's that's all I need to need to flush up my ideas and in our a little
00:07:39.880 startup in our in a rails app here it's gonna be no different so are not my
00:07:45.490 naive implementation in my early startup days might look something like this I have a user it's going to have a couple
00:07:54.190 of things that take place after we create so after I'm created user we're going to create our alert profile we're
00:07:59.230 gonna send a welcome email and our credit score and we are done right easy peasy so if this is your first stop in rails
00:08:06.910 callbacks and lifecycle events I thought I would take a quick second to dive a
00:08:12.010 little bit into the documentation here to explain where and when to use these to make sure I'm not losing anybody so
00:08:19.200 the these first two steps here with these little hyphens these are the
00:08:25.660 functions that get the our methods that are called that trigger the callbacks
00:08:32.110 that fall underneath them right so if we are to say user dot save then one two
00:08:37.360 what these callbacks one through seven would follow in the order that you see here if I were to see check to see if a
00:08:44.590 model is valid so if I say a user not valid then the next step would follow so
00:08:50.710 let's take an example so if we were trying to do some debugging here and we
00:08:56.190 were having a hard time validating our users and we're using slack for some
00:09:02.860 reason instead of like a test or a console there's an example I don't use
00:09:08.560 this one a super ton but it's there if you need to plug in before you do any validation checking so after you
00:09:16.180 validate something or the at this point this is where we're actually validate it so we're gonna we're going to let's say
00:09:23.680 we have a email validation on here that we require a user to have an email address before we can save their
00:09:29.600 record so after validation maybe want to log it and say yes this this user did have a
00:09:35.800 valid email address then we have before save and before create so these are
00:09:43.810 these are used a lot more frequently in my experience so after the validations have taken place
00:09:49.430 and before we save or before before we save could be like from an update before created specifically for creating a new
00:09:55.490 record what are some things I'm going to have so before create again more more
00:10:01.130 logging here but a more practical example is this set modified by let's
00:10:07.700 say let's say we were wanting to do some author write some auditing in here to make sure that we record whoever created
00:10:14.180 a user maybe it's to give a sales person Commission or something like that like
00:10:21.110 these these life cycle events are super important because people's livelihoods could be on the line and this is an
00:10:28.820 example of that right so we're gonna say that I modified this user so I'm gonna
00:10:34.730 have this set modified by a callback that we can attach before we save a record so then we get to everything that
00:10:42.050 happens after we create something so these are a lot more practical as well
00:10:48.550 because maybe after we create something we that's when we blast our sales channel that we have a new sale or a new
00:10:55.310 signup and slack write again these are a little bit more abstract but the idea is
00:11:01.480 you can write whatever custom logic you want in these various stages of the
00:11:07.970 lifecycle so of creating of of updating
00:11:13.000 so the pros of callbacks super easy to use they pair really well with validations and they're very easy to
00:11:20.540 test in an isolated manner and as a bonus they run in every database operation within active record so no
00:11:28.400 matter how you are saving or saving something let's say you're in a rails console or you've built in a view in
00:11:35.750 your synapse controller or maybe different the controller that does like
00:11:41.310 both processing all of those no matter which way you're creating a user they're
00:11:47.010 all going to fire the callbacks which is super helpful kind of a callbacks is they run every time so it's kind of a
00:11:56.100 double-edged sword like it is a pro but maybe sometimes you don't want to run all of that complex logic maybe you want
00:12:01.920 to bypass it because we're trying to do a bulk import for instance we don't want to send them all a welcome email or
00:12:08.670 maybe their welcome email needs to have specific instructions or something like that right so tying those into the
00:12:14.670 database itself is tricky because they're gonna run every time they have a potential for erase conditions and this
00:12:21.120 is where I've I've been bitten a lot as I relied too many times especially when they give you that level of granularity
00:12:27.650 it's very easy to write logic that's out that gets out of order maybe some developer doesn't realize the intricate
00:12:34.290 order of all of the logic that needs to take place during during your callbacks and so you can have a race condition so
00:12:43.160 and again complex conditional logic you're not going to know like if you're depending on some sort of one callback
00:12:50.850 running before the other or one Association being in place but it's not yet it can get very complex and here's a
00:12:59.250 terrible joke about race conditions and if you're not familiar with a race condition the idea is is you might be
00:13:05.970 expecting it to be in one place but it happens somewhere else or it happens later than you thought right and that
00:13:12.450 happens all the time with very with poorly written callbacks and that's the crux that what I want to talk about
00:13:18.390 today is is to how to write these life cycle events that are going to happen that rails gives you beautiful tools to
00:13:25.170 tie into in a way that's going to treat your that that's going to be easy to maintain and maintain your happiness so
00:13:32.010 for time as well so our example here let's start with our pro tip of the day
00:13:38.670 because my example is a very terrible example and my pro tip for you is
00:13:44.000 callbacks should only modify the model that they are defined on so
00:13:51.459 we had these associated models right we had a user we had a credit score we had an alert profile I want you to take away
00:13:58.179 from this talk this point callback should only modify the model that they
00:14:03.309 are defined on so a user should only modify a user you shouldn't actually be
00:14:08.410 creating an Associated record in a callback because for the reasons that
00:14:14.230 I've just talked about with my cons this is going to lead you down a rabbit hole of debugging in the future just you wait
00:14:21.519 I know it's easy and you can write it and know the code will work but as you expand upon your software this is
00:14:27.550 getting to be your biggest point of friction so let me kind of explain this
00:14:32.649 visually here right so active record this beautiful interface that we have that sits on top of a relational
00:14:38.259 database this is how our database is gonna be your data and very separately defined tables it's not but it's not
00:14:44.439 going to know about all the war crimes that our application that's committed because this is how our application is
00:14:50.170 going to look if we implement our software the way that I showed you within our call backs they're kind of
00:14:56.769 like grow come together in this giant mass of a snowball that's you know why
00:15:02.350 do we even have separate files for all of these because if they're so interconnected and related to each other there's no separation of concerns or
00:15:10.119 responsibilities this is a common theme with an experienced software as people
00:15:15.189 software developers those who have read a lot of books on refactoring and software design every model should have
00:15:22.660 should be responsible for itself and not for its associations and callbacks is a
00:15:30.100 great way that we can accidentally blur those lines and bring everything together where there's not a single
00:15:36.779 principle of responsibility that each of these models has so why no multi model
00:15:45.089 modification callback system so like I said model responsibilities are not respected everything is all kind of
00:15:51.220 mushed together and this kind of comes back to the hypothetical before what if
00:15:56.769 what if we want to create a user that doesn't get a welcome email maybe it's like a white labeled
00:16:01.830 than what they don't want to have the same email that goes out to them right what if the credit score creation
00:16:09.240 process needs to vary from one user to another so if you're doing all if you're doing a giant if statement for for that
00:16:16.830 condition all tied within a complex callback with different models it's gonna get super hairy super fast I and
00:16:24.060 what if we want to change the behavior that happens when a new user signs up but we want to maintain what what the
00:16:32.130 older users had or maybe you have a new product that we're spinning app we haven't won a new API the versions that if you're doing that the call back later
00:16:39.000 and the models it's gonna be super gnarly for you to be able to tackle that I haven't at this point home enough for
00:16:45.600 you yet let me do the only there I only have one more trick up my sleeve as a millennial to really make you consider
00:16:52.770 the impact here and that is to give you an Instagram beautiful picture with
00:16:58.200 cursive with this quote because that's what we do damn callback should only
00:17:05.160 modify the model that they are defined on okay so our example here of creating
00:17:10.230 an alert profile welcome email and a credit score this breaks our test no
00:17:15.870 soup for you this is bad because we're modifying associated models than the
00:17:21.030 user callbacks for a user should only modify the user so common question that
00:17:28.020 I get when I'm explaining this concept is that does that mean I can't use associations and callbacks then cuz I
00:17:33.480 love them that you were saying else and that's what makes real so so productive as a an iteration platform the answer is
00:17:40.590 yes you you can use associations as long as they are read-only so in this case if
00:17:47.520 we're gonna update the status of our user after we've saved or after we've
00:17:52.800 committed to the database a change we can do that in a read-only fashion so in
00:17:59.280 this case we're accessing the associated credit score of this user but we're just checking to see if their credit is good
00:18:06.350 because we can English apparently so we're not modifying the credit score
00:18:12.480 we're not deleting her credit score but we're just check King on a tray so that's a that's
00:18:17.620 alright I'll ride alright because we're just reading we're not modifying that credit score so
00:18:23.080 a couple more examples so let's say we have a transaction with paid off a loan
00:18:28.420 we've created a new loan we've accrued interest etc because it's a credit it's a credit application so after a
00:18:35.320 transaction happens this is a very common pattern we need to update a user's credit score this is this is a
00:18:41.440 very naive implementation and this reads super well like I could open up this
00:18:47.350 this this code and be able to know exactly where to look but let me show you how terrible things can get if we
00:18:54.190 crack open this callback yeah okay so at
00:18:59.740 least it's readable because these variable these function and these method names are very english' bulette stake a
00:19:06.760 look from a deep in where our debugging hat and try to see into the future where
00:19:12.940 things could break so after a transaction we want to holy cow look at
00:19:19.240 this we need to obvious some way of checking the debt of this transaction so that's
00:19:26.380 another associated model that we're gonna have to take account of and then we're gonna have to see if it was paid
00:19:31.630 on time and then we're then based off of that we either increased the credit score or decrease the credit score again
00:19:38.380 this business logic is all sound and if it were just these crimes I think we could probably squeeze bye for now and
00:19:45.180 assume this technical debt but now we're gonna do another track of their credit
00:19:50.620 ratio again another associated model that we're going to have to take alert or at least a we'll have to look at
00:19:58.840 their credit their credit score and then view all their transactions to view their ratio and all this is going to
00:20:05.290 take place during every transaction again we could probably sneak by but now
00:20:11.080 we've got some conditional returns in here oh because yeah test users happen all the time demo users we're going to
00:20:16.840 we've got to be able to pitch to get to get our next deal all right oh and then apparently
00:20:22.820 I mean it wouldn't be a FinTech product without you know some good old-fashioned American nepotism that would totally
00:20:29.659 happen oh holy cow now we're reporting to external services credit services
00:20:36.700 Experian credit bureaus that makes sense oh and some Darkman pirates so that all assumes that all of these actors are
00:20:43.340 online and accepting transaction updates and then lastly at the end here we're gonna check to see if our users got an
00:20:49.100 alert profile and if it does then we're going to send them a transaction alert
00:20:54.129 holy cow so there's a lot of things that can go wrong within this callback what
00:21:00.049 happens to your transaction if none of those services are up how do ID bug and the weird example of it's my nephew's
00:21:08.350 debt ratio that's too high should they actually get an email or not like should
00:21:14.299 all of this happen at every transaction that I just open up with an arraylist console I'm exhausted by this so the
00:21:21.830 only serene option here is you should only modify the model that is defined on
00:21:27.710 that callback right so please please please do not it's I know you can write
00:21:35.480 it and it will actually the software will work but over time you're setting
00:21:41.929 yourself up for failure with that giant transaction example because that's what will happen over time with a software with software that is getting used this
00:21:49.340 is a bug taking that it was just assigned to me and you know this is going to be a great bug because it has
00:21:54.440 this terrible word that starts the ticket off of sometimes sometimes new
00:22:00.830 users are getting welcome emails so that sucks or in this ticket we haven't been
00:22:07.009 given instructions to how to recreate it so that's going to be painful and we know that it's gonna be tied within this
00:22:13.190 user signup process because it's a welcome email we know that's where that callback was written so we have to dive
00:22:21.230 in so why would this happen so I think
00:22:26.480 my first thing that I would do is I'd open up a rails console maybe I'd see their alert profile just see if anything
00:22:32.720 weird is going on oh but look at this there's not one okay that makes sense what should have
00:22:38.750 happened to a user should a user exist within our platform if they don't have an alert profile should have been
00:22:44.540 created it at all what happens when we try to send an alert to that user in the future how do we ensure that all three
00:22:51.110 objects are created so the user the credit score's the alert profile how do
00:22:56.960 we ensure that all three of those things were created at signup how do we fix this code smell that I've been having on
00:23:17.770 one of my favorite tools and rails the active record transaction for handling
00:23:23.030 my associated models that I love so much let me show you how this sucker works so a transaction is a block that basically
00:23:30.559 says anything that takes place in this block if there's ever an error in it we're gonna roll everything back and so
00:23:37.190 nothing is gonna be written to the database it's gonna be an undo so let's say our user was created that's all fine
00:23:43.190 and good but if our credit profile fails to be created then the user it's above
00:23:48.740 it also is deleted and that hasn't actually get created as well so to answer our other hypothetical a should a
00:23:57.530 user be able to exist in our platform without an alert profile I'd say no that's part of our signup process it
00:24:04.130 needs to be there so yeah these transactions are super helpful because
00:24:09.679 it's either all of this code happens or none of it so everything with heading did this block either it has to take
00:24:16.100 place or none of it so the mechanism is it triggers that rollback with is is an
00:24:22.760 error that gets raised and that's why I'm calling my I'm super excited to create these records create with a bang
00:24:30.200 syntax because rather than defaulting to Rails validation API with its user dot
00:24:37.190 errors to be able to inspect why my user wasn't valid I actually wanted to skip that and raise an application error so
00:24:44.570 that my transaction will rollback super cool so if I use one of those now when a
00:24:51.860 user signs up even with my nasty callback we at least now know that all
00:24:57.830 of all three of these users or all three of these objects sorry will be created
00:25:04.580 at registration but where does where in theory should that transaction fit
00:25:10.100 should it fit in the model should it fit in the controller we already talked that
00:25:16.520 it shouldn't live in that callback because it's modifying other and this is where I'm gonna harken to
00:25:22.820 this this popular rails post that has seen many different iterations over time and that's should you have a phat model
00:25:29.990 or a Fat Controller phat model and a skinny controller it had actually argue
00:25:36.560 that nothing should be fat everything should be skinny hold on let's take a moment to salute Weird Al the American
00:25:43.940 hero for this great gym so we know that
00:25:49.970 I don't want to just take this transaction and and we definitely want
00:25:56.120 to rip that out of a callback so a model doesn't seem like the right fit should I put this all into a controller it's
00:26:03.140 right in this context it's not a terrible example like if this doesn't seem like it's overly complex but if we
00:26:11.960 already make this a transaction controller you can see all that logic you can see how the responsibility of
00:26:17.960 the controller is getting way out of hand so a controller should be very skinny should process all of its logic
00:26:23.960 to some other entity typically it's a model but in this case we've already determined that the model is not the
00:26:29.420 correct case for it so where does it go so please know a model in the callback please no controller but where does it
00:26:36.410 go and that's our next tool service object
00:26:44.070 so my good friend Fancy Nancy she always says that a service object is just a
00:26:50.500 fancy name for a vanilla Ruby object so yet you've got plain Ruby objects but
00:26:56.830 you have a service object so it's a classier distinction of course so here's
00:27:02.470 an example of one I've abstracted a lot of this stuff but
00:27:07.510 this is a very simple simplistic example of what a serve user service could look
00:27:13.660 like and you'll notice unlike all rails
00:27:18.790 things that are generated there is no inheritance this isn't inheriting from something it makes you feel kind of
00:27:25.180 naked because you're on your own everything that you're writing here is not part of the rails framework but as
00:27:31.930 part of your business logic and it's part of the reasons why I love reaching for tools like this is because your
00:27:39.240 application your business needs to do something specific to you and will frequently not fall into the standard
00:27:47.400 crud request cycle of a scaffolded
00:27:53.230 object in our example here we have got a lot of complex logic it should live
00:27:58.420 somewhere and a service object is a great place to live so just to run
00:28:03.610 through this really quick so we're gonna create so passing in the params this will be like from a registration form
00:28:10.120 all of the fields that we correct collect and then we are gonna extract from those for the fields that we need
00:28:16.360 for a user per am or alert profiles from our credit from a credit score and then we'll actual trigger our create objects
00:28:25.950 method here that will utilize our really cool active record transaction and
00:28:33.160 pretty straightforward whether or not you need an object to process that and you prefer procedural processing and
00:28:38.470 this is actually we're not what I would prefer because I think this is a lot more straightforward and it's a lot more
00:28:44.680 functional well a state to manage we can just pass the the objects that we create
00:28:50.470 in line something like this would be my preference so the procedure is like the
00:28:57.509 the super sophisticated approach to this just having something that's some some
00:29:04.179 procedure that keeps track of all of your your registration process versioning gets super simple because you
00:29:10.899 can take your either your service object or your procedure for registration and
00:29:17.820 throw a version on it so if you need to maintain support for how things used to happen when you signed up users to have
00:29:24.519 things will with your new and improved mobile application let's say yeah just
00:29:30.070 increment the version number in this module when you're off to the races so where should you put a service object a
00:29:37.450 procedure where should you put it I've seen some people put to these type of
00:29:42.730 vanilla Ruby objects in the model directory and that's fine like rails is gonna load them I personally don't
00:29:49.179 prefer the model directory because models in my mind should be what rails generate like everything there should
00:29:55.029 have an Associated database table that goes with it I prefer to from doing
00:30:00.759 service objects to have some sort of other directory for services or maybe in Lib since it's not part of the rails
00:30:08.710 traditional app structure I don't think it really matters what matters is is that your team all have the consistence
00:30:15.090 preference for all of that so pros of a service object so single
00:30:21.730 responsibility for everyone so we're gonna have a dedicated object that takes place that takes care of need your user
00:30:27.940 registration it's one object that its job is to know how all these pieces
00:30:33.309 within our application are glued together and each piece of our application doesn't need to know about
00:30:38.830 everything else like it's everything has its single responsibility it's easy to
00:30:45.610 test because we don't have to create a like a factory bot or something like
00:30:52.480 that or a fixture that keeps put track of the ordering that all of our various
00:30:58.029 associated records need to take place and no like a service object or proceed
00:31:03.460 it knows that we're gonna feed it a / a
00:31:08.640 manifesting very easy to test it's super easy to version like we talked about very easy to complain contain
00:31:16.390 complexities and again because you have an object that sole responsibility is to
00:31:22.000 juggle all of those things it's easier to it's easy to have one place that it has it all and we have the option to
00:31:29.500 sidestep this registration process if we ever wanted if we need to create just a user by itself without an alert profile
00:31:38.350 or a credit score we can absolutely do that and since it's not tied to a callback we have that flexibility
00:31:45.450 because it's not tied to every database event that takes place for a user so the
00:31:51.280 cons of a service object so again just as the the pros of callbacks worked for
00:31:57.340 us they can also disassociate in them could could bite us in time like if
00:32:03.100 we're if a developer isn't aware on our team that's we have the service objects
00:32:09.190 or procedures that take place take care of user registration they might not they just might assume that it's their alert
00:32:15.880 profile is always gonna be there when they create another way to create users within our platform so there is some
00:32:21.190 training that you're gonna have and responsibilities that you're gonna have to to make sure that all of your edge
00:32:28.390 cases are handled it's not vanilla rail so this isn't something this isn't a subject that rails boot camps or college
00:32:35.410 courses are going to cover and if they do it's going to be like a 30-minute session but it's a super powerful way to
00:32:44.280 kind of contain the complexity of your logic the learning curve might be there
00:32:50.980 for a bit especially if this is your first time to kind of dealing with data pipelines because these this type of
00:32:58.930 concept isn't covered in a lot of the educational material so there will be some training that you're gonna have to do and you're also gonna have to figure
00:33:06.520 out how you're going to handle errors so rails with callbacks they tail suit they
00:33:11.950 pair super well with API with if you build your own service
00:33:19.060 object or your own procedure you're gonna have to either use those existing
00:33:25.020 error models or those existing validation API is but find some way of
00:33:32.230 messaging in the controller's of the various consumers of your services that something went wrong it's it's just more
00:33:39.190 things you're gonna have to think about but it's good because you should be thinking about all of those things because going back to our earlier bugs
00:33:46.240 should a user be able to exist without an alert profile right like you need to be thinking through all of those things
00:33:51.820 for user registration but it's just it's just not as easy out of the box as it would be if you're using scaffolding
00:33:58.860 okay so going back to our where does our fat skinny and models or controllers
00:34:06.640 live so in this example our model for a user now looks like this we don't have
00:34:13.090 any callback that has a new logic for it thankfully our controller is just passing off to our user service this
00:34:19.210 create calls or a controller super-skinny and our service has the all of the life
00:34:25.690 cycle logic that needs to take place in a very easily testable and isolated environment so our model our Mott our
00:34:34.870 database in our application are now viewing themselves all in the same way and everything is awesome because
00:34:42.330 everything is isolated and they all have their own responsibilities but all of
00:34:48.640 this makes one giant assumption and that's that all of your logic is taking place within one application and that
00:34:55.740 brings us to our last chapter
00:35:03.350 hi there little fella so how do we
00:35:22.140 handle lifecycle events in a distributed environment it could be microservices or
00:35:27.990 maybe there's just different apps within our within your your platform right let's say our our credit our credit app
00:35:37.530 has scaled significantly so we needed to break these up into multiple apps of course cuz we're a web scale now okay so
00:35:45.690 we've got a couple of different apps now so we've got our main credit app but maybe we've got another app that has business intelligence one for marketing
00:35:51.870 one for alerting and one for sales itself right so when we create a user
00:35:56.880 how do we tell all these various apps about that and how do we handle these
00:36:02.790 complex lifecycle events in a distributed fashion and the core idea is
00:36:09.560 the where before when it was all under one hood we had complete control over the ordering of how these things all
00:36:16.020 happened in a distribute environment you have to let go you have to relinquish the control of
00:36:23.130 all of that and trust that everywhere else in the platform can handle the the new information about your record that
00:36:30.840 you are going to tell it so our job is to tell the system at large that
00:36:36.000 something has happened and we have to trust them to do what they will with that information so it looks something
00:36:43.110 like this we're gonna send a message out that a user is created and then all the gossip then the system is going to take
00:36:48.960 place right we the only control we have is our text message that we're sending
00:36:54.480 out is our message of a user update that we're sending out and the rest of the
00:36:59.880 platform it's their responsibility to consume that information and make their changes as they see fit so a couple
00:37:08.670 technical examples that I have implement or technical tools that I've used to implement this idea of messaging
00:37:15.730 on a distributed basis RabbitMQ and Kafka are great tools to reach for they
00:37:21.250 each have their pros and their cons I won't dive too much into where what tool
00:37:26.350 you should use that's for another talk but what I want to do is give an example of how you deal with these events
00:37:33.160 with these tools in a high-level is all and the idea is we need to publish in a
00:37:38.920 subscribe model so we're gonna publish a life cycle event out into our platform and the various apps in our platform are
00:37:45.820 gonna consume that message by subscribing to that event topic so for
00:37:52.390 an example here's our credit app we have our user that we're going to serialize
00:37:58.150 to JSON and then we're going to publish it out to the world so in a rabbit example we will publish it to a topic of
00:38:06.520 like user dot create so anyone any of these consuming applications that are interested in user create events they
00:38:13.570 are gonna subscribe to user creates and they can do whatever they would like to it so a CRM for instance if we create a
00:38:20.620 user within our app they will consume that user JSON and maybe create a
00:38:26.170 contact for it right then maybe that's something that they would be interested in and the the cool thing is is this one
00:38:33.460 user event kind of gets fanned out to everyone and they all get their own cue to process that data which is really
00:38:40.450 cool for resiliency because if if ever our CRM app is down for instance I know
00:38:48.370 it's never happened in the history of the world but if our CRM is down does that mean that it's gonna miss any chase
00:38:55.000 user J songs that come in in the future that would be a bad system and that's part of the benefit of using a messaging
00:39:01.750 system like this is rabbit is going to persist all of those messages so by the time that it comes back up it can
00:39:07.570 process all of the the user messages that it missed while it was down which is really cool so a an example of that
00:39:15.550 is this so tying into those callbacks that we talked about earlier so when we
00:39:21.190 create or update or delete we want to publish that eight two two two rabbit or two Kafka or
00:39:30.160 it's whatever pub/sub service that you use right so we're gonna publish the state of ourself so in this case self is
00:39:36.910 a user so it's gonna be a JSON payload and then our CR CRM app will consume
00:39:44.619 that message with something like this this is all kind of just pseudocode but it's gonna listen to any users create
00:39:51.219 messages and it will create a new contact and maybe it will be able to deduce what account in our CRM the
00:39:57.509 contact belongs to based off of the company name of the user or something like that right so the responsibility is a lot
00:40:06.549 more distributed now and this is where the costs of distributing computing computing really come into play I don't
00:40:12.880 really have a horse in the the monolith versus microservices battle but I can
00:40:19.150 tell you that distributing computing is a lot more expensive so make sure you are prepared for the expense in
00:40:26.609 technical complexities and the the
00:40:32.410 technical services that you're gonna have in place for for managing all of them because these are a lot more moving
00:40:38.769 pieces and since you don't have that control of what happens if the user errors at what should happen if you
00:40:46.390 can't control it then you're more error prone and you have to write all sorts of logic to get everything back in state
00:40:53.019 and all entry synced it it is possible just gets more expensive so the pros of
00:40:58.859 pub/sub lifecycle event handling what's nice is dependent surfaces can go up and
00:41:05.589 down and they will not miss out on key information and they shouldn't prevent a
00:41:10.660 user from being created we can we can pick up where we left off when the app
00:41:16.289 it comes back online we don't need any service objects or procedures there's no
00:41:23.079 option for a gnarly call back because we're just gonna tell the world about our state and we can move on super
00:41:29.619 resilient to downtime like I talked about our queues can keep track of what we've missed when they come back on
00:41:35.950 and everyone Minds our business part of the part of the the lore of micro
00:41:45.340 services and distributed apps is that it takes that principle of single
00:41:50.380 responsibility and it kind of takes it up another level like our credit app should only be responsible for credit
00:41:56.200 and not for email notifications or CRM integration and like I can see where
00:42:02.920 they're coming from with that because it takes that single responsibility and takes it to the next level the cons are
00:42:09.240 it's super easy to get out of sync especially if you're running into problems with let's say there's a data
00:42:16.750 problem with users like how are you gonna handle updating that problem within your database and in your app and
00:42:23.650 then telling all the distributed apps that hey everything that we did in the past we need you to undo everything it
00:42:31.420 can get kind of complex my my recommendation is to make all of your consumers idempotent so that means you
00:42:37.390 can process the same message over and over and over again without doing any harm so that if you have to get things
00:42:43.870 back in sync you can just rebroadcast the state of your database across and everything is like a worst case scenario
00:42:49.600 and that's totally fine it's just again more complexity that you're gonna have
00:42:55.030 to keep in the back of your head super is susceptible to race conditions and orphans even more so than our call backs
00:43:01.870 example from before because there's different places where all of this logic and and state can live so if you are
00:43:11.640 dependent on different consumers for creating different objects and then broadcasting those out for other people
00:43:17.170 to consume it can get really gnarly and it's very easy to enable a distributed
00:43:24.520 monolith this is an argument that a lot of people who hate micro services really
00:43:29.970 come back to is that we can have the benefit of splitting up applications but
00:43:35.890 not realize but not really realize that true benefit because if they're all so
00:43:41.680 tightly integrated with with in understanding the state of all of the records from all of the different
00:43:48.520 well all we've really done is created a giant monolithic app wherever where
00:43:54.890 everyone's dependent on the state of a user keep that dependency up and running is this this pub/sub model that as
00:44:02.920 momentum before it kind of can be not only to keep up so it can't enable it if we're not being vigilant on what our
00:44:10.370 dependencies are like what do we have what does our CRM have to know about does it need to know about transactions
00:44:16.910 does it need to know about credit scores or does it just need to know about our users and it can be a big old single
00:44:24.800 point of failure so so if rabbit goes down all of this fancy distributed
00:44:30.560 architecture that we that we have built up could crumble pretty quick so in review callbacks they're beautiful
00:44:41.350 but the big point is is they should not modify other associated records they
00:44:46.460 should only modify themselves I beat that dead horse pretty well I think during this talk it's great for setting
00:44:53.240 do you database fields on that model itself if you have to use associated models make sure you're doing so in a
00:44:59.480 read-only matter next it was our active record transactions so if you require
00:45:05.930 many records to be created in line and dependent upon each other use a transaction you can easily roll back so
00:45:11.720 that you made you know that you're either getting all of them or none of them but don't put them in callbacks
00:45:17.510 because that breaks our earlier rules our service objects and procedures so
00:45:23.090 use a service object when you are required to do a series of dependent database calls during a lifecycle event
00:45:29.480 so lifecycle events are so if they're if they have so much business logic that are getting complex and key to your
00:45:35.780 business I highly recommend creating in their own entity to handle user registrations user deletes client
00:45:43.880 registrations like all of that should be bottle up into their own file ruby file
00:45:50.570 just say it as simply as possible and you can use if you if you prefer objects
00:45:56.050 architecture that's great if you're a class method that handles your procedure that works
00:46:02.810 it helps keep your model separate so if you're not a monolithic architecture and you are into some sort of distributed
00:46:09.680 microservices set up I recommend using a distributed messaging platform like
00:46:15.230 rabbitmq or Kafka to broadcast and handle all of your lifecycle events this
00:46:20.900 is really great for maintaining that boundary of responsibility it's very resilient to downtime but it's
00:46:27.260 not immune in fact it's more so immune or more so suspect susceptible for orphans and race conditions alright we
00:46:34.790 made it we got there we've learned a lot I had a ton of fun putting this together I owe a ton to this rails community at
00:46:42.050 large in fact my entire technical career I think I owe to it because I these
00:46:48.470 tools that help me rapidly iterate have given me my career and given me my
00:46:54.830 professional passion as well and I just wanted to find a way to bottle up all
00:47:00.050 the things that I've learned over the time so write simpler more maintainable code and deliver it in this format so I
00:47:05.630 really appreciate you hanging out with me if you enjoyed it here's where you can find me on Twitter and tell me
00:47:11.420 enjoyed it if you laughed at all let me know because I Drive so much am i worth off of making people chuckle that would
00:47:19.550 be great anyways thanks for flying with us today and I will see you next time