Talks

Keynote: Why Hanami?

Paris.rb Conf 2020

00:00:14 Good morning everyone! Thanks for being here and thanks for having me. It has been an amazing conference so far, so great job to everyone involved in organizing this event.
00:00:27 I had the chance to meet old Ruby friends and new faces. I finally met Matt for the first time, so I am really happy personally. That is quite an achievement after fifteen years of professionally working with Ruby.
00:00:41 My name is Luca Guidi. I've been involved with open source for over a decade. In the past, I created gems like Register and contributed to Rails back in the day, as well as working on the internationalization gem.
00:00:55 But today, I am here to talk about Hanami, which I am the author of. This full-stack web framework is built for Ruby.
00:01:08 Of course, I'm also part of the core team of Dry-rb, and I love open source and this community. I work remotely from Rome, Italy for Tap Tile, and I'm grateful to the company for allowing me to be here today.
00:01:22 As you have seen in the title, there is Hanami 2.0. Hanami 1.0 was released four years ago, so it's time for the next generation.
00:01:35 I had the chance to pause, reflect on the roots of Hanami, and share with the community why Hanami is important.
00:01:50 Let's talk about the motto: 'The web with simplicity.' I envisioned a simple approach to Ruby web development.
00:02:02 Last year, I realized I didn't want any monkey-patching or a lightweight system to reason about.
00:02:09 I wanted something that respected Ruby. This goes beyond mere monkey-patching. Ruby is an immense, extraordinary language that has already indicated how we should build applications and systems.
00:02:20 We simply needed to listen to this extraordinary language. The logo and name of Hanami are a bridge to the Eastern community.
00:02:35 The fact that Ruby was born in Japan plays into this, as Hanami is a Japanese tradition that brings together people with joy. This is something I want to bring to our community: the joy and fun of building applications with Ruby.
00:02:48 Hanami is inspired by the tradition of seeing cherry blossom trees blossom together with family, friends, and loved ones.
00:03:02 Of course, cherry blossoms symbolize something simpler than that. However, the point is, there is a Ruby emblem at the center of this idea.
00:03:15 To me, this means that I respect Ruby and have built Hanami around it. The circle symbolizes my desire to protect the language, not change it. We should listen to and embrace what this extraordinary language offers.
00:03:29 I wanted a framework as guidance for developers that is not just fun to build with for a month, but remains maintainable over time.
00:03:43 I aimed for a tool that would serve as an educational resource and promote more attention to long-term maintenance.
00:03:57 Hanami should feel natural to newcomers and also offer extension points for experienced users who want more.
00:04:11 These core principles of testability, long-term maintenance, and productivity have guided me since the beginning of the project and continue to do so today.
00:04:25 However, we are here not just to talk about Hanami 1.0 but to discuss its future and share the principles I've researched and applied over the years.
00:04:40 This research has been applied in real-world applications, even outside of Hanami, to test the concepts I found valuable.
00:04:54 To me, the results of this research are extraordinary. I believe that what I am about to share is solid and has been tested extensively in large applications.
00:05:09 The acronym EORP stands for the blending of functional and object-oriented paradigms—two distinct methodologies that can work splendidly together.
00:05:22 There's nothing intimidating about blending these paradigms. You won't need to know anything about functional programming to see how it aligns with what you already know.
00:05:36 But no spoilers just yet.
00:05:41 You may have heard of Elixir, a functional programming language that runs on the Erlang VM. Elixir is a modern implementation that has brought functional programming to the masses.
00:05:55 This is largely due to the language's founder, who migrated from the Ruby community to Elixir.
00:06:07 But I want to use Ruby. It fascinates me to see languages like Elixir evolve, but I firmly believe that there are many people here today who want to continue using Ruby.
00:06:22 I also believe that taking inspiration from other languages can greatly benefit us as developers, as it adds more tools to our toolbox.
00:06:38 I discovered that Ruby is a multi-paradigm language. Historically, we often view Ruby primarily as an object-oriented language, where everything is an object, including integers.
00:06:54 However, you can also write procedural code with Ruby, and even some functional programming.
00:07:05 We are going to focus on the object-oriented aspect of Ruby and take advantage of its basic features along with inspiration from functional programming.
00:07:17 Let's examine the first concept: pipelines. It's crucial to recognize that web application architecture must be shaped around features, not around MVC.
00:07:32 What defines your apps is not just the structural aspects; it's the features themselves. You can tell the difference between Uber and Instagram based on how people utilize them.
00:07:45 Users don't hire those apps out of necessity; they do so to achieve a specific job, such as uploading a vacation photo.
00:08:00 This focus on the features at hand is of utmost importance. MVC should be considered secondary, especially since there are persistent challenges within our traditional MVC-based Ruby web development.
00:08:13 The MVC approach can lead to complications where the view simply renders HTML, while the controller can get cumbersome due to varying paths and outputs, making it challenging to manage the flow of data.
00:08:27 The M in MVC—representing everything that shapes how web applications are built—includes the issues of persistence, business logic, validations, and more.
00:08:39 These historical challenges have resulted in gigantic, unmaintainable models that become nearly impossible to deal with after your app has been under active development for a year.
00:08:53 Imagine a different approach where each application is defined as a collection of features, and each feature serves as a step from the browser to the database.
00:09:08 In this model, each step has a singular responsibility; every step takes an input and transforms it into an output for the next step.
00:09:20 Thus, you visualize a direct flow from browser to database and back to the user, devoid of an MVC constraint.
00:09:34 MVC can still be utilized to group responsibilities and roles, but what's crucial for Hanami is that for every role (except for the browser), we maintain an object that composes these features.
00:09:49 This structure frees you from the constraints of MVC; you're able to take each component and build the features as you see fit.
00:10:05 Every step possesses a well-defined goal, which allows us to create smaller objects that adhere to the single responsibility principle.
00:10:22 This principle is vital—as smaller objects tend to increase cohesion, they become far easier to maintain, reason about, and test.
00:10:38 In functional programming, this translates to having more functions that you can combine to solve complex problems. Consider a simple function to sum two numbers.
00:10:53 By combining simple mathematical functions together, you can handle more complex equations. Small objects are akin to Lego bricks, providing the foundation for creating complex systems.
00:11:07 However, just creating one object per step is not sufficient. While this respects clear responsibility separation, we must also ensure that there exists an agreed-upon protocol among those objects.
00:11:24 Historically, we had a remarkable example within the Ruby community with Rack, introduced in 2009. Before Rack, web development was a chaotic mess.
00:11:39 Rack's introduction offered a simple, unified protocol that encouraged web frameworks and servers to communicate effectively.
00:11:55 By having components that can seamlessly work together, we develop a cohesive ecosystem.
00:12:07 What does the Rack protocol encourage? It suggests that we use a call method; anything in response to a call is a Rack endpoint.
00:12:19 Why did we end up choosing 'call'? Because once again, it's essential to listen to the language.
00:12:32 Ruby inherently encourages the usage of 'call' since most basic data types respond to this method.
00:12:41 Thus, we can unify our approach by exposing 'call' on our objects. The right side of the slide illustrates this point.
00:12:56 We create clear separations between the external interface and the internal implementation, allowing for interchangeability without affecting the overall system.
00:13:09 This boundary offers significant clarity when extracting services in the future.
00:13:22 Furthermore, we are leveraging Ruby's duck typing principles, which allow us to create simple behaviors that depend only on whether an object responds to 'call'.
00:13:39 Thus, I refer to these as callable objects—objects that have a single purpose.
00:13:54 An example would be an uploader object that handles the responsibility of uploading a file remotely. It accepts an argument and performs its task.
00:14:09 This focused object follows a well-defined protocol, making it reusable within your application.
00:14:22 All the components in Hanami 2.0 will respond to 'call', establishing this shared protocol between our components.
00:14:39 We can also take it a step further with dependency injection, which is another essential principle in object-oriented programming.
00:14:53 To create good architecture, one must pay attention to dependencies. This means having a limited number of collaborators for each object and ensuring that dependencies flow in one direction.
00:15:10 Understanding complex systems becomes a lot simpler when maintaining a clear line of dependencies.
00:15:25 With dependency injection, we refactor our uploader to allow it to receive its dependencies through initialization.
00:15:37 This may sound complex, but let's take a look at how we accomplish this with a simple factory.
00:15:53 By adopting a dependency injection system, we maintain consistent code across our codebase, making it easier to see the dependencies declared in the initialize method.
00:16:06 This leads to easier testability, as we can now pass mock objects, allowing us to avoid uploading real files to remote storage during tests.
00:16:20 As a rule of thumb, use 'initialize' for dependencies and 'call' for inputs; this is a highly useful principle I've found valuable over the years.
00:16:37 Now let's quickly shift focus to pure functions—these are quite simple. A pure function’s output relies solely on the input.
00:16:50 For example, if I input two and two into a sum function, I will consistently expect the output to return four.
00:17:02 This predictability is essential because it ensures that the behavior of your objects remains straightforward.
00:17:14 The key takeaway here is that we want to minimize the reliance on the internal state of our objects.
00:17:30 In Hanami, stateless objects—such as validators—accept input and produce output but do not hold any internal data.
00:17:44 This approach ensures that we can instantiate an object once and use it throughout the application’s lifetime.
00:18:00 Stateless objects tend to be less prone to bugs, as they do not depend on any internal state, but rather process input-output relations.
00:18:14 Unix commands are an excellent analogy for this concept: they accept input and return output without changing their own state.
00:18:27 But state is sometimes necessary in applications where data is a vital asset.
00:18:39 We want this state to be immutable; for instance, entities can hold data but must remain unchangeable.
00:18:53 That doesn't mean you cannot assign variables; it means you should use specific result objects.
00:19:06 These objects can pass through the pipeline operations where they hold relevant data while maintaining the immutability of the steps.
00:19:21 This behavior guarantees determinism and predictability within your applications, ensuring thread safety.
00:19:35 Utilizing frameworks such as Puma or Sidekiq necessitates that your application remains thread-safe to avoid hard-to-detect bugs.
00:19:49 Thus, the components in Hanami 2.0 will be designed to be immutable, aside from the objects passed between different layers.
00:20:04 Implementations like views and repositories will also be immutable, instantiated only once during the application boot.
00:20:18 This approach alleviates stress on Ruby's garbage collector by reducing the need to repeatedly recreate heavy objects like actions and views.
00:20:32 Hanami will operate with a container that holds these instances accessible from the start, ensuring a lightweight framework.
00:20:45 For an in-depth exploration of these concepts, I suggest viewing the talk titled 'Functional Web with Hanami', where I cover everything from functions to complex system design.
00:20:58 To summarize, applying functional programming principles to Ruby leads to comparable, single-purpose, thread-safe, and maintainable object-oriented code.
00:21:15 It is noteworthy that during our discussions, we spoke extensively about functional programming but merely touched on the fundamentals.
00:21:30 Our exploration stresses building from factors that the Ruby language inherently supports.
00:21:44 Moreover, a significant part of the Ruby ecosystem is influenced by these principles.
00:21:57 More than a year ago, we decided to align efforts with the ROM community to co-create an integrated ecosystem.
00:22:08 Hanami is the web framework I presented, while ROM serves as a persistence toolkit.
00:22:22 Dry-rb is a set of gems that includes monads, factors, CLI tools, and other utilities.
00:22:35 With Hanami 2.0, version 5.2 is already available, complementing the maturation of ROM gems.
00:22:49 The goal is to build a fully integrated ecosystem of Ruby gems.
00:23:02 I want to stress that we are targeting the Ruby ecosystem—not creating a walled garden. This means you can pick and choose gems according to your needs.
00:23:19 We offer a diverse set of gems, allowing you to select what works best in your existing applications.
00:23:34 Additionally, we remain respectful of the language, applying new methodologies while staying true to Ruby's fundamental aspects.
00:23:45 Hanami exemplifies the direction of this movement, which advocates for a dry web framework.
00:24:01 The previous maintenance project has been deprecated. Now, let's quickly preview how Hanami looks at a high level.
00:24:13 Everything you see now represents collaboration not just with the Hanami team, but also with both ROM and Dry-rb's principles.
00:24:26 We aim to retain the same features while reshaping them according to the foundational principles discussed earlier.
00:24:41 Stronger principles emphasizing testability and productivity remain integral.
00:24:55 However, simplification is a key focus. I don't expect you to dissect every detail.
00:25:06 This screenshot depicts a freshly created Hanami 2.0 application.
00:25:20 You're familiar with typical files like Gemfile, Rakefile, and other directories, yet this structure is exceptionally straightforward.
00:25:33 Hanami is optimized for productivity, featuring code generators along with production code and unit tests.
00:25:44 The previous version offered a 'you see what you get' approach. We aimed to avoid overwhelming you with settings.
00:26:00 So, all configurations are internally defined by the framework, minimizing the burden for newcomers.
00:26:15 Previously scattered configurations are now consolidated in one place, making it much cleaner.
00:26:29 Now, let's examine the components—starting with the router. This is the third iteration of the router design.
00:26:44 An action functions as the endpoint for your router, but it may seem somewhat complex with multiple mix-ins to consider.
00:27:01 I discovered that embracing mix-ins facilitates a kind of multiple inheritance in Ruby, enabling various behaviors from different modules.
00:27:15 I sought to enforce the single responsibility principle by guiding you to inherit from a single object, simplifying your objects considerably.
00:27:31 Now, let's discuss repositories. These objects are instantiated at boot time and then held as references.
00:27:46 With this reference structure in place, we can simply import repositories that match our database tables, enhancing overall cleanliness.
00:28:01 Thanks to reliance on Dry's injector, our production code remains clean, and we can offer injected fake repositories for testing.
00:28:17 We introduced a new method, previously referred to as 'call,' which allows passing requests and responses as arguments.
00:28:37 Here we see a mutable object that I mentioned earlier, allowing us to relay whatever object we require.
00:28:56 This new design merges functionalities from the DRY approach with what you've seen regarding actions and views.
00:29:10 Creator Tim Wright helped develop a new view component that reflects the class name from the action previously discussed.
00:29:26 We tell the renderer which Rb file template we wish to pass.
00:29:45 There's also a new feature called optional decorators that allows you to customize view logic without the need to edit templates.
00:29:58 In addition, we are experimenting with placing view-related files into a directory labeled 'components' in line with popular JavaScript frameworks.
00:30:17 This structure allows easy mapping between front-end and back-end code; however, this is still experimental.
00:30:32 Hanami integrates with a new model based on ROM, learned from the concerns of creating clean architecture.
00:30:46 There are now three directories: entities, relations, and repositories, simplifying data management.
00:30:58 Repositories are responsible for reading and writing data, while entities return objects from database operations.
00:31:11 Entities must also implement essential logic—for instance, ensuring secure password storage using bcrypt.
00:31:26 Relations hold mappings to a database table, helping expedite setups while allowing customization when advanced manipulation is necessary.
00:31:39 This approach balances simplicity for newcomers with extension points for power users.
00:31:52 This aligns with tools like Absurd, which you may find beneficial.
00:32:06 The Dry models feature a clean template pattern, clearly defining what each application does.
00:32:19 These models serve as valuable return points to indicate whether an operation was successful.
00:32:32 We’ve done a rapid overview of the main pillars of Hanami, but I do want to address a clarifying point before we wrap up.
00:32:51 While speaking to many of you here, I've heard concerns from developers who find it challenging to convince their teams of the value of new technologies.
00:33:05 I recognize that the apprehension surrounding new technologies stems from the inherent risks, and I want to acknowledge that.
00:33:20 Consequently, I’ve been contemplating how to reduce that perceived risk.
00:33:34 Thus, I encourage the idea of removing the complexity of all you’ve seen and presenting one simplified option.
00:33:46 Hence, we now have a new project within Hanami called the Hanami API—a minimal, lightning-fast Ruby framework specifically designed for HTTP API development.
00:34:00 Like an HTTP shell, you have complete flexibility in building your stack by deciding to use Active Record, Sequel, ROM, or any other tools.
00:34:13 The aim is to provide a thin DSL on top of the new router that emphasizes simplicity.
00:34:28 Thus, the Hanami API only consists of around two hundred lines of code, while the new router itself is approximately seven hundred lines.
00:34:45 This design mirrors the familiar structure of Hanami routers, where you define HTTP methods, paths, and endpoints.
00:35:01 This can include any Hanami action or establishment, providing vast flexibility.
00:35:12 You can utilize block syntax reminiscent of Sinatra, facilitating seamless integration with existing Ruby applications.
00:35:26 For example, mounting Sinatra applications or implementing middleware solutions can easily be accomplished.
00:35:38 In this config file, you will instantiate your applications, and the process is straightforward.
00:35:49 The intent behind this new API is to provide a way to experiment with Hanami while minimizing potential risks.
00:36:03 Our router has undergone considerable optimization since Jeremy's earlier benchmarks, and I focused on improving performance.
00:36:16 The benchmark generates applications with 10,000 routes to assess memory footprint.
00:36:30 In this benchmark, Hanami API achieved an impressive 47 megabytes of memory usage, compared to Rails and Sinatra.
00:36:45 When under load, Hanami yielded an incredible runtime of just 0.11 seconds when completing 20,000 requests.
00:37:00 In contrast, Rails took around 17 seconds, while Sinatra needed nearly three minutes.
00:37:15 That's the difference that this discussion and competition brings to our Ruby ecosystem.
00:37:30 The Hanami API, released today, is merely a stepping stone for achieving optimum performance.
00:37:45 I want to express my gratitude for joining me—let's keep the Ruby community moving forward together.
00:37:58 Thank you!