 So, thanks for coming. The title of this talk is Building Generic Software. My name is Chris Salisberg. I've got a lot to talk about, so I'm going to go through some of the easy parts quickly and then the hard parts of it more slowly. So just quick, some points about me. My handle is Shiyoyama, if you've seen that name around. I live in Tokyo in Japan, which you can see in the background of this slide. This is Tokyo. Don't be fooled. I'm not Japanese. That's my stupid icebreaker joke. I'm a Canadian resident from Montreal. I work at a company called Dejica, based in Tokyo, and in the open source world, I'm the author of a number of gems, most well-known one is called Mobility, which I'm going to talk about a bit. And in my free time, I write about stuff like the module builder pattern, which you may have heard of, on my blog, digima.com. Okay, so the title of this talk is Building Generic Software. So the first thing we're going to do today is I'm going to introduce this idea of generic software. What is it? Why would you care about that? And then based on that idea, we're going to look for a problem. And we're going to use that problem as the basis for kind of building some generic software, which is actually going to be a framework. And then after that, we're going to see if we can learn some lessons from that exercise. Okay, so generic software. What's generic software? So I did not invent this term. As far as I know, this term was coined by Jeremy Evans, who you may have seen because he gave a talk actually here yesterday. Jeremy Evans is the author of a well-known ORM called Sequel, similar to actor record, and also another well-known gem called Rota, which he actually talked about here in 2014. And so I've been following Jeremy's work. He's really got some really great gems out there. And so a while ago, I was kind of looking for inspiration in this open source work I'm doing. And I found this quote, which is like buried in these slides from 2012, where he gave a talk on the development of Sequel. And he said in this quote, I'll just read it to you. He says, one of the best ways to write flexible software is to write generic software. Instead of designing a single API that completely handles a specific case, you write multiple APIs that handle smaller, more generic parts of that use case. And then handling the entire case is just gluing those parts together. When you approach code like that, designing APIs that solve generic problems, you can more easily reuse those APIs later to solve other problems. So I thought this was like a great quote, and it really kind of resonated me with the work that I was doing, developing sort of generic components. And so I decided to make this talk, and that's why I'm up here today. But when I started looking into this idea, I kind of found that you can kind of take this kind of general idea of building blocks and go in a very different direction with it, depending on how you apply it. So this is a tweet from September by Gary Bernhardt. He may know, is also a Rubyist. And he says, programming should be like putting Lego together. Just decompose everything into tiny pieces with maximally general interfaces. And that sounds like really similar to that quote by Jeremy Evans. And I mean, you can take my word for it, but Jeremy Evans' work, this works out, this is great software. But Gary Bernhardt said, that sounds great. Then you fast forward a decade, and simple tasks have become wildly complicated, sometimes breaking for reasons that no one understands. And so there seems to be some kind of weird split here, where depending on how you apply this kind of idea of like Lego blocks, you either end up with really great maintainable software, or you end up with this crazy mess that you can't maintain. Here's another kind of data point. This is a comment on a hacker news thread responding to a blog post around the same time September, which was talking about how our software is bigger and bigger doing the same things, but in way more complicated ways. And this commenter says, I put some of the blame on an unhealthy insistence of code reusability and sticking to paradigms. The result is so much code that it's just abstractions piled onto abstractions, piled into more abstractions, yadda yadda. Nobody wants to just write some goddamn code anymore. They include entire frameworks instead of just writing a couple helper functions. And so I can certainly sympathize with this sentiment, right? I think there's a lot of reason. And also if you look at the Twitter feed, the Twitter thread that Gary Bernhardt was on there, he kind of suggests kind of similar things, that you should just write the code yourself rather than requiring all these libraries if you don't really need them. And that's reasonable. But I think it's sort of missing something, right? And so if you imagine that this tree is kind of a giant dependency tree, this is like Ruby gems or something, right? And most of us, most of the time, we're on the leaves of this tree, right? We're building applications. And our applications are not required as software by anybody else. We require lots of software. So we have these branches that go down to these trunks all the way down to the Ruby interpreter. And we're most of the time, we're building these apps on these leaves. And so this suggestion that you should just take these frameworks out is kind of like chopping off the branches from your leaf there. And that's a valid approach. That's a totally valid approach. But I think we should also consider, can we improve this tree, right? Can we make these branches better? Can we make this thing better? And that's what I wanna do today. I wanna talk about this idea. And to do this, I'm gonna use some ideas that date back, way back, like 30 years back basically to the 80s, to before even the internet. And this is a paper. This is a great paper. If you haven't seen it, I recommend having a look. This is from 1988, literally 30 years ago. This is by two guys, Ralph Johnson and Brian Foote. Brian Foote, you may know if you've heard of the big ball of mud. He's the person who coined that term. And the title's Designing Reusable Classes. And this whole idea of reusability is really fundamental to everything that object-oriented programming is about. Stuff like inheritance and polymorphism. Those are there to promote code reuse, right? And also the idea of a framework, which I'm gonna come back. We're gonna actually build a framework is sort of introduced in this paper. So I'm gonna use these ideas. So what I wanna do today is I wanna kind of take some of these ideas that go way back, apply this kind of idea of generic software as kind of our compass to guide us. And I wanna show you that you don't have to end up with code that is not maintainable, that it breaks for no reasons, the reasons no one understands, right? So how are we gonna do that? So generally, when you wanna build sort of generic software, software that is reusable, that people are gonna use in different money, different contexts, you have to start from something specific, at least in my experience, right? You don't just go down this tree by just starting from the base of the tree. You kind of start from the leaves and you work your way down. So that's what we're gonna do. We're gonna start from a specific example and see if we can kind of come to something that's more general. And so since I'm giving this talk, I'm gonna pick this specific example. I'm gonna pick the topic of translation. That's just because I happen to have been introduced to, I happen to, translation happened to have been the entry point for me into the Ruby community. So I was working on a translation platform and that's where I started using Ruby. So the idea we're gonna look at is this idea of translated attributes. It's really basically a pretty simple idea. I'm using the I18 locale here. That's not really so important, but you have some global language, right? And like any attribute that you have, like any adder, accessor attribute type thing, you create a talk, a class talk, you create an instance. You set your title to say building generic software and then you can get your title back. And so far, this is just a regular attribute. But then we change the locale to Japanese and I get my title and it's nil. And it's nil because this value, this attribute is not a normal attribute. This is a translated attribute. So depending on which language you are in, you're gonna get the translation for that language and we don't have a Japanese one. So we can add it and then we can get it back. And if we switch to English, we'll get back the English translation. That's basically it. So that's pretty straightforward and how do you define these things? Well, there are actually a number of different gems. They come out all the time. I noticed a new one just the other day. There's a lot of Ruby gems to do this. They all have their own kind of conventions. Of course you have to include or extend some module. But basically they usually have the same kind of interface. You call some class method. It's usually called translates. You pass some attribute names in and kind of like at our accessor, you get some translated attribute and then you can do stuff with that. And the part where it gets a little more challenging is how do you handle the storage of these translated attributes? And this is where we're gonna get into this generic idea moving forward. So there are a number of different patterns to store these translations. And there are a number of different patterns to kind of add features to that, which I'm also gonna talk about. So what do these storage patterns look like? So the most basic way to do this is this idea of translatable columns. And this is really kind of pretty straightforward. So if you imagine we have like a comment model and we have some comments table, we have an attribute named content. If you call the content method by default with this, your ORM, whether it's SQL or whether it's ActiveBuckers, not gonna know what you're talking about because there's no content column here. What you do is you create columns for every language you wanna translate into. So we have ENFRJ for English, French, Japanese. And then you would have to dynamically define some method which would map from a content method to either ENFRJ, depending on which language you're currently in. And so that's one approach. That's kind of easy, it's pretty straightforward, it's pretty transparent, and that works nicely. Of course, you have to migrate many things. Every time you wanna do an attribute, new language, new model, you're migrating, migrating. There are other downsides. So there are other approaches to do this. Another one that's common is called translation tables. In this situation, this allows you to scale more easily to more languages. If you wanna add translations, what you do is you create a separate translation table. So you move your translations off the model table, and now they're on this other table in the language instead of being the suffix on the model column name. It's now got its own column on the translation table. And you have a reference back, so comment ID reference is back to comments. And then you have the locale for which language the translation is, and then you have one or more columns for the translated attributes. And then what you would do is you create an association and when you wanna get the translation, you go through, when you wanna get the title, you go through the translations, find the one that's in the language right now and then grab the attribute from that. That's another way. And there are actually many other ways to do this. So just to give you a kind of taste for this, you can also use JSON and Postgres or recent versions of MySQL support this as well. And then you can just put all the translations on one column. So you have the keys or the languages or the locales and the values of the translations. So these are some of the storage patterns that you could have. And these are kind of like the base layer. And then on top of this, these different gems implement different, what I'm calling access patterns with maybe not the best term for it, but the idea is you kind of add features that go on top with the next layer on top of here. So the most common one is called fallbacks. And if you've worked with IATN, you may know this because for static translations, this is also a feature that's often provided and it's pretty straightforward. It's pretty much what it sounds like. You fall back from one language to another language. So if I create a talk, I set the language to English, I set the title to building generic software. Now if I did not have fallbacks enabled and I set the locale to ENCA, which is the Canadian regional English, this would be nil because I don't have that translation. But if you have fallbacks enabled, the general convention, and you can always customize this, is that you would fall back from the regional language to the base language, or if you didn't have it to the default language. So I would get back the English translation in this case. And just to show you how this works, it's not very complicated, but we're gonna use this moving forward in this talk. What you basically do is you have some fallback locale. So in this case, you have the current locale and then English, and you just loop through, right? So you try one, you fetch the value, however you do that, again, this could be however you're doing your storage. You fetch the value, if it's present, you return it, if not, you go to the next one. That's sort of how it works. Then there are other patterns you can support. I'm not gonna talk about this in detail, but just to give you an idea, you can support things like dirty tracking. So dirty tracking, if you know, is an active map model, also an active record, also in SQL. This allows you to kind of change the value and then see the changes. And this will not work by default with translator attributes because your ORM's not gonna know what that attribute is, so you have to do some magic to make this work. So if I set it to generic software and then building specific software, you would see the change. And in this implementation, you can see that it shows you that the English title has changed like this. And then there are other things you can do, and this is getting more complicated, more tricky. I create a talk within English and Japanese translations, and then I wanna find the talk which in English has the title building generic software. And then this, again, is not gonna work without some magic to make this work. So these are some of the things you can implement. And now what I wanna look at is sort of the most interesting part, is where the different gems actually combine these things using some kind of control flow. And they do this in a generally kind of similar way. So I'm gonna kind of sketch this a little bit. So I showed you this translates method. This is a class method that we're calling. This is like adder accessor for translated attributes, right? So what are you doing in this thing? Well, you're splotting these attributes or whatever, and then you're going through each of them, and you're calling some method to define the accessor, right? And that's what this method would generally look like. It's not really complicated. You're taking the attribute, that's the name, right? So that's like title, and then you're calling define method. You're defining a title method, and then you're defining title equals, which is like the setter method. And so the key thing is these methods in here, right? Then again, depending on which gem you're using or whatever, these may be injected right into the body right here, but you may call it to another method. It doesn't really matter. I just wanna sketch how this works. And concretely, if this is hard to imagine, if my class is talk and my attribute is title, and you just resolve the metaprogramming there, this is what you would actually get, right? So I have method title and title equals, and they resolve to these methods read from storage and write to storage. So the question here is, what do these methods do now, right? So this is like paraphrased from a gem called tracko, which you can look up if you're interested. Tracko is for translatable columns. And so this is if your storage pattern was translatable columns, and you were also implementing fallbacks. So you can see what happens in here is that first we're gonna do these fallbacks, right? That's what I showed you earlier. So it's basically the same thing. We're just looping through these fallbacks. And then when you need to get the value, I'm calling this method column value here, right? And this column value is this column translation pattern that I mentioned earlier. Read attribute, if you don't know, is just an active record, just gets the value of a column. And then what I'm doing there is I'm taking the attribute name, which is title, and I'm pasting on the locale, which is English, and I get title en, which is the column name, and then I'm just giving, I'm saying give me the value for title en. And then I'm gonna return that to this loop above, and now I'm gonna get the translation. So if you look at it sort of from a high level in this kind of diagram view, you've got your talk, this is my application code, right? So I have a class talk, and I'm calling this class method translates, which is in my gem that I'm using. That's kind of the high level interface for the gem. And then what's happening there, like I showed you before, so translates is now defining some methods in terms of some kind of internal methods, which I called read from storage, but they could be anything, and this is sort of the low level implementation. And so what these libraries, what these gems give to you is kind of this package, right? This combination of the high level interface and how they actually do this. So I said we were gonna present a problem, right? So what in the world is the problem? So this is the problem that I wanna look at. This is an issue that was posted to a repository of a gem called Globalize, which you may have heard of. It's the most well-known gem for doing this type of thing. It uses a translation table pattern that I mentioned, and this is one of the authors, Thomas. He posted this about two and a half years ago, and I also have contributed this project. I don't contribute so much anymore for reasons that'll become clear. But he posted this idea, and this was around, so this was like 2016, and as a kind of context for this, this was around the time that Postgres, Rails community was adopting Postgres, and Postgres had these nice features like JSON storage, JSON B. And so what he kind of said was like, okay, Globalize is nice. Globalize, we kind of have these things. We have this part of the code that defines these accessors, like I showed you just now, defines the setter and the getter, and then we have the part of the code that actually goes and gets these values from some association on the table or whatever. And he said, wouldn't it be nice if we could add like a layer between these two things so that we could then swap out that translation table stuff and swap in, say, JSON storage or JSON B storage, right? And he said, wouldn't that be nice? And I actually saw this later, and I'd been thinking about similar stuff, and so I went out and did this, and we're gonna sketch that. But the basic question I want to first thing about is, so how do you make something pluggable, right? So we're talking about translations. This is just to make it concrete, but the real question we're gonna be getting at here is how do you make something that wasn't pluggable, pluggable? And this is actually a really interesting and really kind of deep question, I think. So now we're gonna kind of go generic with this, okay? So what do we do? So we go back to this diagram that I had. We have this high level application code, which is talk. It's calling some class method translates. It's calling read from storage, which is like the internal implementation. We like the translates part because that's common to all these gems, right? But we want to swap this out. We want to kind of cut this part out, and we want to swap in something else. We want to cut out translatable columns or whatever, and then swap in the JSONB, JSONStorage or whatever. But you can't do that, actually, right? You can't do that because it's all hard-coded. Like that code that I showed you earlier, the read from storage is hard-coded to go through fallbacks and then through to this column storage pattern. So it's all hard-coded and coupled in there, so you can't actually do it that way. But what you can do is, you can instead of going down, you can go back up. So instead of the high-level interface saying, okay, we're just going to do it this way, you kind of pass the buck back up to the high-level application code. And then the talk says, okay, well, you do it this way. And then the high-level interface says, okay, and then it continues on. And this is actually a really important idea. And this is called inversion of control. I'm curious, how many people know this term? Okay, like less than half, but some. So I think it's, I surveyed my own team and I was asking about this. A lot of people knew it. I don't know if people know it so intimately. So this is actually from that paper that I mentioned. If you haven't heard of that paper, that's where it was actually coined. Designing reusable classes from 1988. So this is 30 years ago, right? And I'm just gonna read to you where it actually is originally used. It says, one important characteristic of a framework is that the methods defined by the user to tailor the framework will often be called from within the framework itself rather than from the user's application code. The framework often plays the role of the main program in coordinating and sequencing application activity. This inversion of control gives frameworks the power to serve as extensible skeletons. The methods supplied by the user tailor the generic algorithms defined in the framework for a particular application. And you can see that we're using this word generic. So the generic algorithm there in this case would be the high level part where we're defining a setter and getter. That's common to everything, right? But we wanna tailor that for each different storage strategy, each different access strategy. And so this is great. This term is, I think it's hard to understand. But it means what it says, where we inverted the control flow. Instead of going straight down, we went back up. But it's still a little bit unintuitive. Conveniently, there's a much more catchy name. And it's convenient that we're in Los Angeles because it happens to be called the Hollywood principle. And this is great. I wish we had all our terms that were like this. So the meaning of this is, don't call us, we'll call you. So now you'll never forget it, right? And this is what it means, actually. You call back, so you call back to the application code when you need to. And you'll see how this works. So what we're gonna do is we're gonna build a second version of this API. This is gonna be, every API is a second version. So we're gonna do our second version now. So we take the same ideas before. We have a class talk. We call translates with an attribute. But now we have like a keyword argument. And we're gonna pass a class. It's gonna be a backend keyword argument. That's gonna be a class. We're gonna define the class later. First, we're gonna see how we handle this argument. So we're gonna change this class method that we had before. It translates and we were going through the attributes and defining accessors for each. I'm gonna change how we define the accessor. But first I wanna look at how we do this thing, define backend here. So we're passing in this backend, which to think concretely could be like a column backend. And so it's gonna look like this. We're gonna define a method. The method's gonna be called attribute backend, which would be like title backend. And then in the body of this method, we're gonna take that class. It's gonna be like something like column backend, which we don't know yet, but it's gonna be a class and we're gonna create an instance of this thing. And we're gonna pass in self. Self is gonna be an instance of this model. So an instance of the talk and the attribute name, which is like title. And then we're gonna memorize that. So this is probably a little bit hard to see. So let's just make this more concrete. Suppose our class is talk again and the attribute again is title. So we would create without the metaprogramming now. We have the def title backend and then we would say column backend new. We pass in self, which is the talk, an instance of the talk and the name title. And then we just memorize it on some hash. Don't worry too much about the memoization. It's not really that important just so we don't have to keep creating them over and over. And then we're gonna define these accessors. Now, remember that when I defined the accessors previously in the way that we do it in these other gems, we were hard-coded to this read from storage or something, which was hard-coded all the way down to whatever implementation we have. But we don't wanna do that. We wanna make this pluggable. So here's what we can do here. We call title backend that grabs the backend. Then we call these methods. I'm gonna call them read and write. And we pass in the locale and in this case of the writer, the set or the value. So this is gonna be our protocol. And protocols are really important and I'm gonna come back to this. But we now kind of see what this backend class is gonna have to be like, right? So this is kind of the idea. And so now we're gonna define this backend class. And this is, I was kind of, when I was preparing the slides for this, I realized this is kind of a little bit like TDD, so test-driven development. In test-driven development, you write the tests and then you write the code to pass the tests. So we wrote the protocol for the framework. Now we're gonna write the code that satisfies the protocol. So we need an initializer. It's given an instance of the model, like the talk and an attribute. We just assign those to instance variables. And now we need to define this read and write, right? So what are they gonna be? Well, this is actually pretty simple, right? So we have the attribute name, we have the model, we have the language in the argument of the method. So we can just do basically what I did in that second section where I was showing you the code from translated from Traco. Basically we take the language, we append it to the attribute name, which would be like title, so you'd be like title en. And then we, again, we call read attribute on the model. So there's nothing really that complicated here. And this is sort of our column backend. This kind of encapsulates the translate of a column strategy, right? And just to show you in a different way, this is how it would look. So if I have a talk and the talk is called by the method title, then the first thing I do is I say, do I already have a backend? If I don't have a backend, I'm gonna create a backend. I'm gonna pass myself in, I'm the talk and the name title. And then what's gonna happen? Then the talk's gonna say, hey, backend, give me the value for English. And then the backend says, oh, okay, well, hey, can you tell me what's the value of the column title underscore en? And then talk says, oh, that's easy, that's building generic software. And then the column backend says, okay, well, that's your answer, building generic software. And the talk goes off and gives that back to whoever called title. So this is basic idea. It's not super complicated, but the way this is happening is really important. And the key thing here is that this is the pluggable translation, this is the pluggable storage logic that we were after. That's the part that we wanted to cut out. And we have actually managed to cut it out because that backend class, which is passed top down, is what is actually defining that part of the logic here. But there's something missing. So I wanna show what's missing just by sketching a little bit what would happen if you plugged in a different backend. So suppose we plug in here the kind of translation table approach. The translation table approach is a little more complicated. That's the one where I showed you where you had different translation table and you're joining stuff. And I'm not gonna go into detail that because that would take more time. But the key point is important here. So the top part doesn't change, right? The talk still says, hey, backend, the title doesn't know what the backend is. It just knows that it has a method of read. So the talk says, hey, backend, give me the value for English. And then the table backend is gonna say, okay, I need to join this translation table onto this talk model. And so you're probably gonna implement that with an association. So it's gonna say, hey, give me your translations, right? And then the talk's gonna say, okay, here are my translations. And then the table backend's gonna iterate over them, find the one in the language English and then grab the title on that thing and return it. That's roughly what would happen. But that's not actually gonna work because the talk doesn't have an association called translations or called talk translations or anything, right? It doesn't have that association because all we did was just call this method translates on it. We never did anything else. So there's something a little bit missing. We need a hook for the backend to define something on the model. So we need to add something to this method we have. And what we can do is we can add a little bit here where we take that backend class. This backend here is a class again. That's like the column backend. And we call a method setup model. And this is gonna be an added thing to our protocol. And now we pass self, self is now the class, so like talk, and we pass the attributes in. And this is gonna be a chance for the backend class to do some kind of extra logic to the model if it needs to. For the column one that I originally showed, this is just a no op, it's not gonna use this chance. But for the table backend, it's gonna do some stuff where it can say, okay, hey, model class, you're gonna have many translations and pass a class name in. Basically it's gonna create this association so that when it gets the read call, it can then go back to the talk and it knows that the talk will have these translations on it. And as it turns out, this is enough to support all those different translation backends. So if you do that roughly, your protocol is just read, write on a class method called setup model. Your backend can encapsulate everything that you need to know about all these different storage strategies. So you can do column, I kind of sketched a little bit of table, you can do JSON, you can do pretty much anything you can really imagine. And you've isolated the core logic of the setting and the getting stuff. And this is sort of an extensible skeleton. That idea from that paper from 1908 where they're talking about this extensible skeleton, that's sort of what it is. But we're not quite finished yet because we're missing those access patterns. You remember I talked about fallbacks as an example of an access pattern that goes on top there. What happens to those? Well, if you wanna put that stuff in there, there's not really anywhere to put it right now. So you're gonna have to kind of put it into the backend itself. So if you have this backend, we're gonna have to kind of rename it, call in with fallbacks backend. And now we can stick the fallbacks in. And this is basically very similar to the code I showed you in the second section where we were looking at what Trako does, where it couples them together. So you basically kind of added fallbacks and then you're calling down to get the actual, the column value. And so it's all coupled together. And this will work, right? This will work. But the problem is that you end up with a picture of something like this where every pair of storage pattern and access pattern becomes its own backend, right? You're gonna have to support everyone. Probably you're just gonna do all of them into one thing, but that's not really very elegant. So you've isolated the core. You've isolated this top level stuff that's defining these setter and getter methods, but everything else is all together. So the problem is that you've put together two things which are basically different concerns, right? Storage and access are both being handled by this thing we've called backend. And what do you do with concerns? You separate them. So we just separate these and we say, okay, storage, you just do what you're supposed to do. You just do the storage. You don't worry about all these other things. And we add some new thing we're gonna call a plugin and that's gonna do this access stuff, this stuff like fallbacks. And how is this gonna work? So we're gonna have every good API as a third version. So we're gonna add another version. So class talk translates title. We had the backend. That's a required keyword argument column backend. Now we're gonna pass in an optional argument plugins and then we pass an array in there. And this is gonna be an array of what? Of modules. So how does this work? This is the code we had before. I just bumped it down so we have some space. So translates, we're past some attributes, we go through the attributes, we define the accessors, we define the backend and then we do some backend hooks so that the translate table backend can do some stuff. But we're gonna add this plugin logic. So how is this gonna look? This is gonna look like this. And what are we doing? We're not doing that much here. We're just taking the backend class and first we're gonna subclass it. So we're gonna create anonymous class if you haven't seen this before. It's just basically like what you do when you subclass. This is just so that we don't pollute the original class. We don't wanna start adding, including stuff into just some arbitrary class. So we create anonymous subclass and then we iterate through these plugins. Again, these are modules, right? So we're iterating through the modules and then we're gonna say, hey subclass, include this plugin, right? So that's gonna be like column backend include fallbacks plugin. And that's what that's gonna do. And then we're just gonna use further down. It's the same but we're using this subclass instead of the original class. And so how do we then do this? Well, the backend part is gonna be what we did before. We just put it back to what it was before. It just reads the value from the column. It doesn't do any fallback stuff. And the fallback stuff we move into this plugin and the module, and the module is actually very simple. It just has the method read, which pass the locale and it goes through, like I've showed you a couple times before, you go through all the locales. But the key thing here is that we're calling super, right? And super is one of the most underrated methods in Ruby. It's really an amazing thing. And method composition in general is really powerful. And so super is gonna go back up to the backend and get the value from the column. But the key thing is we're gonna decouple these things now. So if you look at it kind of like a diagram from the model, the model's gonna get called by title and then they're gonna call through to the backend. And so we're gonna get this read with something like ENCA and this is gonna go first to the fallbacks plugin, right? And the fallbacks plugin doesn't know anything about how we're storing stuff. It just knows that it has to get the value for this language ENCA or a fallback for that. So it's gonna say, okay, hey, column back in, give me the value for ENCA. And it's gonna call super, right? And the column back in also doesn't know anything about fallbacks. It just knows how to get the value for an locale. So it says, okay, I don't have that one, that's nil. And then the fallbacks plugin says, okay, well, do you have English? Hey, give me English. And then the column back in says, oh yeah, I have that one, that's building generic software. And then the fallbacks plugin can return building generic software. And that goes back to the model, which returns it for the value of the title. And the key thing is that the plugin doesn't need to know anything about storage. And the storage, the back end, doesn't need to know anything about this extra feature that's on top of that. So we've decoupled these things. And so if we put this all together, right? Backends, each solve a generic problem. They solve a problem like translatable columns or translation tables or JSON tables, JSON translations or whatever. And the plugins, each also solve a generic problem. I've only shown you one, because I don't have that much time. But you can do the same kind of thing with other ones. And what's the core? What's left? This is the stuff that glues, that links this all the stuff together. So now I can tell you this is actually a gem. I've kind of sketched what is a gem. It's a gem that I've been working on basically for the last couple of years called mobility. And mobility is a pluggable Ruby translation framework. And it supports all these different types of strategies for storing stuff and for doing stuff on top of that. And this is sort of what it looks like. It's got a little bit more than what I mentioned, but basically the core of what I explained is what this thing does. And it has this back end protocol that I talked about here. And the plugin has that, and everything is decoupled. And they're connected through these common protocols. Okay, so I want to now look at some of the things we can kind of learn from this exercise. So the first thing is this idea of generic software. Generic software doesn't generally live in a vacuum. It really lives in some kind of a frame. And I think when we think of frames and frameworks, we think of stuff that has lots of stuff in them. So Rails is kind of the classic one. We think of Rails, it's got all the stuff in it. But the stuff that's in it and the frame itself are actually different. And so this comment in that hacker news thread that I mentioned earlier, which says like include entire frameworks, right? Kind of implies that the framework has to be really big. And this is not true. Like frameworks do not have to be big. And in fact frameworks are actually, I would argue are exactly the opposite. So what happens when you require a gem like mobility? Not only mobility, but just because I know mobility, right? You get the core. And the core doesn't have very much in it at all. And then what happens? Well, of course you wanna translate. So you're gonna need to get a back end. So you're gonna grab one of the back ends. And then you're probably gonna want some kind of features. So there are plugins for that. But the key thing here is that you don't need to include anything you don't want, right? So if you don't want fallbacks or whatever, you can just not include that. And that's because mobility is a framework, right? So the key thing is like a framework being pluggable also implies that it's unplugable. And that's something I think we don't, we don't think about very much, right? So you can unplug these things. And of course in those gems that I mentioned, if it's got fallbacks bundled into it, you can't take it out. You can, of course, you can add a conditional in there which would then say, okay, if it's enabled and do this and if not, then do that. But that's exactly the kind of thing that leads to code breaking for reasons that no one understands. So any well-designed framework should really allow you to unplug everything. Is my philosophy anyway? And so then the question is, well, what happens when you unplug everything? What's left, right? And so what's left is kind of this. I know the font size is small here, but this is all that we worked through in the last section. So you've seen all this code already. I just put it in one big thing. I put it into a module called Translites. I actually made a gem of this so if you're interested, you could pull it down. It's also an under-shellium of Translites. But it's just kind of a sketch version of what mobility is doing. But the key thing is this is the core, right? This is what's left. If you take away all the backend stuff that we did and you just look at what's left over there, this is sort of the key generic software here. This is what's holding everything together. And it's interesting, I find it interesting to think about what are the Ruby things that are used in here, right? What's used in here? We use really, really core Ruby stuff, like sub-classing, module inclusion, a little bit of metaprogramming, dynamic method definition, not really very much, but those are really powerful features actually, right? You don't need a lot of the fancy stuff, all these innumerable methods that we learned about the other day. They're great and everything, but you don't need them for this kind of stuff. And then also you think about what's not in here. So there's no active record. All these gems that I'm talking about, they're all active record-based things, but the base here is not actually, it doesn't have any active record. There's no active support in here. And this is true also of mobility, right? So mobility supports SQL as an other option for ORM support, and it can do that because it doesn't have active support in the core, so you don't need to include active support. What else? It doesn't have any mention of persisted storage, right? Which is kind of funny because this whole talk started from this idea we're gonna do all these different storage back-ins, but the core actually has no mention of persisted storage of any kind. What else? This is a translation framework, but there's hardly any reference to translation in the core, right? The only reference is really in these, when you're defining these accessors, you need to pass in the language so you pull it from my 18N, but that's not a really very important thing. So this is really, I find this really interesting to think about that the core of the framework actually looks kind of different from what you think it should look like, I think. And so what's left? What's left when you take out everything? Is the protocols, right? And so this is something you learn about when you build a framework, at least in my experience it was, it's like how important it is to build good protocols, flexible protocols, and stick to those protocols. And I don't wanna just be talking about my work all the time, so I really wanna reference some really great gems that are out there. One of them I kind of mentioned earlier by Jeremy Evans, it's called Rota. Actually, he presented it here in 2014. This is a really great gem, it's a little bit like Sinatra. But, and then there's another one here, this is called Shrine. This is a file attachment toolkit, is what it's called, but toolkit is basically like an alias for framework. So these are basically frameworks, right? But if you open them up and you go and lib Rota, you find only one folder, right? There's a folder for plugins and that's all. And that's because the protocol in Rota is so flexible that you can build anything you want out of just the combination of different plugins, right? And Shrine is the same way, Shrine is a file attachment toolkit, it only has two things, plugins and storage. Mobility isn't quite that clean yet, I'm aiming for this, but. So if we go back to this original thing that I was talking about in the introduction about, this kind of, you can kind of go off in two different directions, right? I had that Jeremy Evans quote that was like saying it's ideal for generic software and then, oh, but we end up with code that nobody can understand. And so what is separating these two different paths, is we're kind of aiming towards similar things where we seem to be going in totally different directions. And so the way I think about it is if you kind of put it on two axes, right? On the one axis, you have the kind of the reusability of the software. And the reusability of the software is something that we always want to maximize this, right? If we're building some kind of gem or something that we want to share. We want people to use it in lots of different ways. But the other thing is that I think we don't think about quite as much as this idea of the complexity of the protocol. So if you don't focus like a hawk on this complexity of the protocol, you're going to end up with this. Which I kind of think of as like the Swiss Army knife model, right? And this is kind of this maximally general interface thing that Gary Byrne had was talking about. This is where you basically make it more general, whatever you're making more general by adding stuff to it, right? And this works well up to a certain point, but then you just can't scale it anymore. And unfortunately I think that a lot of our gems, a lot of the stuff we work with kind of veers towards this type of model. And so what have I done today? I hope that I've shown you that there's a different way of doing this. And I think of it like this, right? We started from this idea of a back end. And when we started with this idea of a back end, we had this column back end. It really could only do one type of storage. It couldn't, it wasn't generalizable to other types, but then we kind of added a hook in there. And that then made it possible to do many different patterns. And then we pulled out these plugins and then suddenly it was possible to combine any different kind of thing. And those were small changes. They were increases in the complexity of the protocol. It's true. So that base thing was getting a little more complicated, but only small changes for like leaps in reusability. And that's the kind of thing you're after. And so I kind of think about this as kind of an exacto blade type model, right? Where exacto blades, you can pull out a blade and you can put another blade in and you can do that with many different blades because the interface, which is a holder, handles many different types of blades. And so I really think that we need more of this type of software. And I think that the skills required to build this type of software are kind of different from the skills that are required to kind of like pump out a lot of code in a hackathon or something. And so I think there's a real opportunity for people to get involved in building these kind of simple, very simple components. Because if you spend a lot, you can spend a lot of time building one of these things. If it's gonna be used in all kinds of different scenarios, that's worth the time, right? So I really encourage you, if you're interested, have a look at the stuff. If you're interested, talk to me. I think we need to get more people involved in building this kind of really quality generic software. It'll help our ecosystem a lot. Thanks very much.