Tim Riley

Hanami 2: New Framework, New You

A talk from RubyConfTH, held in Bangkok, Thailand on December 9-10, 2022.

Find out more and register for updates for our 2023 conference at https://rubyconfth.com/

RubyConfTH 2022 videos are presented by Cloud 66. https://cloud66.com

RubyConf TH 2022

00:00:00.120 foreign
00:00:16.100 Such a good description! Would you like to come up here and do the talk?
00:00:20.820 Yes! Uh, hi, I’m Tim. I’m super excited to be here.
00:00:23.760 I live in Australia, where I work at Buildkite. Buildkite is a fast and flexible CI/CD system, and we do all of that with Ruby, which is really cool.
00:00:27.359 As Matt shared, I’m involved in a few open source projects, all in Ruby: Hanami, Dry-rb, and Rom-rb, and we’ll be talking about at least one of these today.
00:00:39.719 I am super grateful and excited to be back here. I had the privilege of coming to RubyConf Thailand in 2019, and this is actually my first time outside of Australia since then.
00:00:50.820 It’s just so great to be back with a crowd of Rubyists. This is not something that I want to take for granted.
00:01:02.699 Since this was my first overseas trip in a while, I did sit down and do a bit of research before I came. I discovered one really cool thing: in Thailand, you celebrate the New Year three times, which is amazing! You have the first of January coming up, of course, and then Chinese New Year later in the month, followed by Songkran in April.
00:01:21.840 This was such a great discovery for me because, with the end of the calendar year coming up soon, I wanted to spend some time today talking about New Year’s resolutions.
00:01:35.400 You probably know what I’m talking about: as we get to the end of December and start at the beginning of January, some of us like to look back and reflect on what we’ve done in the year past. We also look forward and resolve to try and improve ourselves in various ways in the year to come.
00:01:46.620 Now, some people might make fun of this. They ask, why should we need an arbitrary change of date to try and better ourselves? But for me, I’m all for it. If a new year is going to help you take up something new, then why not? It’s a powerful idea: new year, new you.
00:02:01.320 So, since this is a Ruby conference, we won’t be talking about exercise or any kind of things like that. But I do have one suggestion to make, and that’s to think about constructing our Ruby apps in a different way.
00:02:23.400 So here we are—this is the talk: Hanami 2: New Year, New Framework, New You! The timing is great, too, because my teammates and I in the Hanami team released version 2.0 just two weeks ago.
00:02:32.040 I’m really excited about the possibilities that this may unlock, not just for the shape of Ruby apps, but also for each of us as Ruby developers. Now, let’s briefly explore how we got here.
00:02:48.000 Hanami itself began in 2014, and that’s when its first release was made. Hanami was described as a simple, fast, and lightweight web framework that brought a strong focus on testability and maintainability.
00:03:02.280 Meanwhile, the Dry-rb project, where I was working, began in 2015, and we released a range of gems to help Rubyists write more maintainable application code.
00:03:09.900 Then, in 2018, our two teams realized we were all traveling in largely the same direction, so we joined forces. After four years of largely no conferences—good and a lot of hard work—we made it. Hanami 2 is now out, and it is indeed faster, better, and stronger!
00:03:31.320 It combines all the power and flexibility that you might see in Dry-rb with a really friendly and streamlined out-of-the-box experience. This was a huge team effort, so I want to give a shout out to the legends, particularly Luca and Peter, my teammates on the core team.
00:03:50.880 If we’re going to make trying Hanami our New Year’s resolution, we need to plan for it, and a plan is important. Here’s ours for today: Firstly, we’re going to build and get to know the framework by putting some things together.
00:04:09.840 From there, we’ll learn more about what it can offer us, and lastly, we’ll see how that might help us grow as developers. Now that we know what Hanami is generally speaking, let’s get to know it for real by starting to build with it.
00:04:29.400 The first thing we can do is install the gem, and then we can run the Hanami new command to create a new app. For today, we’ll call our app ‘bookshelf.’ Let’s check out the app definition for starters.
00:04:39.840 This is pretty nice and simple, so we won’t need to spend too long here. But there is one thing I want to point out: in Hanami apps, all of our code inhabits the same shared Ruby namespace. Here we have the module Bookshelf, which is where our app lives and where all the rest of our code will reside.
00:04:58.080 Now we can check out the routes. In a fresh app, we have just a simple default welcome string as our starting route. To make our app do something, we need to move on from here and generate an action.
00:05:20.760 We have a friendly generator to help us with this, so we’re going to create a ‘books index’ action for our bookshelf app. This has gone and made a new route for us, and it looks pretty simple. We have the ‘books’ URL pointing to that books index action.
00:05:36.840 Now, I think it’s time to check out the action itself. Here’s the index action that our generator has created for us. This is a basic starter action, and in Hanami, actions are the objects responsible for handling all of our HTTP interactions.
00:05:51.780 The way we structure them is that we have exactly one action for each endpoint. The `handle` method here is the most important part of each action because that’s where we define how it behaves. We are provided with separate request and response objects that we can work with, each offering a range of convenient methods.
00:06:12.900 For example, we can use them to inspect the request parameters or, as we’re doing here, set the response body. Speaking of those request parameters, actions also support flexible parameter validation schemas. This ensures that those parameters meet all of our expectations, both in terms of their structure and their types.
00:06:30.000 With this being an index action, we’ll want a couple of optional parameters: a page number and a per-page value. We’ll want both of these to be integers and greater than zero. This is really useful because now we can return an error response as soon as any of those parameters don't meet our validation requirements.
00:06:45.540 We’ve seen that actions give us a range of useful facilities for dealing with HTTP interactions, but our books index is still not returning any books. Where can we put our business logic? This is where we can explore one of Hanami’s most important features: in Hanami, our app also acts as a container.
00:07:05.340 A container works as a central organizing object that’s responsible for loading and managing access to all the components in our app. The action we just created is already a component, and we can fetch it from our app just like this, using a key that matches its name.
00:07:24.840 What we get back is an instance of that action, ready for us to work with. In fact, any class that we pass into the app can become a component. For instance, we can create a component for a book repo that returns a list of books for our action.
00:07:41.940 This component could look something like this, and since we’ve put it in the ‘app repos’ directory, we’ve given it a matching namespace inside Bookshelf. We can then add a method to return some of the latest books that we’ve read.
00:08:04.920 Now, this is a very simple repo. It’s a stand-in for something that connects to a real data source, but for the sake of our little app today, it will do. So now we’ve got two components: we have our action and we have our repo. But how do we get them to know about each other?
00:08:23.040 What we really want is for that repo to be used inside our actions so that we can return that response. For this, we can use Hanami’s `deps` mixin. We can use this mixin inside any class in a Hanami app, passing it a list of components that we want to have as dependencies.
00:08:41.160 Those components then become available as instance methods that we can use wherever we need them inside our classes. Using `deps` like this is central to how we can create higher-level behavior by bringing together a range of smaller, focused components.
00:09:04.560 In this case, we can use those latest books from our book repo to finally create that JSON response from our action. Now, we can spin up our Hanami server, and here’s our list of books.
00:09:15.480 So we’ve made our first working feature with Hanami! Well done, everybody! Hopefully, it’s helped us to get to know some of how the framework works, and I think it’s given us a great position to go on and discover a few more of the features that Hanami offers.
00:09:24.660 We can start by looking at the testing experience. We’ve just created an action—how do we test it? Like every part of Hanami, actions are designed to be directly testable.
00:09:34.080 This means we can call `new` directly on our action and even pass in a test double for that book repo dependency, which will allow us to control its behavior for the purpose of this test. And that’s enough for us to call our action and test its response body.
00:09:48.540 Now, this is a pretty low-level, isolated test. For most actions, we probably won’t want to go that far. What would be more useful is an outside-in test: a request spec, something that calls our app by URL.
00:10:01.440 But because all Hanami objects are designed to be directly testable like this, we get to make the right choice about the kind of test we need for each situation. So, as we start interacting with our app code in our tests, we can also do this in the console.
00:10:20.400 The console loads through the `hanami console` command, and inside, we have a nice app shortcut to access our app. This allows us to use that app to access all of our components, which come back already initialized, ready for us to work with.
00:10:40.260 The console in Hanami always starts quickly, no matter the size of the app. This is because we support two different kinds of boot levels: we have one called `boot`, which is the traditional kind that loads everything up front.
00:10:54.300 It’s exactly what we’d use when setting up a pre-forking server like Puma. But we also have one called `prepare`, which is a super lightweight boot mode that does as little as possible to make our app available.
00:11:04.500 We use this in all aspects of development, which makes the experience snappy in the console, when running our tests, and when running our local server. One of the things that Hanami does during both of these boot modes is load our settings.
00:11:26.100 These settings are the values we give our app to ensure it does the right thing in each environment. Things like flags, toggles, keys, etc. We can define our settings here.
00:11:39.240 For example, if we want our bookshelf to send emails when new books are added, we might want to use a third-party email delivery API. So here, we can add a setting for its API key, specifying that this setting should be a string.
00:11:55.680 During boot, Hanami will load these settings from matching environment variables. In development, we have nice .env integration, so we can use a range of different env files as well.
00:12:09.120 We can access our loaded settings as another component, and we have methods for each of those settings we just defined. This means we can also include settings as a dependency of any object, thanks to our `deps` mixin.
00:12:25.500 We’ve seen settings, and we’ve seen the components that are created for us from our app directory. But what about components that might need some kind of special setup as part of getting them ready?
00:12:39.120 This is where providers help, and we need to make one for our email service. We just created the setting for its API key, but we didn’t need to pass it in somewhere.
00:12:57.840 Providers live in their own folder in the `config` directory, and here we are setting one up for our email service. They come with a range of lifecycle steps, which we will step through now.
00:13:10.140 For starters, we’ll want to require the gem for that third-party email service. Next, we’ll grab that API key from the settings we created earlier, pass it in to configure that email client, and then register that client as a component in our app.
00:13:24.960 This means that any class using the email service as a dependency will get that fully configured client ready for it to work with. By now, we’ve covered nearly everything we need to know about Hanami apps, but there’s one more important thing for us to discover, and that’s slices.
00:13:43.200 Slices help us organize our app into separate domains or technical concerns, and they’re a fantastic way to introduce modularity and clearer internal boundaries to our app as it grows.
00:14:00.480 Now, you won’t see them when we start a new Hanami app, and that’s because Hanami can work for really simple apps too. We want that first-run experience to be as simple as possible.
00:14:12.840 But they are built-in, and they’ll appear as soon as we start putting any code into the `slices` directory. We can do this with a slice generator.
00:14:23.640 So here, we’ll create a slice for ‘admin,’ which will prepare an admin slice directory for us. We can then throw in another action, for instance, letting our admins create some books.
00:14:41.520 Just like our app, every slice maps to a single Ruby namespace, so this one maps to the admin namespace. Hanami creates this slice class for us to use, and that slice works exactly like we just saw with the app.
00:14:58.320 It offers access to all our components. The only difference is that the components within the slice are confined to whatever's defined in that slice directory.
00:15:11.880 Just like the app, slices also provide a `deps` mixin, with the same restriction: they only load the components from inside the slice. Now we’ve seen that both the app and slices act as containers for our components.
00:15:27.060 Thanks to the `deps` mixin, we can clearly recognize the dependencies between the components and begin to think of them as a directed graph. From here, we can take things one step further because slices can also import components selectively from other slices.
00:15:45.240 This means we can envision an equally clear graph of all of our application’s high-level concerns. This is a really powerful approach; here we have the tools built right into the framework to help us better organize our code at every level for easier understanding and maintainability.
00:16:07.380 One thing we wanted to do for Hanami 2.0 was to extend this experience beyond just web apps to apps of all kinds. Let’s take a look at the gem file for a new Hanami app. This one is a web app because we’re including the gems for the router and the controller.
00:16:17.520 However, these are not fixed dependencies of the framework; this is why they’re here in the gem file. If we wanted to build something different from a web app, all we need to do is remove those extra lines from the gem file.
00:16:37.680 With the single core Hanami gem we have remaining, we retain all the quality of life features that we just looked at: containers, components, deps, settings, the console, and even slices.
00:16:54.300 This means we can keep everything we need and nothing we don’t, allowing us to use all these features for any kind of app. So, Hanami 2 is no longer just a web framework; it’s an everything framework!
00:17:10.680 If you want to build a CLI tool, a chat bot, a serverless function, a Kafka consumer, but still want all the conveniences of a full framework and its conventions, now you can do that with Hanami.
00:17:24.120 I’m not sure we’ve ever had something quite like this in the Ruby community, and I’m really excited to see where people can take it. I’m also excited about how Hanami might help us grow as Rubyists, because it brings more to the table than just a range of framework features.
00:17:39.240 It provides a new opinion on what app design might look like, embodying those opinions in its own design. I think we can learn from that and apply those principles to fuel our growth as developers.
00:17:54.840 So, how can we grow? I think we can start by growing our understanding of our app’s domain. Hanami encourages this in a few ways.
00:18:09.240 Firstly, it makes one important thing clear from the get-go: the framework is not your app. These are separate entities; the framework should work in service of your app, not the other way around.
00:18:23.280 Since these two things are separate, we must consider how to name and organize our business logic code to find the right arrangement of components and concepts, making our code the most useful expression of our business domain.
00:18:42.120 Hanami makes it easy to decompose our logic into independent, focused components, giving us the chance to control the shape of our code. Much of this is about growing our modularity muscle, boiling things down into those focused components, and then composing them to create the high-level behavior we want to offer our users.
00:19:00.420 This really boils down to figuring out boundaries, an important skill in every aspect of our lives, especially as developers. We’ll benefit from working with a framework that acknowledges this vital activity and provides tools to reason about it.
00:19:15.540 The steps and slices in Hanami are to facilitate this process. Lastly, recognizing that the core premise of the framework is separate from our app allows us to write more of our app code in plain old Ruby. And that's glorious!
00:19:31.800 As we build confidence with plain old Ruby, it will benefit our ability to think and problem-solve fluently in the language. We might even begin to see Ruby through a new lens.
00:19:47.040 Creating objects with injected dependencies, adding one or more methods that receive arguments and return a value, but otherwise don’t change internal state, starts to resemble functional programming, which enhances clarity in our code.
00:20:00.360 Picking up Hanami might help us explore a new part of the Ruby ecosystem, as it builds on and around a rich heritage of gems designed for Ruby apps of all flavors. We might find inspiration in how some gems solve their problems.
00:20:18.900 While there are many chances for growth, things might feel overwhelming. If we want to revisit the whole 'new framework, new you' idea, I’d like to reassure you with a story.
00:20:36.750 I know the ‘new framework, new you’ concept is possible because I’ve lived through it. It started for me seven years ago. At that time, I had already been writing Ruby for 13 years, which is a long time.
00:20:54.840 But even then, I wasn’t happy with how my apps were turning out. I wanted a new approach but didn’t know how it should look.
00:21:09.540 So, I began to explore and found two significant tools. The first was the ROM gem—the database toolkit that we’ll be integrating with Hanami to bring to you in the next few months.
00:21:26.280 The second was the Roda gem, which we’re lucky to have Jeremy share with us tomorrow. These gems and their designs, ideas, and the community behind them opened up a whole new world for me.
00:21:43.800 One thing led to another: I picked them up, started playing around, and then added them to my apps. Over time, those apps began to change shape as I recognized the ideas behind the gems.
00:22:05.520 Now, here I am today, seven years later, having helped build a whole new range of gems that aim to make some of these ideas more accessible than ever. It’s been the most fulfilling years of my Ruby life.
00:22:22.440 No matter where you are on your personal journey as a programmer or with Ruby, you have the chance to take things in an exciting new direction.
00:22:37.380 With that in mind and thinking about our resolutions, I’d like to offer a few parting tips. Firstly, be curious: there’s lots out there to learn, and something like Hanami can be your gateway into a world of new ideas.
00:22:53.040 Secondly, be bold: if you see something interesting, don’t hesitate to try it. If it feels unnatural or uncomfortable, then that’s a good sign—it means you’re learning.
00:23:07.680 Be excited! You might tell that I’m really excited about all of this. I believe we’re at the beginning of a special moment for Ruby—a resurgence of fresh ideas.
00:23:14.760 This is your chance to be part of it. Lastly, be persistent: you can always try and try again. Since we started this journey around the idea of New Year’s resolutions, let’s take inspiration from Thailand and remember that you’ll always have a second or third New Year to pick things up and give them another go.
00:23:31.680 If any of this is interesting, and you’d like to learn more, you can visit the Hanami website. We’ve got a great getting-started guide to help you with your first Hanami 2 app.
00:23:48.180 I’m also developing an open-source example app on GitHub called ‘Decaf Sucks.’ I’m working on it one PR at a time, so if you’re interested, you can follow along as we put it together piece by piece.
00:24:05.160 I’d love to chat more about any of this. There’s probably another hour of content that I chopped from this presentation, so please find me at the conference or reach out to me on Mastodon at Tim Riley on ruby.social. Thank you very much!
00:24:20.480 Thank you!