Talks
Architecture of hanami applications
Summarized using AI

Architecture of hanami applications

by Anton Davydov

Summary of "Architecture of Hanami Applications"

In this presentation at RubyKaigi 2018, Anton Davydov discusses the architecture of Hanami applications, emphasizing the significance of maintaining business logic for long-term maintenance. Davydov highlights the challenges in finding a framework that provides specific guidelines for managing business logic, making a case for the advantages of Hanami.

Key Points Discussed:

  • Maintenance Challenges: The talk focuses on maintenance problems in web applications, as they often encompass management, performance, and maintainability issues.
  • Good Architecture: Davydov suggests that good architecture should prioritize isolation, controlled logic, and avoid global state. He emphasizes that these elements are crucial for adding or refactoring features with ease.
  • Functional Objects: He promotes using functional objects (like dry transactions) for maintaining state by eliminating global state and facilitating easier testing.
  • Dependency Injection: Essential for providing flexibility in functional objects, dependency injection enables testing different scenarios easily by substituting dependencies.
  • DRY Containers: Containers help manage dependencies efficiently, promoting better performance and easier testing by allowing instance memoization.
  • Separation of Business Logic: Emphasizes that business logic should be separated within service objects and models for better maintainability, drawing contrasts with Rails' approach, which sometimes conflates responsibilities within models.
  • Domain-Driven Design (DDD): Discusses using DDD for structuring applications where each domain can cater to distinct business values, allowing for cleaner communication between services.
  • Event Sourcing: Introduces event sourcing as a method of capturing events rather than traditional data storage. This allows applications to react to events and simplifies management of business logic across complex systems.

Examples & Illustrations:

  • Davydov shares anecdotes from his experiences with Rails applications where shared business logic led to complexity, contrasting this with Hanami’s ability to manage similar business logic across applications seamlessly.
  • He explains the Dry system for better dependency management and introduces repositories in Hanami and their advantages over Rails' ActiveRecord.

Conclusions and Takeaways:

  • The importance of maintainability in large web applications.
  • The need for clear separation of concerns and isolation within business logic management.
  • A focus on utilizing modern patterns and practices like dependency injection, functional objects, and event sourcing to address common architectural challenges in web development.
  • Davydov encourages developers to explore further resources for detailed understanding and invites questions for deeper discussion.
00:00:01.340 Okay, let’s try this. Can you show our Kiowa?
00:00:07.230 Thank you very much. You can see my email here; just send me something and I’ll send a lot of links because this is a longer presentation. Maybe you will miss something.
00:00:16.289 This is my second time in Japan, and I can say that I've learned many things, especially during this visit. I've gotten answers to some really important questions for me.
00:00:31.199 The first question is: Is Ruby still awesome? I can say yes, it is awesome. The second important question is: Is Java more popular than Ruby? Yes, it is. A very important question I will answer today is: Is Ruby dying? Of course not. And the last question: Is Rebecca still awesome? Yes, she is still awesome.
00:00:54.149 Thank you so much! I'm super happy to see you all here and to be here again. My name is Anton Davydov, and you can find my contact information here. I'm from Russia, which is very close to Japan—just an hour's flight away. You need to know two things about me: First, I really love stickers; I have a new laptop, that's why it's empty now, but maybe in the future it will be covered in them. Second, I really love open source and the ideas from open source.
00:01:29.189 A little announcement: This is a really big presentation, and I’ll repeat it again—send me anything to this email, and I will answer you with a lot of links from this presentation. Today, we will talk about architecture. I'm not able to see your architecture, but you can trust me.
00:02:06.390 The main reason for giving this talk is to share some ideas from Hanami projects. I've written many Hanami projects and I try to understand the best way to manage dependencies and business logic. I also want to get feedback from you, so if you want to discuss anything, please find me tomorrow, today, or on the last day of the conference and we can talk about problems because who doesn't love problems?
00:02:45.600 In my practice, each application or project in a company usually has three different technical problems: management problems related to deadlines and asset research, performance problems which may involve multiple SQL queries, and maintenance problems. Today, we will only focus on maintenance problems. Let’s talk about some interesting concepts you can find on the internet, which include clean code, good architecture, and maintainable projects.
00:03:41.280 Can someone explain what good architecture means? I think it's a complex topic because it’s difficult to define what clean, good, or maintainable really means. Therefore, let’s try to answer a simple question: What do we want in our system? We want to add new features and usually for adding features, we need some rules. Typically, this involves isolating logic and managing global state.
00:04:09.180 We also want to delete or refactor some features, which requires us to isolate certain parts of our project. This makes it easier to maintain just one part instead of thousands of lines of code. Additionally, we need to ensure we have test coverage. If we conclude all this, we can say that from a maintainable system we need isolation, controlled logic, and no global state.
00:04:40.220 Unfortunately, we live in a world where we have something like Rails. If you want, I can show you all the stats; it has more than 2 million lines of code. So how can we improve this situation? The first thing we need to understand is abstractions—because abstractions are everywhere.
00:05:02.190 We will start our discussion from actions. Some internet articles or books suggest that the MVC pattern is extremely clear and simple for maintenance and building. I agree with this because you write code with some view controllers and then you create the MVC pattern. Some solutions for this problem include functional objects. These can be anything, such as services, interactors, or dry transactions.
00:05:58.840 Can I see a show of hands for who has used dry transactions? Great! So what is it generally? It’s an object you create without any state. You call some methods with parameters, and it produces a result object that you can work with, without mutating any state. It ensures isolation for our project.
00:06:11.680 For example, in Hanami, when we work with actions, we see that we get a result, and then we operate with this result without issues. The functional object pattern also helps establish a clear sequence for logic, as evidenced in dry transaction examples from my project where we handle different steps: extract data, transform data, and create something new.
00:06:52.060 Moreover, functional objects eliminate global state. Every time you create a new instance, you call a method that doesn't mutate any data within your object. This focus on a non-mutable state makes testing easier. But to ensure your functional object is easily testable, you need to implement dependency injection.
00:07:11.680 So, who loves dependency injection? It’s important because we can inject different functional objects into our Hanami actions or any other object. This means in our tests, we can easily substitute a successful dependency with a failing one, ensuring our tests cover various scenarios. Additionally, we can use lambdas and procs, as they have call methods. Lastly, some objects can call other objects, such as interactors in Hanami.
00:07:45.900 However, we encounter issues with this pattern. Firstly, we have numerous objects, making it very challenging to manage everything. We need to use namespaces and scopes, which leads us to use lengthy names for objects. For example, we could have something like 'orders car and coupon operation'—that’s quite a mouthful. Additionally, every time you call a new functional object, you instantiate a new object which can negatively impact performance.
00:08:18.660 We can mitigate this by using containers, which leads us to the next abstraction: dry containers. Let’s talk about dry containers. Has anyone used them before? Awesome! The concept is straightforward: you create an instance of a container, register some dependencies with specific keys, and then call these dependencies by their keys to obtain their instances. For instance, in Hanami, when you call a method with some parameters, you can control global state more effectively without creating tons of constants.
00:09:24.620 This container also allows instance memoization for better performance, and testing becomes more manageable since you can create mocks for your container dependencies. The next abstraction we’ll discuss is dependency injection. Think of it as dependency injection on steroids. To use it, all you need is to create a new instance of your container, making it usable throughout your classes.
00:09:53.850 When you do this, you can add a method that initializes your dependencies and use them in various places throughout your application. Moving on to models, let’s first establish that the MVC as a pattern is incredibly simple. You just create a view and controller and add models; that’s it. However, we need to define that all business logic should reside in service objects and models.
00:10:15.080 Validation in models is a bad practice. Our models should simply be responsible for loading and persisting data. In the Rails community, I know that query objects are popular. So who uses query objects? In the context of Hanami, we have repositories. If you look at Hanami’s documentation, you’ll find descriptions of how to manage this more effectively for neat and organized logic.
00:10:49.300 For complex logic when you need to create, save, or update complicated entities, you can use change sets. But doing it this way might lead to callbacks that can create a complicated logic flow. For example, take a look at this code where a class injects user repositories instead of merging created information into the payload directly. Using change sets allows you to manage this effectively.
00:11:38.550 Now, let’s talk about applications. This is my favorite part of the talk because in a previous job, I worked with Rails applications that contained models and services. Over time, I realized that we had two Rails applications sharing similar models and services while using the same database. To update any one application, we would also need to run migrations on the others, which created unnecessary complexity.
00:12:30.420 With Hanami, if you look at a typical application, you'll see that it can act as one project with shared business logic. This shared logic between different applications can significantly change your mindset. If you recall the earlier discussion about actions and interactors, you can easily transform that into applications that invoke business logic seamlessly.
00:13:54.890 In Hanami, you can start with just one application, which is crucial when you want to split your projects into different parts. However, we still encounter challenges. I recall a situation where a developer updated a mobile API but forgot to update the administration application. This caused a break in functionality, highlighting the importance of keeping track of changes across applications.
00:14:45.310 Sometimes, you might need to apply similar yet differing logic in various areas. We need to delve deeper, and a brief warning: we’re now entering an advanced level. You can build applications using all the previously mentioned concepts, but the next steps involve improving quality and maintaining project integrity.
00:15:39.600 The first new concept is a dry system. It’s essentially a dependency management system that can do two main things: after registering dependencies into a container, it operates similarly to Rails in managing the registrations and retrieval by specific keys. This makes it easy to handle dependencies without cluttering your code.
00:16:36.240 On the other hand, another helpful feature of the dry system is dependency booting. This allows you to create a specific register block where you initialize your application by checking connections to your database and managing dependencies in an orderly manner. This ensures a structured environment where all dependencies are well managed.
00:17:27.470 Next, let’s discuss domains. Love for DDD (Domain-Driven Design) is extensive, especially in Polish communities. If you want to understand why DDD is beneficial, I recommend checking out Martin Fowler’s book on the subject. The essence is that you can create separate domains for distinct business values.
00:18:17.360 For instance, a shopping application might have one domain for orders and another for users. Each domain is related to others, which allows you to create independent services that can easily communicate with each other. While creating an application structure under DDD could be complex, it opens up more manageable pathways for scaling your applications.
00:19:10.680 When looking at typical Hanami projects, you’ll note that each application can invoke various business logic, such as utilizing repositories or network clients effectively. The architecture allows for flexibility and independence, where applications can call different domains. HTTP is just a small portion of what your system comprises, as in real applications, you will likely interact with background processing, websockets, and much more.
00:20:07.560 This means that your business logic consists of different events. For example, when you create a user, you fire an event to notify that the user is created. When you process an order, you create an event to indicate that the order was completed.
00:21:21.160 Now, let's delve into the concept of event sourcing. Who knows what it is? Great! The general idea of event sourcing is that rather than storing data in databases, applications simply capture events. For instance, when a user is created or an order is placed, an event is stored. This event log is key to how our applications will run. Applications can then consume these events as needed.
00:22:29.740 In essence, various applications will react to these events. While working with Rails, you might opt for traditional data storage; alternatively, in systems keen on leveraging events, using libraries like Kafka to handle event sourcing can be illuminating. By calculating all events for a specific user, the system retrieves the necessary states allowing refined interaction with your databases.
00:23:12.280 For example, in a Hanami project, a web application might send events to an event log without needing additional applications. This can include persistent storage such as SQL or NoSQL databases, giving you full control over how you handle business logic while ensuring integrity. There are numerous pros and cons to this strategy, and while it's not a silver bullet for every case, it presents exciting opportunities.
00:24:38.740 As for tools, it’s good to check out libraries to manage event sourcing effectively, such as Relevant Store or Hanami Event, though they are still proofs of concept awaiting more thorough implementations.
00:25:15.320 Ultimately, it’s not merely about Hanami and Rails; our approach could involve using technologies like Dry combined with ROM. The important part is to isolate your business logic effectively.
00:25:51.600 In conclusion, maintainability is crucial in large and complex systems. I've referenced many ideas you can explore via the provided links, which include practical examples with Hanami. I welcome you to send me anything you’d like to discuss further, and I’ll make sure to provide those links in my replies.
00:27:31.940 Let’s go drink coffee! Thank you!
00:28:21.350 I see a question; nice! You’re my hero.
00:28:25.990 I want to ask about the relationship between models and repositories. I'm using Rails and want to make a comparison between Rails and Hanami. In Rails, there's ActiveRecord, but Hanami uses repositories and models. Can you explain the advantages and disadvantages of using repositories in this context?
00:29:29.250 In my opinion, Rails models contain a lot of logic, including validations, callbacks, and the actual data persistence functions. It may be beneficial to separate this logic into different areas since maintaining long Rails models can be cumbersome. Rails models often contain both class and instance methods, which can quickly become cluttered with responsibilities.
00:30:07.720 The advantages of using repositories are that logic is separated, making it easier to maintain and test. You can mock dependencies easily and test functionalities without heavy reliance on broader classes.
00:30:29.910 However, the downside is that it introduces an additional level of abstraction. You must carefully find a balance in your directory structure and documentation for clarity and ease of understanding among developers.
00:30:55.440 Thank you! You're welcome.
Explore all talks recorded at RubyKaigi 2018
+58