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