List

Cultivating Developer-Centric DSLs

Cultivating Developer-Centric DSLs

by Jake Anderson

In the talk "Cultivating Developer-Centric DSLs" presented at RubyConf 2021, Jake Anderson of Weedmaps explores the concept of Domain Specific Languages (DSLs) to enhance development workflows and reduce the burden of writing boilerplate code. The presentation covers the motivations behind creating DSLs, particularly in repetitive workflows, and provides a framework for building effective developer-centric tools. Anderson begins by explaining the definition of a DSL as a language optimized for a specific problem rather than a general-purpose language.

Key Points Discussed:
- Understanding DSLs: The talk establishes what a DSL is, comparing it to libraries in programming. Libraries encapsulate reusable behaviors and can be seen as DSLs tailored to common tasks in programming.
- Rate Limiter Implementation: Anderson introduces a practical example of a rate limiting mechanism and demonstrates it in action. The rate limiter ensures that certain operations (like API requests) do not exceed defined thresholds.
- Constructing a DSL on Top of Rate Limiter: One of the main focuses is on how to build a DSL on top of the created rate limiter to manage API calls more intuitively. The idea is to provide an easy and consistent way for developers to implement rate limits in their applications without worrying about the underlying complexity.
- Global vs. Local Rate Limits: The discussion includes the differences between global and local rate limiting strategies, and how they can be implemented within a DSL. Anderson highlights the potential pitfalls of nesting different limiting strategies and the benefits of creating a clean interface for developers.
- Developer-Centric Design Principles: The talk emphasizes principles for effective DSL design, such as:
- Only building a DSL when necessary.
- Focusing first on developer usage before functionality.
- Ensuring consistency in usage across the project.
- Maintaining a clear and focused scope for the DSL's purpose.
- Simplifying error handling associated with the DSL.

The presentation concludes with a call to action for developers to think critically about their use of DSLs, encouraging them to prioritize clarity and usability in their designs. Key takeaways include the importance of understanding developer needs when constructing tools, as well as ensuring that added complexity does not lead to confusion or misuse, ultimately fostering a better development experience.

Writing boilerplate code can be draining. Breaking things out into classes and modules doesn't always make this process more enjoyable either. At Weedmaps, we've developed a suite of Domain Specific Languages (DSLs) for the vast majority of our workflows. This includes things like query objects all the way to crawlers. Done right, these tools allow team members to accomplish more with less. Done wrong, team members would have been better off writing everything from scratch. This talk is meant for those who want to build better tooling around repetitive workflows. It's also for those that want to better understand how some of these tools work under the hood. Embrace the magic!

RubyConf 2021

00:00:11.920 so we've all been there where product might have come to us and said hey we want you to build this thing
00:00:17.920 they're freaking out a little bit because they think it's going to be really complicated and it turns out you know you've built
00:00:23.119 some tools around that already and it's not as hard as they think it is but we've also been on the flip side of
00:00:29.279 that to where they think something is going to be really simple and easy to build and uh you might not have great tooling in
00:00:36.000 place or maybe the tooling you have in place is let's just say less than ideal you built some weird stuff so
00:00:42.239 it's more difficult so this talk is more centered on trying to get us to the point where in the the
00:00:47.920 former of that group um i am jake anderson i am a senior
00:00:53.360 software engineer at weed maps i'm currently on the live menu team
00:00:58.399 and so the things that we do are menu management menu curation external apis and pull-based crawler
00:01:05.519 integrations but we have a whole bunch of teams at weed maps this is my first time at rubyconf so
00:01:12.159 super excited to to be here what is weed maps for those who aren't
00:01:17.520 familiar we are the leading technology company and software infrastructure provider to the cannabis industry
00:01:23.600 so think all things cannabis all things read maps
00:01:28.960 we are actually uh having a party tonight uh for the entire conference so be sure to rsvp to that
00:01:35.920 it's from 7 to 10 p.m at the great divide it's at the bottling hall off of brighton boulevard there are two great
00:01:42.720 divides so make sure to go to the right one the bottling hall is the correct one
00:01:48.640 it's going to be open bar dinner should be a lot of fun that qr code should take you to the rsvp
00:01:55.040 um but if you can't scan that you can go to rubycomp.org on the schedule on the tuesday page
00:02:00.479 scroll all the way to the bottom and you can rsvp there
00:02:05.759 we are hiring at weed maps so if you're interested definitely come hit up our booth there's a code challenge there if
00:02:12.239 you complete it and pass you actually move to the final phase of our interviews so um definitely come do
00:02:19.840 that if you're looking for a new job all right getting to the talk so today
00:02:25.440 there's a few things i'm going to try to cover um first of which what is a dsl domain
00:02:30.640 specific language sometimes we've heard that acronym dsl don't even know what it means so we're going to just level set a
00:02:37.040 little bit there as to what a dsl is we're actually going to dive right in after that to building
00:02:43.360 our own dsl it's going to be on top of a rate limiter we're using this dsl in
00:02:48.720 production or a version of it in production at weed maps and it's serving us pretty well at least i like to think so um and then
00:02:55.840 we're going to kind of discuss a little bit about what we did why we did it how it kind of worked the way it did
00:03:01.599 and then applying some of this knowledge towards kind of future projects or any things that you might be doing
00:03:09.360 so what is a dsl what's a domain specific language
00:03:15.920 some of you might recognize this name martin fowler um he defines it as a computer language
00:03:21.200 that's targeted to a particular kind of problem rather than general purpose language that's aimed at a kind of
00:03:28.239 software problem um to me i didn't necessarily understand this right away
00:03:34.159 but as soon as i started thinking of in terms of hey libraries libraries are all dsls
00:03:39.840 they took ruby and they had a behavior that they found that they wanted to try to repeat
00:03:45.040 and so they created a library around it and essentially formed a dsl as to how you can use ruby to accomplish a certain
00:03:51.519 behavior or task sometimes concerns or modules would also be considered dsls at the end of the day
00:03:58.640 it's really any reusable behavior that you might have throughout your code if you find that you're using something
00:04:05.200 more than once chances are there might be a good opportunity for a dsl there um not always but um it does kind of hint
00:04:11.760 at doing that but that's enough about like dsls at a
00:04:17.280 high level let's just kind of dive right on into some of the code hopefully build out something that makes sense and um
00:04:29.360 for those who aren't familiar with rate limiters it's a way to limit a piece of code execution over a period of time so
00:04:36.160 say you only want something to occur 100 times per minute or 10 times per second a rate limiter is a way that you can
00:04:42.960 accomplish that some examples of rate limiters may be api rate limiters probably the most
00:04:48.880 common of hey stripe only allows you to hit them every so often and so you might implement a rate limiter on your side to
00:04:55.600 make sure that you don't go beyond those limits and get 429 errors but other examples of rate limiters
00:05:01.520 might be login attempts maybe you don't want people to brute force log into your application and you want to rate limit
00:05:08.000 and say hey you're only allowed to try to log in 10 times per minute other examples might be coupon code
00:05:13.199 redemptions or text messages imagine you had a job running in the background and
00:05:18.639 that job happened to run a thousand times on accident you just sent a thousand text messages to one person
00:05:23.759 right it'd be great to have maybe a little bit of a rate limiter in there to be typical to be able to say
00:05:29.280 only send messages to people once per hour which would prevent something like that from happening
00:05:38.479 so this is going to be a little bit of a demo of a rate limiter in action i have a little method called puts dot and it's
00:05:44.960 going to print a little dot onto a screen on the left side it's going to put that
00:05:50.320 dot every half second and on the right side it's just gonna put as many dots as it possibly can but they're gonna share
00:05:56.479 the same rate limit of five dots per second essentially is the way to think about it so pay attention to kind of the cadence
00:06:03.520 of the left side and then see on the right side how when things come in they
00:06:08.720 really take over so go ahead and take a look here two separate
00:06:13.759 terminals so here on the left side we've got those dots kind of printing to a screen every half second
00:06:19.440 and then the right side kicks off and starts going but you'll notice it does pause right because it's it's hitting
00:06:24.880 that limit it knows it needs to stop i made the five requests the one on the right finishes and then the one on the
00:06:30.960 left finishes i'm going to run this one more time and pay attention to the left
00:06:36.560 side you'll notice that it's very sequential it's going um at a very steady pace and then as
00:06:44.080 soon as the right side starts it it really slows down and that right sides really starts to eat everything up so
00:06:50.080 we'll go ahead and watch this again
00:06:55.680 you can see that left side really starting to slow down and then the right side will finish up
00:07:00.720 here and it goes right back into its regular cadence so we'll talk a little bit more about
00:07:06.000 that just wanted to call it out don't want to run this again cool perfect
00:07:11.280 so the rate limiter class i don't want to dive specifically into rate limiters
00:07:16.800 i will provide a gist at the very end if you want to see how the rate limiter itself is implemented the focus here is
00:07:22.880 building a dsl on top of the rate limiter and we'll talk a little bit about why we're doing that so all we
00:07:29.360 need to know about this rate limiter class is it accepts the first argument is a bucket name and that's just the key
00:07:35.919 that we're looking this rate limit count up in to be like oh hey this has happened one time or 10 times or you
00:07:41.840 know 42 times it's all going to be in that bucket namespace it also has some
00:07:47.680 other arguments this particular rate limiter is backed by redis which allows us to have both of those processes
00:07:53.840 running as we saw in the previous slide still respecting that same rate limit
00:07:59.360 the second thing to call it here is this within limit method that accepts a block and anything
00:08:05.520 that uh that within limit just kind of wraps whatever block you pass to it within the
00:08:10.639 rate limited functionality again just comment that out just to focus more on the dsl layer um
00:08:17.840 but the within limit if you are within that limit that block will run immediately otherwise if you are not
00:08:24.479 within that limit it'll enqueue a sleep it'll just wait a little bit longer and then it will try again so
00:08:31.120 it'll more or less put it on hold and then once that rate limit becomes available again it will retry as we saw
00:08:36.880 in the demo so one way we can use this
00:08:43.200 is like this we have a rate limiter class here um
00:08:48.240 at the bottom that we've memoized so we don't have to instantiate a new one every time
00:08:53.600 and in each one of our methods thing one thing two we call our rate limiter do the within limit and kind of do our
00:08:59.760 thing and what's nice about this it doesn't matter if you're calling thing one you're calling thing two um you're always going to be respecting
00:09:06.480 the same api limits across your entire application
00:09:13.040 but as we saw in the demo that lefts are the right side that had a
00:09:18.480 the no limits in terms of how how much it was executing was taking up a lot of that bandwidth
00:09:24.399 and but it still worked um but is there a way that we can prevent
00:09:31.760 one particular class from just taking over the entire rate limit and if if
00:09:38.240 we're only allowed to do 100 per minute and one class is taking up everything well then nothing else can get
00:09:43.440 everything done which is less than ideal so can we build a way to to kind of handle that scenario
00:09:50.160 and so right here we're starting to introduce a behavior around rate limiting and we're going to call this uh
00:09:56.240 local versus local or global versus local rate limits
00:10:02.240 and it might look something like this we have uh thing one and we've got a rate limit or local uh
00:10:08.640 and a rate limiter global and then we're doing some nesting to we're like hey we're going to first try to get the local rate limit and then if we have
00:10:14.880 that we're going to try to get the global rate limit and then we'll execute our block
00:10:20.160 but don't feel great about this right it leaves some weird nesting
00:10:25.600 um and to me i don't like this it seems like kind of some gross ruby code like coming
00:10:32.720 in this class i'd be like i don't really want to touch thing one or thing two um
00:10:37.920 some of you might have caught it but thing two actually grabs the global lock before it grabs the local lock
00:10:44.800 and that might be a problem if you have something continually grabbing in that local or the global and then not
00:10:50.720 actually being able to do the local because it's using your global rate limit but not actually executing
00:10:56.320 anything so nothing else can do anything and neither is your class so
00:11:01.600 it it seems like there's a it's easy to kind of misuse this global versus local
00:11:06.880 limit there's also a bit of setup right you kind of have to set up this global limiter instead of this local limiter
00:11:13.680 and then do the nesting and so we're kind of seeing a pattern here or behavior that
00:11:19.920 maybe would be good to wrap in a dsl
00:11:26.079 but what might that dsl look like and a great place to start when you're thinking about writing more of that
00:11:32.000 developer-centric dsl is how do we want to use it
00:11:38.320 and then we'll focus on how do we want to implement it so focusing on the developer journey and then figuring out
00:11:44.959 the implementation i also try to recommend not going with the first pattern you think of it might
00:11:51.360 be the best one but it might also not be the best one if you have other people on your team definitely reach out get their
00:11:57.360 opinions and thoughts and so we're actually going to show two different options and we're going to implement both of those options
00:12:04.800 just so you can kind of see different worlds so one option would be to do something like this
00:12:10.480 to introduce a method within a class called within rate limit you can pass that a block and then it will
00:12:16.160 automatically take care of the global and local rate limits for you this is great in the scenario to where
00:12:22.079 you always need this function to be rate limited say you're using an external api and you
00:12:28.800 need to always guarantee you're hitting it at a certain interval this is a way that you can guarantee that and no one
00:12:34.000 else in the code base can call this method without being subject to that limit
00:12:39.519 however what if you don't want that case what if you have thing two and sometimes you want to call it rate
00:12:46.320 limited but sometimes you don't so in this case we can do a little bit of meta programming to introduce this
00:12:53.040 concept of a rate limited method prefix to where if somebody calls rate limited
00:12:58.880 thing two or rate limited thing one it will automatically know to wrap that
00:13:03.920 in our rate limiter and execute that function
00:13:10.800 but you might have noticed there was nothing around configuring these rate limits right like how are these things supposed to know i'm i need to execute
00:13:17.920 one per second or one per minute there's a ton of different ways to to do this but one way that we're going to do this
00:13:24.240 in this example is through a module and class level configurations
00:13:29.760 if you're familiar with rails you might have done this with device to where you hop in the ruby class the user class you
00:13:35.680 write devise authenticatable all those types of things we're going to mimic that same type of functionality to where
00:13:41.360 you can define class level configuration and in this case we're calling it a rate limiter you can pass it a bucket name so
00:13:46.880 in this case i'm calling it my service and then you can have global and local rate limits that
00:13:54.160 that respect that so like in this case here we've got a global rate limit of 100 per minute but we're only allowing
00:13:59.600 each individual service to call it once per minute
00:14:05.199 so yep we define our global local limits our method will refer to these limits
00:14:10.480 that within rate limit that we were showing in the previous slide we'll look up these on the class
00:14:16.560 and then what's nice here is these definitions are at the top of the class so when you hop into any ruby class you
00:14:23.279 can very quickly see oh hey rate limiting is in here and i i can go ahead and utilize that in whatever way that
00:14:29.600 we've defined to to do that but let's get coding um
00:14:36.320 so we're going to start with the the module and kind of like this base configuration and uh some people might become come
00:14:42.560 from the rails world you've got active support concern you can do included do blocks we're kind of skipping that a little bit and keeping things pure ruby
00:14:49.920 and you can accomplish it by doing something like this classes have an included method on their
00:14:55.760 on their main class and you get the the class that's getting inherited and you can extend that out so that's
00:15:01.920 essentially what we're doing here is we have the class methods and everything that's in that class methods module we
00:15:07.839 want to add as a class method to whatever this module is included into so that's our rate limiter function
00:15:14.480 right there if we go to the on the previous slide we got that rate limiter definition there at the top so that's
00:15:20.079 where that's coming from is in that rate limiter method definition and we're also setting a memoized value
00:15:28.079 of rate limits that will be able to be referenced through any instance of this class
00:15:37.440 so yep create the methods extend the class and note this doesn't actually do anything
00:15:43.279 yet um so this works like if we did everything in our code before it's not
00:15:48.399 going to break and you could ship this to prod but nothing is going to be rate limited it is literally just gonna keep
00:15:53.759 passing things right through that within block and really do nothing for you um which is obviously less than ideal no
00:16:00.800 point of having a dsl that does nothing for you so let's go ahead and add some functionality to that method
00:16:10.160 and it would look something kind of like this right to where we have this we're going to try to grab that local limit
00:16:15.519 now we're going to try to grab that global limit we're going to pass that block all the way through and some of these methods here at the bottom are
00:16:21.600 more or less just helper methods to to generate all that stuff out but some of you may have noticed it's
00:16:27.680 more or less mimicking the same functionality we showed previously right to where we have that rate limit or
00:16:32.800 local within limit rate limit or global within limit and then we have that block right there in the middle
00:16:38.800 but we've abstracted that away so now no one needs to know like how am i supposed to do the global first am i supposed to do the local first like don't even worry
00:16:45.360 about it anymore we took care of that just call within rate limit and you're going to be good to go
00:16:53.040 again all of this will be available in just afterwards so don't feel like you need to take a whole
00:16:58.800 bunch of shots but we talked about meta programming
00:17:04.400 right what about that option two where we want to have that rate limited prefix like sometimes like that seems kind of
00:17:10.799 cool like meta programming ruby like that sounds kind of fun let's let's uh how might we do something like that
00:17:16.480 um for those of you who aren't familiar ruby has a method called method missing and if you're you try to call a method
00:17:23.679 called think3 we never had a thing three if we tried to call that what's going to happen is method
00:17:29.280 method missing is going to get called and it's going to pass that method name and any arguments that you pass to that
00:17:36.320 non-existent method and you can then define those methods or pass through those methods on the fly
00:17:43.120 hence the whole meta programming and so really what we're doing here is we're
00:17:48.480 checking to see hey the method that got called does it have this rate limited prefix on it if it does we care about it
00:17:55.600 and we're going to do some stuff otherwise it doesn't we're going to call super pass it back up the chain and some other method missing can get to it but
00:18:02.720 in this case if we had rate limited thing 2 we know oh this is something we want that we
00:18:08.720 care about so we're going to pay attention to it uh we're then going to wrap what we got
00:18:15.520 within that uh within rate limit block and then call that method without the r
00:18:20.720 or we should be say it should say with the args uh or without the prefix passing yards okay no i wrote it right um
00:18:27.280 so it looks something like this um that within rate limit we defined in that previous slide we can just reuse
00:18:34.000 that method right here it might be hard to see the little yellow brackets but uh that within rate limit is uh getting a
00:18:40.880 little block there and what that block would look like is something like this we can do a public send
00:18:47.600 to whatever the method would be without that prefix and so again if we got rate
00:18:52.720 limited thing two what we're doing here is removing rate limited underscore from that method
00:18:58.400 name and just calling thing two we're then passing through any arguments that we got to that for those of you who
00:19:05.520 aren't familiar with that dot dot dot syntax javascript has something very similar it allows you to essentially say
00:19:12.000 everything else that you get here just like pass it on through as these dots and so you can kind of go through the dots that
00:19:17.919 are above and below the method signature however or more just to show that there's other things happening in the
00:19:23.520 file not to kind of confuse that one other thing to call out here is we're using public send
00:19:30.160 it's best best practice with metapro meta programming generally to
00:19:35.200 only call public methods if we had this as a regular send we could access or anyone can access a lot of our private
00:19:41.120 methods which is less than ideal so by using the public send we can guarantee that we're only using the public surface
00:19:46.559 area of whatever this module is getting included into
00:19:52.880 lastly it's generally not a good idea to add multiple ways to do the same thing right um we implemented this meta
00:19:59.360 programming way which is great it's fun um it's a good use case but then we also had this other way
00:20:04.799 if you have two ways to do something people are going to use two different ways to to do it right and then you have
00:20:10.960 two different usages within your app and you've got you know one team that uses it this way one team that uses it that way and so when you want to introduce a
00:20:17.280 change you now have to support both use cases so it's typically best to stick with one
00:20:23.120 we did both just to kind of show the different examples of how you could implement a dsl like this
00:20:31.840 but can we take this one step further what happens if we have a service that
00:20:37.679 doesn't want a local limit we only care about a global one like you know what this one can eat up all the bandwidth it
00:20:43.600 wants i don't care or maybe it's the opposite maybe i only want to run this one locally or what if i forgot to add
00:20:49.600 in those rate limit definitions like it'd be it would really stink if all of a sudden the app went down right
00:20:56.400 so it'd be kind of cool to introduce a concept to where if we don't get a bucket name or if we don't get a rate we
00:21:02.400 just pass everything on through and just call it as if there's no rate limits there at all
00:21:08.400 so like how can we do that how can we execute that block
00:21:13.760 so if we go and revisit this rate limiter class i know there's a lot that's abstracted away here
00:21:19.760 we can we can look at this within limit call and right now it's getting past that block but what we could do is something
00:21:26.320 like this to where we can immediately call that block if it's
00:21:31.679 unlimited ignore the rate limiter altogether i don't want to touch redis i don't want to do any of that immediately
00:21:38.240 call that block but what is a limited mean and we can call unlimited to just like
00:21:44.480 hey if you didn't give us a bucket name or if the rate that we got was zero
00:21:49.600 it's considered unlimited just like pass everything right on through rate limiter doesn't exist um
00:21:54.880 we can also use the dot zero here because if you're not familiar in ruby if you call nil.2i which is kind of what
00:22:01.039 we could be doing in this rate the top you get zero
00:22:07.440 so yeah now we have a conditional rate limiter that's officially supported so if somebody included that module for a
00:22:13.120 rate limiter and didn't add a global or local it's like nothing ever happened if they add the global we're going to
00:22:18.559 respect the global if they have the local we'll respect that but if they add both we're going to respect all the
00:22:24.840 things so here's the end result uh as promised this is a qr code to the gist
00:22:30.720 um to you where you can see this rate limitable module you can also see the
00:22:36.240 rate limiter itself um it's just uh
00:22:41.280 github gists and commits happen on github it's also on my badge which i am not wearing um but you can always find
00:22:47.760 me after the after the talk if you um want to learn more and you can't find it um
00:22:56.400 so that's great and all we built this dsl but like what did we learn by doing this like what was the value and how is
00:23:02.400 this even developer centric and really it comes down to five principles or rules like i hate the
00:23:08.640 concept of rules or principles because it means that something always has to be done a certain way and i don't think that's true these are just some of the
00:23:15.120 principles that i like to follow when building dsls and we find at weed maps tend to be more developer friendly
00:23:23.120 first things first is do we even need it don't build a dsl unless you need a dsl
00:23:30.000 rule number one right uh yes it's it's cool it's fun building things in ruby is
00:23:35.440 always fun and interesting but if you don't need it don't build it in our example we
00:23:41.760 kind of saw the beginning we had that weird nesting and then you can kind of put those in different orders and so it
00:23:47.279 kind of made sense for us to build a dsl more from the reliability standpoint of reusability we don't want people to
00:23:53.600 misuse our rate limiter and so we've kind of found a good need
00:23:58.640 to to create that second rule uh begin by thinking of the
00:24:06.159 developer usage first and then think about the functionality so what we did
00:24:11.919 right is we're like okay how would how could we how do we want to use this and we have
00:24:16.960 that within rate limit method we have that meta program method and i'm sure there's a whole bunch of ones that i'm
00:24:22.559 not thinking about that aren't on this slide if you've got ideas definitely come hit us up too we'd love to hear them um but we focused on the developer
00:24:29.919 usage first and then we figured out how to make that happen typically that's the the best way to do
00:24:36.720 things if you can start with the developer
00:24:41.840 you also notice that as i kind of mentioned here rule number three explore multiple patterns the first one
00:24:48.880 might be the best one it might be the worst one i didn't come up with the meta programming example until i was
00:24:56.000 doing this talk and maybe that would have been a better way to to do this rate limiter overall
00:25:01.679 so um by by taking the time to explore more patterns you explore more possibilities get more people involved
00:25:08.400 you know you know get the whole team chatting about it but at the end of the day favor consistency
00:25:14.240 don't have more than one way to do the the same thing try to be consistent with your dsl it's
00:25:21.039 it's great when you know a dsl enough and can pop into any class and oh i see it's got that
00:25:28.159 rate limitable module in it i know exactly what i need to do to rate limit stuff
00:25:35.120 rule number four keep it focused the rate limiter we could have
00:25:40.720 introduced functionality that's maybe specific to api clients like http or we could have made it specific to
00:25:47.120 text messages but we kept it super generic you could use what we just did for apis you can use it for text
00:25:54.640 messages you can use it for any of the examples that i gave earlier so we kept it really focused on a small piece of
00:26:01.279 behavior and and didn't really extend it any more than we needed to and and so if you find
00:26:06.799 that you're like oh maybe there's something that it would be cool to implement this thing now just just wait just don't do it just
00:26:13.840 give it some time it's always easier to add functionality later than to try to rip functionality out that you realize
00:26:19.440 that you don't actually want to use and the last and final rule arguably one
00:26:27.039 of the most important rules is try making error tracking as easy as the
00:26:32.159 implementation i think one of the reasons people don't like dsls is because of the meta programming
00:26:39.279 and some of the magic that happens behind them to where when you know you've got honey badger raising an error
00:26:44.640 and you're like oh my gosh i have no idea where this is coming from it says thing three is not defined but like i'm
00:26:51.039 not calling think three anywhere in my app and so how do you make some of that a little bit more user-friendly when
00:26:58.080 things inevitably do go wrong is is super important so make sure to think of that journey
00:27:03.600 as you're building some of these dsls in our method missing for example we could have raised a custom error there
00:27:10.320 that the method wasn't defined pointing more towards our rate limited dsl so that somebody knew that that to
00:27:17.360 look up a rate limited three thing three and not just think three
00:27:26.080 so parting thoughts like where do we go from here i've got some examples of things that um you can do to just take
00:27:32.960 this down and add some functionality to it right hopefully you learned enough to kind of think of
00:27:39.440 what might make good dsls how might i build that in a developer-centric way but if you want to just start with this
00:27:45.039 rate limiter and potentially build out some more functionality on top of that and kind of wet the pallet a little bit
00:27:50.399 here's some ideas on things you can do one idea is to move the configuration to
00:27:56.000 something global maybe you're creating a gem that needs to use rate limits and
00:28:01.360 you want people to include your gem but be able to configure those rate limits so moving that type of configuration to
00:28:07.760 global might be something that you can do rather than doing it on the class itself another option might be to minimize the
00:28:15.039 amount of methods that you have defined uh even more to prevent method bloat so every single method that we add to our
00:28:21.760 module gets included in the class that's it it's included in um so the more
00:28:27.200 methods we have the more we're bloating up that space and have a potential to collide with anyone else that's writing
00:28:33.520 methods within that class and so it's typically also a good idea to keep your method names very small and concise and
00:28:40.480 specific to what your particular dsl is doing
00:28:46.880 but more on like the the rate limital rate limiter configuration side of things is maximum retries so in
00:28:54.240 our rate limiter we are using ruby's native timeout functionality to where if things don't complete within five minutes just like
00:29:01.279 kill it raise an error like make sure it's done and go away we don't want something just getting stuck in an
00:29:07.360 infinite loop but maybe you want to add functionality to her like hey like you could i want you to loop as many times
00:29:12.799 as you need but don't go more than a hundred like if you've done this a hundred times just like move on error
00:29:18.799 error out like re-enqueue this to try again in an hour or something like that um that's a great thing to try to add in
00:29:26.240 another configuration option might be delays so the rate limiter example that you'll see in the gist
00:29:32.240 it when it encues and it really just sleeps for a second and then it tries again
00:29:38.240 and so maybe you want to sleep for five seconds or a minute and you can make
00:29:43.360 that a configuration option to customize the amount of delay or sleep between those retries
00:29:51.840 the last one i'm not crazy about but you could do it if you really wanted to is allowing for people to override those
00:29:56.960 class level definitions on those rate limits so when you're doing something like uh with rate limiter being able to say like
00:30:03.840 actually for this method i want the rate limited to be this and not respect the global limits you can do that
00:30:09.919 so that's another way that you can extend this dsl out
00:30:15.520 but at the end of the day build for what you need now not what you think you'll need later down the road it's much
00:30:21.440 easier like i said to extend functionality than rip things out that you no longer want or need
00:30:29.840 thank you very much and if we have time for questions i'll happily take them if not i will be at the weed maps booth so
00:30:47.840 yeah so let me see if i understand the question so like what are we doing to ensure that we're actually respecting
00:30:52.880 our rate limits yeah so um we've got partners that are pretty loud
00:30:57.919 so they'll usually tell us if we're not respecting those limits we'll also start seeing those 429 errors
00:31:04.880 come through um and so we've got some monitoring in place that tells us and we can monitor and say oh hey we're seeing
00:31:10.640 an increase in 429s um that's actually how we you know the whole local and global thing we originally had that
00:31:16.640 flipped and uh we figured that out pretty quickly that that was the wrong way to do it um and so uh we flipped
00:31:23.039 those around and realized uh yeah that was probably not the smartest but because we had that monitoring in place
00:31:28.559 we saw the 429s people were reaching out to us like hey you're not really respecting these limits so we made that fix and we're able to kind
00:31:35.200 of prove that it was working accordingly but it can get really tricky to test some of these things uh when you're
00:31:40.480 working with you know redis and things across processes for sure well that's uh i mean i'm more thinking
00:31:46.799 of this on the fly i'm not saying this is a good answer um but you know there's always uh arrays right you can have your
00:31:53.279 api config can be based off of arrays and you can just kind of go through that list starting with the first and if that
00:31:58.480 one fails implementing retry logic to try the next one in that list so
00:32:05.360 you know just one way that you can kind of implement that dsl and potentially within the configuration right you can
00:32:11.840 say the index at which you want prefer that api key to be used but
00:32:17.279 yeah that's definitely a tricky topic for sure all right well thank you very much if you have anything else come find me