Talks

Everything a Microservice: The Worst Possible Intro to dRuby

Microservices are great, but I think we can all agree: we need more of them and they should be micro-er. What's the logical limit here? What if every object was remote in a language where everything's an object? Let's take a look at dRuby, the distributed programming module you've never heard of, and use it to achieve that deranged goal! You'll learn about a nifty little corner of the standard library while we attempt to reach the illogical conclusion of today's hottest architecture trend. Be warned: those sitting in the first few rows may get poorly-marshaled data on them.

RubyConf 2022

00:00:00.000 ready for takeoff
00:00:17.660 all right let's get started
00:00:21.060 now
00:00:22.800 I'm sure you've all heard of
00:00:23.880 microservices the hottest new
00:00:25.859 architectural trend of the last decade
00:00:27.420 really but what is a microservice well I
00:00:30.779 think we can infer from the name that
00:00:31.980 they're like regular services but
00:00:33.180 smaller and we can infer from the hype
00:00:35.160 around them that they are like regular
00:00:36.300 services but better somehow
00:00:38.640 and as far as I'm aware this is all the
00:00:40.680 information about microservices anywhere
00:00:42.360 on the internet not a single blog post
00:00:44.100 book or think piece anywhere so we are
00:00:46.620 going to have to extrapolate ourselves
00:00:48.000 and I think we can conclude from those
00:00:49.620 two pieces of information that's smaller
00:00:51.059 is better for microservices
00:00:52.800 but what is the optimal size for a
00:00:54.780 microservice maybe each of your
00:00:56.640 microservices should encapsulate a
00:00:58.739 single concept or maybe if you're
00:01:00.600 breaking apart a monolith each of your
00:01:01.920 models should be a microservice or maybe
00:01:03.960 a single cross-cutting concern now I
00:01:06.180 would like to put this to a vote but I'm
00:01:07.380 not going to because you'd all be wrong
00:01:09.920 let's take a look at the data if regular
00:01:13.140 service oriented architecture is good
00:01:15.000 and microservices are better well any
00:01:17.460 fool can draw a line between two points
00:01:19.200 and I am such a fool so I assert that
00:01:22.259 the optimal size for a microservice in
00:01:23.880 Ruby is the single object
00:01:26.340 if you've got an object anywhere in Ruby
00:01:28.320 it should run on a remote machine but
00:01:30.540 let me be more concrete
00:01:32.280 so you've got a class and we've got an
00:01:34.740 instance of that class and I call a
00:01:36.119 method on that instance this method
00:01:37.619 should run on a remote machine this
00:01:39.119 object should be a proxy object no
00:01:40.740 matter what it is folks you may not like
00:01:42.960 it but this is what Peak architecture
00:01:44.280 looks like
00:01:45.840 I don't know how this is better than a
00:01:47.640 regular service but it must be because
00:01:49.560 microservices
00:01:51.360 and of course our custom objects should
00:01:54.360 be microservices but this is Ruby
00:01:56.040 everything's an object and therefore
00:01:57.899 everything should be a microservice this
00:01:59.579 array should be a microservice this
00:02:00.899 string should be a microservice even
00:02:02.759 these integers should run on a remote
00:02:04.619 server and to that terrible end I give
00:02:07.020 you this talk everything on microservice
00:02:09.239 the worst possible introduction to druby
00:02:14.000 thank you
00:02:16.680 now because I have the moral compass of
00:02:19.560 a Disney villain I'm going to walk you
00:02:21.120 through setting up your code to do
00:02:22.260 exactly this to make every single object
00:02:24.900 run on a remote server let's take a look
00:02:27.540 at our terrible terrible goal every
00:02:30.540 object including ones created by Ruby
00:02:33.180 automatically running on remote servers
00:02:36.120 but to get that horrible end we need to
00:02:38.160 start small we need some concept of a
00:02:40.319 remote object I want to somehow create
00:02:42.599 an object and then when I call a method
00:02:44.280 on that object have that method run
00:02:46.080 remotely
00:02:47.160 now I could do this myself maybe have
00:02:49.560 that method issue HTTP requests and then
00:02:51.720 deal with responses and have some
00:02:53.340 service set up to run that object but
00:02:54.959 that sounds like a lot of work and one
00:02:56.340 thing you should know about me is that I
00:02:57.720 am as lazy as I am irresponsible and
00:03:00.480 therefore I'm going to try to use
00:03:02.280 something built into Ruby I would like
00:03:04.500 to introduce you to druby now drewby is
00:03:08.340 an interesting little API for creating
00:03:09.959 and using remote objects it's built into
00:03:12.000 the Ruby standard Library although if
00:03:13.920 you want there is a bonus external gem
00:03:16.140 that goes with it that you can get by
00:03:17.700 installing by doing a gem install derb
00:03:20.099 and that just adds some features to the
00:03:22.560 built-in version but we're going to
00:03:23.580 stick with the built-in version now
00:03:25.319 before I go and talk about how how not
00:03:28.620 to use Ruby let's start out with the
00:03:29.940 correct way to use it so droopy has two
00:03:32.940 parts it's a sort of a client service
00:03:34.980 architecture or client server
00:03:36.599 architecture you've got some local
00:03:38.580 script let's say it's running on my
00:03:40.080 laptop and then we've got some remote
00:03:41.640 script running on a server in the cloud
00:03:43.379 somewhere well in the remote script
00:03:45.299 first require derp derb because Derby is
00:03:48.000 required by the standard Ruby standard
00:03:49.860 library but it's not required by default
00:03:51.480 so you have to require it then we set up
00:03:54.239 some class that is going to be a remote
00:03:56.519 object
00:03:57.480 next call durb.start service
00:04:00.540 and this will do what it says this will
00:04:02.159 start a droopy service on whatever
00:04:04.080 hostname import you give it
00:04:06.239 then we give it some object that's going
00:04:08.400 to handle requests for this service on
00:04:10.920 this port
00:04:11.879 and here we're just going to give an
00:04:13.980 instance of remote server next we call
00:04:17.299 durb.thread.join this will block this
00:04:19.560 script at this point so that it doesn't
00:04:21.840 immediately exit it will say stay
00:04:23.340 running and listening for incoming
00:04:24.780 connections now on the local side we
00:04:27.300 again require durb Durham then we do
00:04:29.660 dermobject.new with URI and this does
00:04:32.639 what it says on the tin this will create
00:04:34.199 a new object from whatever URI you give
00:04:37.080 it and what you want to give it is the
00:04:38.699 URI where you started your server
00:04:41.220 now this remote object this is a proxy
00:04:44.100 object
00:04:45.000 that means it's not really an instance
00:04:46.979 of remote server if I call a method.foo
00:04:49.440 that method will get proxy to the remote
00:04:51.479 server and run on the remote side and
00:04:54.180 then any any result will get returned
00:04:55.860 back to the local side let's see how
00:04:58.080 these work
00:04:59.220 I can run the remote.rb and this will
00:05:01.979 stop and wait for incoming connections
00:05:03.660 then on the local side I'd run local.rb
00:05:06.840 and this will immediately cause the foo
00:05:09.000 method to get run but it will cause it
00:05:10.680 to get run on the remote server not the
00:05:12.900 local one
00:05:14.340 this is already kind of like a little
00:05:16.380 microservice with very little code we
00:05:19.080 have a remote object
00:05:20.699 but all right that's how you're supposed
00:05:22.139 to use Ruby and it kind of sucks
00:05:25.560 I mean we have to explicitly set up the
00:05:27.660 spoon method on the remote side any
00:05:29.460 method you want to call has to be
00:05:30.960 previously set up on the remote side
00:05:32.400 before you can use it on the local side
00:05:34.620 and that's not what we want let's take a
00:05:37.080 look back at our goal we just want to
00:05:38.699 write arbitrary Ruby code and have every
00:05:40.860 object magically become a remote object
00:05:44.340 what we want is some sort of function
00:05:46.139 that will given a class like this create
00:05:48.900 a remote instance of it via magic and
00:05:51.780 then what we should get is a nice proxy
00:05:53.400 object
00:05:54.600 and we don't want to have to
00:05:56.580 pre-configure everything on the remote
00:05:57.720 side
00:05:58.680 and well if we want if we can set up any
00:06:01.560 object to be remote through Ruby why
00:06:05.220 don't we just make a remote class that
00:06:07.080 creates new objects so here I'm calling
00:06:09.060 remote I'm creating a class called
00:06:10.380 remote newer with a method make new all
00:06:12.539 it does is it takes in a class calls new
00:06:14.580 on it and then Returns the resulting
00:06:16.320 object we set that up as a ruby service
00:06:18.600 and now on my local side I can create an
00:06:21.600 instance of this remote newer using
00:06:23.160 derby object.net with URI and call make
00:06:25.560 new give it a class and I should get an
00:06:28.080 instance of this remote newer class of
00:06:29.759 this uh some class class
00:06:32.699 and this sort of works but it doesn't
00:06:34.979 quite do what we want
00:06:36.600 let's take a look at why here's what
00:06:38.639 ruby is doing if you have a remote
00:06:40.380 object and you call a method on it that
00:06:42.600 method will run on the remote side and
00:06:44.520 then anything that method returns has to
00:06:46.020 get back to the local side so droopy
00:06:48.419 will serialize whatever it's returning
00:06:50.220 in this case the string bar send it over
00:06:52.259 the wire and deserialize it on the local
00:06:54.120 side and if you do that with a string
00:06:56.160 all you get is a copy of that string a
00:06:58.440 local stray that's not what we what we
00:07:00.720 want with our make new method we want to
00:07:02.699 get another proxy object instead
00:07:05.280 well it turns out droopy is very good at
00:07:07.440 serializing simple things like strings
00:07:09.120 and integers it doesn't know how to
00:07:10.860 serialize everything though
00:07:12.600 for example if I have a remote method
00:07:14.639 that returns something weird something
00:07:16.139 that druby does not know how to serial
00:07:17.940 eyes instead of trying to return this it
00:07:20.699 will keep this instance of something
00:07:22.259 weird on the remote side and instead
00:07:24.300 return a proxy object object
00:07:28.680 and of course then if I call a method on
00:07:30.419 that it will get proxied out to the
00:07:31.919 remote side and back and that's what we
00:07:33.240 wanted with our make new function
00:07:35.220 well how does Drew me know what is too
00:07:38.099 weird to serialize well um
00:07:41.039 you can tell it you can say for a given
00:07:43.319 class include this durb Durban dumped
00:07:45.720 mix in and now Derby will never attempt
00:07:47.340 to serialize instances of this class
00:07:50.220 so all right back to our remote newer
00:07:52.680 make new nonsense now this make new
00:07:54.840 function takes a class we create a new
00:07:56.580 instance of it and we're just going to
00:07:57.720 cram in this durb Durban dumped mix in
00:07:59.759 to any object we have created
00:08:02.160 and so this works I have some class I
00:08:05.039 create a new instance in my remote newer
00:08:07.020 I use that to make a nuisance of this
00:08:08.940 class and now what I get is a derp derp
00:08:11.580 object a droopy proxy object
00:08:13.919 but this make new function it kind of
00:08:15.960 sucks it only works if the class has
00:08:18.419 takes no arguments so let's beef it up
00:08:20.639 now it can handle classes that take
00:08:22.680 multiple arguments
00:08:24.300 um keyword arguments blocks whatever and
00:08:25.919 it just passes that on to the new
00:08:27.240 function of this class
00:08:29.580 easy enough and this works I create my
00:08:32.520 remote newer and now I can call make new
00:08:34.560 and let's say we're going to make a new
00:08:35.940 string and I'll give it some initial
00:08:37.620 value of the string123 now what I get is
00:08:40.500 actually a remote string instead of a
00:08:42.180 local string and I should be able to be
00:08:44.399 able to call any method that I could
00:08:46.020 normally call on a string on this remote
00:08:47.640 string I can call 2i that 2i will get
00:08:50.760 proxied out to the remote server run and
00:08:53.220 then the result returned
00:08:54.899 but how can we prove that to ourselves
00:08:56.880 because 2i is just going to return an
00:08:58.440 integer for all we know it ran locally
00:09:00.120 well here's how uh don't worry about
00:09:02.640 this code too much all I'm doing is
00:09:04.140 monkey patching The Living Daylights out
00:09:05.580 of druby so that anytime a call it tries
00:09:08.339 to do proxy a method it will print out
00:09:09.959 what that method is
00:09:11.700 so back to our remote new nonsense we
00:09:14.640 created a remote string we called two
00:09:16.500 ions well let's see what happens on a
00:09:19.560 remote side make new is a proxied method
00:09:21.779 and so is 2i 2i did run on the remote
00:09:24.720 server this little string here this is a
00:09:27.660 tiny microservice
00:09:29.220 and so we can already make everything we
00:09:31.740 want be microservices we create a remote
00:09:34.440 newer and we can create a remote string
00:09:36.480 a remote Hash a remote instance of any
00:09:38.519 class we want
00:09:40.080 but that's already kind of lame for a
00:09:43.500 couple reasons and not just that all
00:09:45.540 we've done is add a couple hundred
00:09:46.560 millisecond delay to all of our method
00:09:48.360 calls
00:09:49.860 really notice how we have to manually
00:09:51.959 call this make new function anytime we
00:09:53.940 want to create a new instance of
00:09:55.440 anything I don't want to do that I just
00:09:57.120 want to call string.new or something
00:09:58.500 like that and get a remote object
00:10:01.800 well this is Ruby the only limits are
00:10:05.339 our imagination and our good sense
00:10:07.640 we clearly have exactly one of those
00:10:09.839 things it let's just override
00:10:12.000 string.new why not heck why stop there
00:10:14.700 let's override object.new now here I am
00:10:17.820 over we're overriding the new method on
00:10:21.000 the root object class so that instead of
00:10:23.399 returning a normal local object it will
00:10:25.380 return a remote object
00:10:28.440 and this should work string.new should
00:10:30.720 return a remote string what could go
00:10:32.880 wrong
00:10:33.899 okay well that it immediately crashes
00:10:36.420 and throws a stack Overflow error
00:10:38.519 um it turns out that druby does need to
00:10:40.620 create some objects under the hood to
00:10:42.300 function so object.new triggers droovy
00:10:45.120 triggers object on new triggers Ruby and
00:10:46.560 so on and so forth then you get an
00:10:47.579 infinite Loop
00:10:48.839 um and you know the classic way to deal
00:10:52.320 with infinite recursion is to carefully
00:10:53.940 consider your recursive cases your base
00:10:55.560 cases your edge cases and make sure that
00:10:56.940 doesn't happen but that sounds like a
00:10:58.500 lot of work so I'm just gonna inspect
00:11:00.000 the call stack and if I'm about to
00:11:01.380 recurse don't
00:11:04.040 why not
00:11:06.720 so here I am using the color method in
00:11:09.000 Ruby which is cool method it gives you
00:11:10.740 the entire stack Trace at the current
00:11:12.779 point in code as an array of strings so
00:11:15.120 we're just going to look through that
00:11:16.200 and just muck about in there and say is
00:11:18.120 anything in here drewb was I called from
00:11:20.339 druby and if so then I am at risk of
00:11:22.620 recursing and so instead of creating a
00:11:24.660 funky weird remote object we are going
00:11:26.579 to create a regular local object we do
00:11:28.920 this by before overriding object.new we
00:11:31.380 stash the original object.new method
00:11:33.240 that actually creates local objects then
00:11:35.579 if we are at risk of recursing we unbind
00:11:37.500 it and rebind it to the current objects
00:11:39.060 and then call it to CR this is all this
00:11:41.279 effort just to create a normal object
00:11:42.959 but if we are not at risk of recursing
00:11:45.540 we can do our crazy remote new object
00:11:49.200 and voila this works I can call
00:11:52.440 string.new and I get a remote proxy
00:11:55.019 object for that string same with this
00:11:56.700 array and calling 2i will run on the
00:11:59.279 remote server clung some will run on the
00:12:00.660 remote server look at this array it's a
00:12:02.760 thing of beauty I've always thought that
00:12:04.440 the only thing better than a ruby array
00:12:06.060 would be a ruby array where every method
00:12:07.920 call involves a network round trip
00:12:10.800 so all right
00:12:12.839 um I can create an array and it works
00:12:14.399 fine I can call any random method I want
00:12:16.500 not that one that one causes an error I
00:12:19.800 call the dot each and it gives me no
00:12:21.660 dump data is defined for class proc and
00:12:24.420 I swear there's a reason for this it's
00:12:25.800 not just that Drew B is getting tired of
00:12:27.240 our and let's just throwing
00:12:29.160 exceptions out of spite really it's
00:12:31.800 because each takes a block now let's
00:12:34.560 take a look at what Derby's doing under
00:12:35.940 the hood understand why that's a problem
00:12:37.980 if you have a remote object in Ruby and
00:12:40.200 you call a method on it with an argument
00:12:41.519 drew me needs to serialize this argument
00:12:43.980 send it to the remote side and have the
00:12:46.139 remote side handle it to do that it
00:12:47.940 calls marshall.dump which we'll look at
00:12:49.740 in a minute
00:12:50.820 with that argument
00:12:52.740 if you call a method on a remote object
00:12:54.779 that takes a block well a block is just
00:12:56.820 a fancy kind of argument and so droopy
00:12:59.040 will just try to dump that as a proc
00:13:02.279 now what is Marshall Marshall's Ruby's
00:13:05.339 built-in way of serializing arbitrary
00:13:07.200 objects you give it some hash like this
00:13:09.480 a colon one and call marshall.dump and
00:13:12.480 it will dump it to some data data that
00:13:14.700 you could save to a disk or send over
00:13:16.620 the wire or the network or whatever and
00:13:18.779 then later on you can reload it with
00:13:20.579 marshall.load and that should give you a
00:13:23.220 copy of the same object you put in
00:13:25.440 now here's the problem
00:13:28.100 sorry Marshall does not know how to
00:13:30.899 serialize procs
00:13:33.959 um and why would it I mean actually
00:13:37.500 sorry first martial is not unless you
00:13:39.420 realize procs the error you get is that
00:13:40.920 same error we were seeing earlier no
00:13:42.540 dump data is defined for class proc and
00:13:44.700 really why would Marshall know how to
00:13:46.200 serialize a proc procs can reference any
00:13:48.600 arbitrary Ruby code they can reference
00:13:50.779 variables outside of themselves classes
00:13:53.040 outside of themselves methods constants
00:13:54.779 globals whatever and so in order to
00:13:58.139 serialize a proc you would have to I
00:13:59.760 don't know serialize the entire program
00:14:01.200 execution context somehow so there's
00:14:03.360 just no way to coherently serialize a
00:14:05.519 plot a serialize a block so obviously we
00:14:08.579 are going to incoherently serialize one
00:14:10.260 because it turns out you can totally
00:14:12.060 tell Marshall how to serialize things it
00:14:14.040 doesn't know how to serialize you do
00:14:16.139 this by defining a couple methods on
00:14:18.060 whatever the class of whatever instance
00:14:19.860 you're trying to serialize first we have
00:14:22.380 to define a dump method and this method
00:14:24.839 should serialize whatever proc is trying
00:14:27.600 to be serialized we're going to do this
00:14:29.220 with a method called serialize block
00:14:30.540 we'll look at how that works in a second
00:14:31.980 but what it does is it's going to
00:14:33.779 serialize the current proc in into a
00:14:36.180 string representing that proc just as
00:14:38.100 raw source code basically
00:14:40.199 some people can already see the the
00:14:41.639 implications of this
00:14:44.519 and anyway if you tell Marshall how to
00:14:47.220 serialize something you have to tell it
00:14:48.480 how to deserialize something you do this
00:14:50.399 by defining a load method which takes in
00:14:52.560 the same thing your dot method put out
00:14:55.139 now given a string like this how would
00:14:57.000 you turn that into a real live in-memory
00:14:59.579 proc well you can just prepend the
00:15:01.920 string proc and evalot and you're going
00:15:04.079 to eval something like that
00:15:08.100 now
00:15:10.380 um that's how you that's teaching
00:15:11.779 Marshall how to serialize procs but you
00:15:14.820 might notice that I'm eliding a lot of
00:15:16.500 dark magic underneath this serialized
00:15:18.660 block function how does that work
00:15:21.120 poorly but this is already a talk about
00:15:24.540 things that should not be so I'll take
00:15:27.060 you through it it's a two-step process
00:15:28.980 the first is we need to find the source
00:15:30.839 of some block so we've got this proc
00:15:32.760 here we need to find the raw source code
00:15:34.680 of this and we can get part of the way
00:15:36.720 there with a method called with a some
00:15:38.760 proc dot binding.source location and
00:15:41.160 what this gives you is two pieces of
00:15:42.899 information first the file name where
00:15:44.940 this proc was defined and the line
00:15:46.500 number well if you want you can just
00:15:48.660 read that file name from disk skim down
00:15:50.760 to the appropriate line and pull out
00:15:52.079 that line why not and that's like that's
00:15:54.660 doable it gets a little messy if the
00:15:56.339 proc spans in multiple lines but it is
00:15:57.959 still possible but it's a lot of code
00:15:59.579 and I'm we've established that I'm very
00:16:01.260 lazy so instead we're going to use a gem
00:16:03.600 called method source which does exactly
00:16:05.519 what we just saw but under the hood and
00:16:07.260 for us so given some proc I can call
00:16:09.800 sunproc.source and it will give me the
00:16:11.760 source code lines for that proc as a
00:16:13.860 string
00:16:14.940 but notice how it gave us the entire
00:16:17.339 line not just the block we want just the
00:16:20.220 things from the do to the end inclusive
00:16:22.079 but it also gave us the proc method the
00:16:24.360 assignment the variable All That Jazz so
00:16:26.519 how are we going to pull out just the
00:16:27.779 block well I guess I could run this
00:16:30.300 string through an incredibly complex
00:16:31.560 regular expression but I think if I
00:16:33.420 tried to parse Ruby code with regular
00:16:34.800 Expressions I would get beaten up in the
00:16:36.540 parking lot by a gang of angry computer
00:16:38.040 science professors for my sins against
00:16:39.779 computer science so instead we're going
00:16:42.540 to run it through an actual parser there
00:16:44.699 are several nice Ruby parsers out there
00:16:46.279 the one we're going to use is syntax
00:16:48.480 tree now don't worry too much about this
00:16:50.279 code it's kind of the font is
00:16:51.720 intentionally too small to be useful
00:16:53.220 what we're doing under the hood here
00:16:54.540 with syntax tree is we start out with
00:16:56.519 some raw Ruby code
00:16:58.199 we are going to pass this to syntax tree
00:17:00.000 and say hey parse this for me and it
00:17:02.160 will do that it will turn it into a
00:17:04.140 tree-like data structure an abstract
00:17:05.760 syntax tree representing the structure
00:17:07.980 of that code
00:17:09.720 then we're going to walk that sin that
00:17:11.640 abstract syntax tree until we find
00:17:13.199 something that syntax tree the gem tells
00:17:15.600 me it tells us is a block we'll get here
00:17:18.240 in syntax which we will say yes this is
00:17:19.799 a node of type block I'll say great
00:17:21.900 let's give me everything below this
00:17:24.179 point in the tree and then format it
00:17:26.160 print it out turn it into a string and
00:17:28.980 look what we get the block itself just
00:17:31.200 what we wanted with none of the cruft
00:17:33.480 and
00:17:34.820 now uh of course if we use this to
00:17:38.400 serialize a block and this block
00:17:40.440 referenced any variables outside of it
00:17:42.240 or classes outside of it or anything
00:17:43.799 that's not in the block itself it won't
00:17:46.140 work but when have we ever let common
00:17:48.120 sense like that stop us so
00:17:50.460 let's press on remember we taught
00:17:52.320 Marshall how to serialize a block using
00:17:54.600 this serialized block function and it
00:17:57.299 works I can call marshall.dub with a
00:17:59.220 proc and it will dump something it will
00:18:00.840 give us some data well can we reload it
00:18:03.059 yes Marshall that load works we get a
00:18:05.520 reloaded proc now I can even run this
00:18:07.980 proc with three and it will print four
00:18:09.539 we did what the impossible we've
00:18:11.280 serialized the block and they said it
00:18:13.020 couldn't be done okay no one said it
00:18:15.299 couldn't be done they said it shouldn't
00:18:16.380 be done but we did it anyway and it
00:18:19.559 works we've taught Marshall how to
00:18:21.419 serialize a block and drewby just uses
00:18:23.940 Marshall so if I call radon Newt that
00:18:25.980 gets picked up and turned into a proxy
00:18:27.900 object by our previous code now I call
00:18:30.120 each and Ruby will serialize this block
00:18:32.280 and it won't throw an error this each
00:18:34.679 will get run on the remote server and
00:18:36.360 the block will get run on the remote
00:18:37.559 server and that's where one two three
00:18:38.820 will print out we are now one step
00:18:41.400 closer to the microservice architecture
00:18:43.620 that all our favorite thought leaders
00:18:45.059 tell us we need
00:18:46.620 but you might have noticed a problem and
00:18:49.679 I don't just mean the sketchy looking
00:18:51.360 guy up on stage wearing flannel
00:18:53.340 you might have noticed how we have to
00:18:54.840 explicitly call array.new I don't
00:18:57.720 usually create arrays like that I
00:18:59.520 usually would much prefer if I could use
00:19:01.380 the array literal syntax or the string
00:19:03.360 literal syntax or the hash literal
00:19:05.160 syntax
00:19:06.360 but how can we do that I mean we could
00:19:09.120 override a radon new like some kind of
00:19:10.919 maniac but we've already overrid an
00:19:12.600 object.new and this technique works if
00:19:16.380 you explicitly call a raid on you like
00:19:17.940 we've been doing but it does not work
00:19:19.799 for the array literal syntax nothing
00:19:21.720 happens when you do that and it turns
00:19:23.760 out there's just no good way to override
00:19:25.440 the literal Syntax for arrays at least
00:19:28.080 not without getting into Ruby's C code
00:19:29.640 which is a little more sketch than I'm
00:19:31.500 willing to do in this talk
00:19:32.940 so instead what if we could take the
00:19:35.340 array literal syntax and somehow
00:19:36.960 transform it into array.new syntax that
00:19:40.020 does the same thing if that more or less
00:19:42.120 well this could work but then I'd have
00:19:44.220 to run some Ruby code that reads my Ruby
00:19:45.960 code transforms it and spits out more of
00:19:47.760 Ruby code and that's I don't want to do
00:19:49.080 that I just want to run my code and have
00:19:50.580 it work
00:19:51.900 so to do that I'm going to introduce you
00:19:53.580 to the ultimate tool in every Ruby
00:19:56.280 villains toolbox for messing with syntax
00:19:59.280 data sorry um that's probably confusing
00:20:02.400 data data yes the uh the Ruby keyword
00:20:06.780 that pairs with another keyword called
00:20:08.520 underscore underscore and underscore
00:20:10.020 underscore and here's how it works
00:20:12.539 you've got some arbitrary Ruby code and
00:20:14.820 then you've got an end
00:20:16.380 and Ruby will read and execute down to
00:20:18.480 this end then ignore everything after so
00:20:20.460 you can have things that are not valid
00:20:21.840 Ruby code thereafter it's meant for data
00:20:24.179 now you're supposed to put data there
00:20:25.440 but you can also just put oh sorry I
00:20:27.660 skipped over an important bit if you
00:20:29.820 call data.read in your code you actually
00:20:32.700 get the contents that came after the end
00:20:34.919 as a string
00:20:36.299 now you're supposed to put data there
00:20:37.799 but you don't have to you can just put
00:20:39.120 more Ruby code it won't get evaluated
00:20:41.039 unless you evalot
00:20:43.020 now
00:20:44.220 at the moment this is just an incredibly
00:20:45.780 convoluted way to run a few lines of
00:20:47.280 Ruby code but the cool thing is it lets
00:20:50.160 it gives you an opportunity to run a
00:20:51.840 preprocessor on your code for example I
00:20:54.539 can take this code after the end run it
00:20:56.400 through a regular expression that finds
00:20:57.900 and replaces anything that looks like an
00:20:59.700 array and converts it to the array.new
00:21:02.400 syntax and heck I can do the same thing
00:21:04.860 for hashes and strings too why not and
00:21:07.799 this will convert each of these into
00:21:09.240 explicit dot new syntax which will get
00:21:11.280 picked up by object.new and turned into
00:21:13.440 one of those droopy proxy objects for us
00:21:15.780 now I did say that if I were to parse
00:21:19.140 Ruby code using regular Expressions as
00:21:21.480 we're doing here I would get beaten up
00:21:23.160 in the parking lot by a gang of angry
00:21:24.539 computer science professors but the talk
00:21:26.520 is almost over and I think I can make it
00:21:28.260 to the airport before they find me the
00:21:30.659 rest of you are on your own
00:21:33.120 quickly one last step
00:21:35.220 right now each of these objects will
00:21:36.900 become remote proxy objects but aren't
00:21:39.600 they really microservices
00:21:41.100 I mean they're all remote but they're
00:21:42.419 all remote to the same place our Network
00:21:44.340 diagram kind of looks like this which is
00:21:46.620 pretty lame I mean if we want our blog
00:21:48.900 post about this Cutting Edge
00:21:49.980 architecture to make it to the top of
00:21:51.720 Hacker News which is obviously the only
00:21:53.460 reason to mess with microservices it
00:21:55.320 needs to look something more like this
00:21:57.900 because as any distributed systems
00:21:59.820 engineer will tell you the more Network
00:22:01.380 hops you have in your system the faster
00:22:03.120 and more reliable it will be
00:22:05.400 so
00:22:06.480 let's rig this up so that our remote
00:22:08.159 code each remote objects runs on a
00:22:10.860 different remote server
00:22:12.720 now we're already on track to have a
00:22:14.700 really trendy hipster architecture but
00:22:16.620 going even further in that direction
00:22:17.760 let's make it serverless let's run each
00:22:20.460 of our objects in an AWS Lambda function
00:22:24.360 now
00:22:25.980 um
00:22:27.240 those of you who are familiar those of
00:22:29.159 you who are familiar with AWS Lambda
00:22:30.480 might know that lambdas are limited to
00:22:32.700 15 minutes of runtime at most which
00:22:34.620 thankfully I don't think will have
00:22:35.580 anywhere near that amount of uptime for
00:22:37.020 this architecture so we don't have to
00:22:38.760 worry about it our program will crash
00:22:40.080 well before that
00:22:41.400 no
00:22:42.900 um so let's Press On
00:22:44.220 if you're not familiar with AWS Lambda
00:22:46.380 it's a cool service by Amazon web
00:22:48.360 services you give them a function
00:22:50.159 definition and they will run it for you
00:22:51.720 whenever you ask one time a thousand
00:22:53.880 times a thousand times at once and you
00:22:56.220 don't have to worry about where it runs
00:22:57.539 you don't have to worry about it ec2
00:22:58.980 boxes or Heroku dinos or any of that
00:23:01.320 stuff and of course for the privilege
00:23:03.299 they will charge you a fraction of a
00:23:04.679 cent per second that your Lambda is
00:23:06.600 running cool anyway the basic Lambda
00:23:09.539 function definition looks like this
00:23:11.820 so let's just cram our Ruby setup into
00:23:14.520 that so I started Ruby service and then
00:23:16.620 I called derp.thread.join so there's
00:23:18.360 Lambda handle Handler which is supposed
00:23:19.799 to run for only a few seconds will block
00:23:21.840 and never complete and just listen for
00:23:23.520 incoming connections forever
00:23:25.500 um and AWS will charge me for the full
00:23:28.440 15 minutes before it times out
00:23:31.020 now a brief aside to those of you who
00:23:32.940 really know your lambdas this isn't
00:23:34.620 quite possible lambda's uh they don't
00:23:36.780 allow you to accept incoming connections
00:23:39.000 but it is possible with the magic of
00:23:41.280 tail scale unfortunately I do not have
00:23:43.500 time to go into how that works but tail
00:23:44.760 scale is really cool because really but
00:23:46.500 really that's that's devops nonsense and
00:23:48.120 we're not here for that we're here for
00:23:49.559 Ruby nonsense so back to that
00:23:52.440 so we're trying to create a remote proxy
00:23:54.960 object in a Lambda first we need to
00:23:57.179 start one of those lambdas I'm going to
00:23:59.100 do that on a a random hostname then we
00:24:02.340 need to sleep three seconds because we
00:24:04.020 need the Lambda to like have time have
00:24:06.299 time to get started and Drew me to start
00:24:09.059 listening I mean on the port then we
00:24:10.980 construct the URI that we want to
00:24:12.179 connect to to connect to that Lambda
00:24:13.740 then we can create one of our remote
00:24:16.020 newers inside of this Lambda
00:24:18.900 then we can call remote newer.make new
00:24:21.120 and create a new Pro new proxy object to
00:24:23.460 an object living in this AWS Lambda then
00:24:26.400 we have to do a little bit of hackery
00:24:27.659 because sometimes drewby gets confused
00:24:29.220 about what URI is supposed to be talking
00:24:30.480 to then we return this proxy object we
00:24:33.179 just created and now we have what we
00:24:35.280 wanted every single object remote into a
00:24:38.340 different machine for each object
00:24:39.480 running an AWS Lambda you can tell this
00:24:41.940 will be blindingly fast because I had to
00:24:43.980 add that three second sleep in there
00:24:45.179 just to make it run
00:24:47.340 so
00:24:48.659 let's see how this whole system works
00:24:50.820 this is the Unspeakable atrocity that I
00:24:53.159 promised you and this is what we've done
00:24:55.980 we pre-processed in a in what is surely
00:24:59.340 a cardinal sin of software we converted
00:25:01.320 all of our array hash and string
00:25:03.000 literals into explicit.new calls using
00:25:05.340 regular expressions
00:25:06.720 then in a further show of utter
00:25:08.280 depravity we overrode the object.new
00:25:10.559 method and then to add insult to injury
00:25:12.840 we prevented infinite recursion just by
00:25:14.580 mucking around the call stack next in
00:25:17.400 one of our only reasonably normal moves
00:25:19.740 we use druby to create remote proxy
00:25:21.900 objects then because we are incapable of
00:25:24.600 doing anything simply we've read our own
00:25:26.760 source code from disk parsed it into an
00:25:28.620 AST transformed it spit it back out into
00:25:30.659 a string and sent it over the wire all
00:25:32.460 just so that our remote objects could
00:25:33.960 handle block arguments
00:25:35.760 finally in order to be maximally hipster
00:25:39.059 we shoved the remote half of this code
00:25:40.799 into serverless AWS Lambda functions put
00:25:43.860 it all together and we've got
00:25:44.760 microservices let's see how it works and
00:25:48.600 it does
00:25:50.520 this is a remote object now and so is
00:25:53.700 this and so is this and so of this
00:25:55.380 they're all microservices ha
00:25:57.840 let's see how it works in real life I've
00:26:00.600 got some sample of arbitrary Ruby code
00:26:02.279 surely our microservice architecture
00:26:04.620 will make this blisteringly fast
00:26:07.380 okay so it takes 10 seconds to run not
00:26:09.120 so much
00:26:09.960 but surely it's a very cost effective to
00:26:12.360 run here's my Lambda bill after about a
00:26:14.700 month or so of messing around with this
00:26:17.400 um okay so it costs more than it has any
00:26:19.980 right to but surely the elegance and
00:26:22.260 simplicity of this setup would be worth
00:26:23.820 it this is all the code we've gone over
00:26:25.679 in this talk
00:26:26.940 all right so it's not so simple
00:26:29.700 relative to normal code it's slower more
00:26:32.100 expensive and hideously complex
00:26:34.559 well folks I say we've succeeded because
00:26:36.779 that is the textbook definition of a
00:26:38.460 microservice architecture
00:26:39.919 congratulations we should all feel
00:26:42.240 terrible about ourselves
00:26:47.220 how about we stick to building monoliths
00:26:49.679 and agree that the real microservices
00:26:51.120 were the friends we made along the way
00:26:54.059 now I'd like to take a few minutes for
00:26:55.980 questions and answers but because I like
00:26:57.539 the sound of my own voice entirely too
00:26:58.980 much I will be providing both
00:27:01.260 now
00:27:02.700 the first question I always get when I
00:27:04.500 present on this sort of nonsense is
00:27:06.240 should I use this in production and oh
00:27:08.820 thank you hopefully by this point the
00:27:10.559 talk the answer is clear absolutely
00:27:12.419 please do and tell me how it goes that
00:27:15.179 will be an amazing story if you have an
00:27:17.640 excess of stability performance and
00:27:19.440 maintainability this architecture will
00:27:21.299 solve that problem for you
00:27:23.520 now on a more serious note you may be
00:27:25.620 wondering what are some reasonable
00:27:26.900 non-ridiculous use cases for droopy I'd
00:27:29.400 say droopy is pretty great whenever you
00:27:31.500 for internal projects hack projects
00:27:33.900 small projects anytime you want to very
00:27:35.520 quickly get up a connection between two
00:27:37.020 chunks of Ruby code running on different
00:27:38.880 processes or even different machines
00:27:40.860 that said droopy has no built-in
00:27:42.659 security or authentication so if you
00:27:45.240 expose a derby port to the public
00:27:46.919 internet you are asking for a remote
00:27:48.840 code execution vulnerability so please
00:27:51.120 at the very least wrap it in a VPN or
00:27:53.039 something
00:27:54.179 anyway
00:27:55.559 um because some people just can't help
00:27:57.360 it but Rubberneck at a car crash I'm
00:27:59.279 sure some of you are wondering where you
00:28:00.779 can see this code in more detail and you
00:28:03.059 can find that this particular code on my
00:28:04.679 GitHub it's the same code just with more
00:28:07.200 detail with a lot more comments and
00:28:09.059 hopefully a little bit easier to read
00:28:10.500 and with a lot of the infrastructure
00:28:11.940 gaps filled in
00:28:14.220 now at this point I'm sure many of you
00:28:16.620 are wondering who in God's name are you
00:28:18.600 how did you get on stage and how can we
00:28:20.159 beef up rubyconf security so it doesn't
00:28:21.720 happen again next year
00:28:23.159 well my name is Kevin cookta you can
00:28:25.080 find me at various places online my
00:28:27.059 Twitter handle is there my personal
00:28:28.140 website is there and I've recently been
00:28:29.700 spending more time on Mastodon which you
00:28:31.140 can see in the bottom right corner
00:28:32.760 now if you're tired of hearing me talk
00:28:34.260 to myself and have a real question feel
00:28:35.820 free to hit me up after the talk I'm
00:28:37.260 always happy to chat or catch me into
00:28:39.120 town anywhere around the conference
00:28:40.799 and finally I'm sure at least a couple
00:28:42.299 of you are wondering who in God's name
00:28:43.980 would hire you and how can I avoid ever
00:28:45.900 working for them and I would like to
00:28:47.880 assure you in no uncertain terms that I
00:28:49.980 get this sort of nonsense out of my
00:28:51.240 system in my spare time so that when I
00:28:52.980 go to log on to work I write nice
00:28:54.840 reliable boring code that as far as I'm
00:28:56.640 aware none of my co-workers want to
00:28:58.380 strangle me over
00:28:59.700 that said I worked for a company called
00:29:01.200 Daybreak Health we are a fully remote
00:29:02.820 mental health company and we provide
00:29:04.620 therapy to teenagers it is easily the
00:29:07.020 most rewarding job I've ever worked at
00:29:08.520 we have literally saved people's lives
00:29:10.559 in the time that I've been there
00:29:12.600 and I don't believe we have an open
00:29:14.100 wreck at this exact moment that will
00:29:15.419 certainly change so next time you're
00:29:16.799 looking for a job feel free to reach out
00:29:18.240 I would be happy to chat about it feel
00:29:19.980 free to check us out I think we're
00:29:21.179 pretty cool
00:29:22.440 and that's the talk I hope you enjoyed
00:29:24.659 it I hope you enjoyed the rest of the
00:29:25.740 conference thank you