 And thanks for coming. Today, I'm going to talk a little about Rails and how awesome it is. And I think part of why it is so awesome and why it's great is its community and the huge ecosystem around it. And when I try to explain Rails to newcomers to the industry or even colleagues, I think of Rails about like this city map, this huge massive city map. It's big and consists of different areas like over there is the active record district and there's the initializer town and all these things. And in our day-to-day work, it can feel kind of intimidating sometimes even. Like you're surrounded by all this functionality and you don't know what's happening and you feel constrained sometimes even. And while it's true that Rails has it certain ways and patterns how things should be done, this does not mean that we cannot find or create our own little areas of freedom. And today, I'm going to talk about some of the areas we explored at 8th Light in order to help us work in a more efficient way with Rails. So at the beginning, I want to talk a little bit about the status quo. Then we talk a little bit about breaking of some things followed by a quick wrap up about general application architecture and then talking about some of the trade-offs. So quick question to the round. Who here knows status quo? Couple of hands, all right, cool. So in contrast to Rails, status quo had a very simplistic approach. They always only played with three courts and while I'm not going to continue today to talk about the banned status quo, I thought coming here from London, I should at least take off a British reference. So we're done with that. We continue with the actual status quo as in the current state of affairs. Two years ago, DHH mentioned at Rails Conf that Rails for him is his proper pack. Like if the world ends tomorrow, he still wants to be able to use Rails in order to write web applications and I think this is a really cool feature and idea behind it because everything comes with Rails. Like batteries included, you can continue or you can start writing web applications without the need of anything else. I'm more of a ultra light backpacking person myself and while I'm not prepared for the zombie apocalypse, I always have just enough gear with me for my current adventure. And this is reflected in the Pareto principle, mostly known also as the 80-20 rule. So for example, 80% of your users use only 20% of your product's functionality or in Rails case, 80% of Rails applications out there use only 20% of what Rails offers as features. And the background of this talk has something to do with Omakasa Rails applications and throughout several years and having done a couple of Rails projects, we felt the same pain over and over again and that is that Omakasa Rails applications lead to slow test suites and this is a problem for us at Aethlite because we're used to quick TDD cycles. We wanna be able to write a little code, run the test suite to get feedback immediately. So this writing a little code and running the test happens, I don't know, five to six times a minute. Sometimes if I'm typing sloppy 10 times and I want this fast and quick feedback loop. Unfortunately, the bigger your Rails code base grows if you follow the regular Omakasa style you lose out on these things and in a previous application where we felt the pain and it was the last application we built in this way, this will be our reference application for throughout this talk and we ended the project with around about 4,800 tests and the full test suite ran in about six minutes and waiting six minutes until you know whether refactoring worked or not is too long in my opinion. So I need my code base malleable in order to make a quick decision. Yep, okay, this refactoring worked, continue, this buggy fixed or it's not fixed. I wanna get this feedback really, really quick. So after having seen that throughout a couple of projects we went back to the drawing board, put on our thinking ads and started to really dig into what is the actual issue and this is me with my thinking ad on and one hotspot we identified was the Rails Buddha process which looks roughly like this at the beginning, the Rails framework is loaded, then it will load all dependencies from your gem file and then it kicks off its initialized bang process which loads all the code from all configured Rails engine and so on and so forth. And during that process something like called eager loading or in the docs it's called or referred to as eager loading kicks in and eager loading requires or is responsible for requiring all code that is inside your app directory for example. And we started to modify our configuration a little bit and in a typical Rails application you see in the application.rb you see a line like this require Rails all, require the whole world and continue. So we changed that a little bit in order to only require the two Rails ties or the two frameworks we actually needed for this particular application which was only action controller and sprockets for asset management. Turned out which I learned after we did this change that there's also already support for this kind of configuration by Rails new. If you run Rails new with a minus minus help flag you get a list of all configuration options you can provide there and it actually allows you to skip a couple of plugins already and you see there skip active record skip action cable if you don't need them you can already tell Rails hate don't generate the new project with these dependencies because I don't need them. And I did run this command with a minus minus skip test because I usually use RSpec for my testing. So what Rails new generates then is an application.rb that looks like this it doesn't require Rails all anymore only Rails which is the bare minimum and then it actually tells you hey pick the frameworks you want pick and choose and it commended out the test unit Rails type already for me because I was skipping that in the previous step. So that's good and well the next thing after requiring the initial plugins there's a line that will look like this bundler require Rails groups in previous versions of Rails it looked a little bit different it required the default and then the Rails nth it has been changed I don't know in which version but this tells bundler to require all gems from your gem file for your current environment. The problem with that is that this line will add a linear load time to your boot process which means the more gems you add the more time will be spent when you load up your application and this is unfortunately a hard fact and a hard truth we need to accept at the moment there is no way around that. So we continued there are a couple of more settings we started to tweak and one of them is active support supports a bare flag in order to say hey don't load everything from active support but only the dependencies that are actually needed in order to boot up Rails and then there are two more settings cache classes in order to yeah keep a class cache if the class has been loaded keep it loaded and then we disable specifically the dependency loading and this is because at that time we were big fans of a thing called screaming architecture which I will go to which we will come back to at a later point at the presentation but as a quick heads up screaming architecture the idea behind that was that it should be obvious for a new person on your team that to know by just looking at the directory structure what kind of application is it and if you take the Rails core base as an example if you generate a new Rails core base you see app in config db lib public and I don't know what domain is represented in this particular application just because I've seen Rails before I have an idea yep that seems like a Rails app and if you drill down a little bit in the app directory there are even more sub-directories which do not really reveal the intent beyond the application itself so what we started doing is we moved everything that was an app into the lib directory with a proper namespace so in this example it was a movie organization application so there you can see there's an edit index and show ERB template there is a movie model, a movie repository and also the movie controller like everything that used to be an app we moved to lib we deleted the app directory all together in order to really make sure that nothing will be eagerly or auto-loaded or looked up anywhere there are a couple of more settings for the application.rb we made sure okay eager load false don't try to load anything and we made sure don't even try to look up any path because we pretty much nulled them by emptied them all good and well until here so we booted up our Rails application no errors whatsoever trying to hit the index page blew up with an exception uninitialized constant movies controller so I mentioned before Rails expects everything to be loaded upfront this is why this pre-loading or eager loading happens in the first place so now Rails can't find our application controller anymore luckily for us there is a thing called lazy loading to the rescue for us and what we did there in the sector as you can see a call to active support, constantize so at that time Rails tries to constantize a controller name based on the route definition we provided there so because Ruby is flexible and awesome we monkey patched constantize more specifically this particular one line you see there like we wrap the existing constantize and say okay well if you try to constantize a controller actually require this controller as well and this made the trick everything worked we could serve requests everything worked fine but it had an interesting effect on our code base because now we needed to have explicit requires everywhere and this was a constant decision within the team but it's also not very Railsy if you wanna say so what I like about it like it makes it obvious how many dependencies my current file I'm looking at has so I refer to this as a manual static analysis like if you open up a file and it has like 20 require statements it gives you at least a hint to well maybe this thing is doing too much and then you can start thinking about extracting things or splitting it up in different ways maybe it's okay but at least you can start having a conversation and think about is this thing doing too many things and with all these settings now configured and we were able to start a new Greenfield project which will be our example one so Greenfield project we had everything at our disposal we could do whatever we want because we were in complete control of what we wanna do and how we wanna approach it so we did all these configuration settings and here's the existing or the previous application again with the six minute runtime of the suite I was not part of the project when we kicked it off I joined the project when there was already 5,500 specs that ran in 23 seconds and while I don't have a percentage here I think this is a pretty big increase, decrease Additionally to that with all the tweaks about like how many files need to be loaded at the beginning loading or testing one controller in isolation used to take around 18 seconds until you knew okay this controller works doesn't work now with the new approach and the new configuration settings we only need to wait two seconds which I think is still pretty good one caveat here though is we made also a choice to not use ActiveRecord at all like we switched to the SQL gem and a repository pattern in that way which you don't need to discard ActiveRecord for that it's mostly about that you can switch your implementations for your persistency depending on the environment you're in so when we were testing we had an in-memory repository in order to help us gain more speed in production of course we use a real repository that actually connected to our Postgres database but we could still have used ActiveRecord for that it's just we switched to SQL no particular reason it was just the way we started working with it it's worth mentioning here though that this adds double burden on us like we needed to maintain two separate versions for each repository like we needed to maintain a Postgres implementation as well as an in-memory implementation but the benefits for that was that we gained a lot of speed for our test suite so it was worth for us investing into this double maintenance the way we did that was with shared RSpec examples that ran for both implementations so we actually only needed to write the test ones but implemented twice basically the second example I have was a res application we took over from a new client and that started with 220 gems and the liberal usage of ActiveRecord the application itself came with 2,600 tests out of which were 730 controller tests and the rest of them were not just plain or Ruby object tests they were a bunch of Cucumber tests I don't know, Capybara tests as well as tests that integrated or verified some elastic search behavior as well as verifying that the side gig job was kicked off so it wasn't just controller and the regular Ruby world and the full suite ran in around eight and a half minutes so we thought, okay, let's optimize it our way in order to gain more speed and running a single controller with the existing application with the require rails all the way took around 14 seconds in that case after we did all the configuration changes and all the require statements because now we actually needed to add all the require statements manually back into the code base which weren't there before because it was a regular Omakasa Rails application we were only able to cut it down to nine seconds still a little bit better but not the benefit we had before and for a non-controller spec it looked a little bit better if we were running a single non-controller spec it took around six and a half seconds and we were able to cut this down at least to around half a second so there was more optimizations that was a better optimization for us than with the controller test but we weren't able to just apply it throughout the whole code base because manually adding all the require statements took a lot of effort I did all the benchmarking for two controllers and at the end of it I had like my Git status showed me I had 150 files changed so because it's a client project we can't build a client for refactorings that we wanted to do for refactoring sake so we would need to find a way to slide it in as we add more features so we couldn't just flip the switch and say yep now your application is faster and we have a better development experience but we could do some projections so around eight and a half minutes was the previous runtime for this week and with the knowledge about like it's not just controller tests and non-controller tests for us we projected numbers that we were able to cut it down to at least five to six minutes which is still fairly long too long for my own taste but a little bit better than before and with all the changes and all the explicit requires and all that not many people would agree with me the main point I'm trying to make here is that launching a test suite should not be a daunting task you should, you want to be able to run your test suite multiple times a minute to get this fast feedback and if you only take one thing away from this talk is split up your spec helpers split your spec helpers in a spec helper that has, that just loads our spec and maybe defines some global test helpers that you want to be using throughout your test suite and a separate Rails spec helper that requires the existing spec helper and also requires the Rails environment this way you have, this is a really low hanging fruit in order to at least optimize all your non-rails tests like if you just test a class that does not require anything from Rails you get a huge speed benefit having said all of that let's talk a little bit about the Rails way so what does this actually mean? In Rails's doctrine we can read that there is the reevaluate convention over configuration we made a very conscious decision in our team that we favor explicitness over implicitness I like to read explicit and boring code like magic code does not, I'm not, I don't really like it that doesn't mean that I don't like Rails but it's just I favor explicit code over knowing that okay something will be loaded and something will be done for me in the background it's worth mentioning that conventions are heuristics they are not rules, you can break them and if you learn something new you might even realize all of this convention we used to follow for three years maybe it's not a convention anymore we should use something else and it's not a bad thing this comes the 80-20 rule applies here again like all these conventions we find in Rails work very, very well for 80% of Rails apps out there but the remaining 20% might need something else and that is fine, it's not bad and it doesn't mean that Rails is not the right fit for them it's just that Rails still offers enough flexibility in order to support the remaining 20% you just need to maybe work a little bit harder to reap these benefits as I mentioned earlier I wanna talk a little bit about general architecture as well and while I'm not, I don't want to bore you with 90s, 70s material about what software architecture should look like there are a couple more recent architectures out there first one is clean architecture which defines some entities and the core of the application that will be orchestrated by so-called use case implementations and then these use cases will be exposed through controllers in our Rails application for example through the web in a similar vein there is a thing called hexagonal architecture coined by Alyssa Coburn which goes in a similar direction like at the core of your application is your actual application and you provide different adapters for different clients like you can have a GUI adapter in order to connect the QT GUI to it or an HTTP adapter to connect maybe a browser to it like think of Rails here and these adapters do not only work for like inbound connections also for outbound connections like you have an adapter in order to connect to your persistence layer Postgres on MySQL or you have an adapter for a REST adapter that connects to Salesforce for example and last but not least in Martin Fowler's book Patterns of Enterprise Application Architectures this is a thing called service layer same idea again you have a user interface that connects to a service layer which guards your domain model which then in itself has access to this it's called data source layer here it's your database at the end of the day and when we look at these three architectures they look very very similar they all have the same idea behind them and it's not something completely new even because if we take the service layer as an example and we focus on one particular area and we zoom in on that it's pretty much a layered architecture again like nothing new to learn here we're back in the future back in the 90s it's fine like new words for old concepts like isolate the application from the outside world that's pretty much it it's important to remember though that Rails is not your application and this is why I was emphasizing on the architectures a little bit and I want to show you an example of a pictures e-commerce platform here again in e-commerce platform we might have a user class that if user logs in we have a customer class representing this particular user and the user can have a basket a basket can consist of a couple of products and each product has a category and while we're checking out we need to select a delivery method as well as a payment method and at the end of the payment process we get an invoice as a PDF download for example and this is our core application like we don't see a controller there we don't see a session hash or any gnarly Rails or web details like this is the core of our application and this goes in the direction of the domain of design a book I highly recommend to read and in order to tie that back to the architecture we saw before let's have a look how that applies to our Rails application that would apply to our Rails application so if you start from top to bottom our user interface is usually a browser and this browser connects through HTTP to our Rails application or our checkout controller and the checkout controller then translates this web request to a thing called our checkout which is our use case implementation in this instance and the checkout process, the checkout use case then mediates between several domain model objects which ideally should just be plain or Ruby objects that define some behavior and then at the end it will create an order which is or which acts as our gateway to our database and this is pretty much it top-down one request comes in and you might have something new in the database how does that tie back to Rails' MVC idea? MVC has become this more of an abstract concept than a design pattern or anything like that and we've seen that a couple years back in the Rails community even where there was talk about oh yeah we should favor fat models over to skinny controllers until we realized okay these models get really really hard to test so we went to the other extreme and said okay let's keep the models really thin and put all the behavior in the controllers which made it really hard to test the controllers and it's not enough to only think or talk about these three buckets because I see this as a balloon that you just squeeze on one end you're not magically removing air or responsibilities from things I mean you're just squeezing it here and it will expand the balloon on the other side so it's important to not think about or focus on skin or fat anything we should focus on a healthy everything because spreading the responsibilities into small chunks will help us create an easier to understand code base like if we only worry about classes that have five to 10 lines it should be easier to understand than following a 50 method long method for example and when I was preparing this talk I had a chat with a friend over coffee and I was going through the topics and areas I wanna discuss with him and he was asking me okay so we did this type of Rails style for a couple of years now and he was asking would you do it again would you still do all your applications like this and I was thinking a moment and I thought well all the pre-loading and loading optimizations yes for sure but the idea with the screaming architecture like removing the app directory and moving everything into lib probably not I think this is utterly overrated because if we think back to the default directory structure I don't necessarily need to know that this is a movie organization database or e-commerce platform for flowers or something like that it's okay to start with okay it's a Rails application and if I want to know more I probably look into lib or if there's a source directory and there's source and then I have my nice namespaced responsibilities laid out there and with the separation of concerns or like separating your application from everything that is Rails specific I actually like the idea to have these physically separated now like my core application is a lib nothing Rails specific should leak into lib everything Rails and web related should stay in app like this makes it not trivial but at least easier to upgrade Rails as well because we don't need to worry about anything that is in lib maybe we wrap some active job dependencies inside lib but this is really about it lib is our application nothing of Rails should leak in there so everything comes with a trade-off right I'm not trying to sell you the promised land here like it's not that magically all of a sudden we will end up writing more and more awesome Rails apps there are trade-offs and there are hard trade-offs you make for example explicitly requires this is something that is not necessarily used in a typical Rails application and if you take back the city example or the city metaphor at the beginning like we put up some big construction sites and just cut through a couple of blocks not without thinking but we need to be aware that it comes with the cost because we need to remember like we work in teams usually and we're not an island right it needs to be a team decision because at the end of like in the end it's the team's effectiveness is more important than our own idealistic view of how an application should be structured because at the end of the day that is a technical detail whether file for control it is in this directory or that directory who cares really would I start every project like this now with this new approach probably not I would wait until I until I feel the pain of having a slow test read and then worry about okay how can I optimize this for example if I would put up a Rails application for people to sign up for my birthday party that I will put on a free tier Heroku Dino scaffold the hell out of it and deploy it and call it a day really it's really thinking about how much maintenance do you expect for this particular problem and how much benefit do you get from all these optimizations and we should remember to not just follow whatever has been done before us question things break things fix them again but break things and question things like take the Leaning Tower of Pisa for example it's a great attraction massive amount of people go there every year is that an indicator that we should build every tower like this now probably not and last but not least or more importantly know your tools like know why you follow certain rules but also know why and when to break them make it a conscious decision on that note I think it's a time all right I will leave some room for some questions but first say thank you for listening to me