00:00:28.960
Welcome everyone! Today, I will share about the monadic approach to Ruby.
00:00:46.079
How is everyone doing today? I am good! I've been traveling for weeks and I've fallen in love with Taiwan. The weather today is just perfect for the conference.
00:00:58.559
Welcome to the talk on the monadic approach to Ruby error handling. Exceptions in Ruby are well known to all Ruby developers as a unique data structure representing an error or unexpected condition in a program. It can contain information such as the error type, error message, and stack trace. Let's take a look at a code snippet that demonstrates how to handle a common error: division by zero.
00:01:34.880
In the snippet, we have a divide function that handles division by zero, which is a quite common error. If division by zero is attempted, an error is raised, and we print out the details to see what shows up on the console. Additionally, I raised an error if the result is unexpected.
00:02:08.080
Imagine we are creating a solution in Ruby. Today, I will showcase a web application for ticketing because it is a familiar domain. I will visualize a high-level design system as the backbone for our web application. It starts with a user who interacts with the frontend to buy a ticket. The user journey is facilitated by the backend server.
00:02:40.159
The ordering process begins with using the user ID and the ticket ID. The order is stored in a database, and we also integrate with a payment gateway for our solution.
00:03:05.519
Let's dive into the application. We want to build our blocks for the server using a straightforward architecture, where the controller interacts directly with the payment gateway, bypassing the view component. I came up with a nice implementation, though it may look a bit overwhelming at first, but don't worry—I will break it down.
00:03:30.720
Initially, when a user buys a ticket, we need two pieces of data: the user ID and the ticket ID. Then we search for the corresponding user. If we find the user, we create an order with that user and check the user's balance against the order price. If the balance is sufficient, we continue with the transaction.
00:04:05.200
The controller interacts with our payment gateway to initiate the payment. This is where we integrate the external service to process the payment. Upon successful submission, we confirm the purchase by rendering a successful response to the user.
00:04:43.560
Next, we handle three main value points: user balance, record retrieval, and payment submission. If the user's balance falls short, we render an 'Insufficient Balance' message to the user. We also handle exceptions in a traditional way when dealing with errors.
00:05:09.800
If the payment submission fails, we again catch the exception. Each step must inform the user clearly with an error message.
00:05:57.400
Now, do you have any comments? This approach is not very extensible or reusable. Can we make it better?
00:06:02.440
Before we continue with the talk, let me introduce myself. My name is Du Gia Huy. I am a software engineer with five years of experience working on high-traffic web applications. I currently work in Singapore and have presented at RubyConf Thailand. Today, I'm excited to be here at RubyConf Taiwan.
00:06:39.919
At my company, we build systems that deliver content to millions of users around the world on a daily basis. Our platform is a leading service for Asian entertainment and culture, where millions discover and consume their favorite shows and movies.
00:07:09.760
So let's discuss what programming is about. It involves crafting solutions to a set of instructions and giving them to the computer to solve problems. As software engineers, our primary job is to solve problems.
00:07:52.560
Everyone is likely familiar with the mathematical concept of functions, where f(x) = y. Here, f represents the solution, x is the data parameter needed to execute the logic, and y is the expected output when executed correctly. However, if we execute the function with specific input and the output differs from our expectation, this results in a software error.
00:08:40.159
We can categorize errors in software into two main types: expected and unexpected errors. Expected errors are part of program execution. As software engineers, it is our job to anticipate these expected errors and prepare for normal operation. For example, we might expect errors due to business logic constraints or external dependencies.
00:09:36.760
On the other hand, unexpected errors occur outside our control, such as database issues or memory failures. However, we can manage these unexpected errors through diligent monitoring and improving our software.
00:10:02.000
In this talk, we will focus on how to handle expected errors effectively. By mastering expected errors first, we can better tackle unexpected errors later in our daily software engineering.
00:10:56.880
Our goal is to create a robust system that minimizes disruption and prevents incidents. We want to ensure that when a user tries to buy a component, the implementation is improved. I introduced the concept of a business logic layer that encapsulates our core functions and decision-making.
00:11:36.919
In contrast to passing requests through various layers, we have this business logic layer perform necessary functions. This separation of business rules makes our system more maintainable and adaptable to future changes.
00:12:20.560
Furthermore, this approach simplifies our architecture. We can integrate with other front-end systems or messaging platforms without starting from the controller.
00:12:39.839
Now, let's discuss refactoring and improving our initial implementation. At first, we handled multiple responsibilities within the controller. This led to a messy design, so I decided to modularize it using the business logic layer.
00:13:08.440
I wrapped the order creation into a dedicated service. The payment mission was also separated for clarity. This change has ensured our controller remains clean, making the code much more maintainable.
00:13:47.280
After creating an order, we have wrapped it into create order and checkout services, so the flow remains essentially the same while improving clarity and reducing our technical debt.
00:14:34.920
Now let’s move on to exception handling. In the initial implementation, the controller handled errors directly, while in the new design, we propagate errors back to the user clearly and concisely.
00:15:23.640
The difference here is that now the maintainer can more easily see and understand the error scenarios compared to the traditional way.
00:15:43.680
As we continue, we should maintain our focus on error handling and how it can further be improved.
00:16:02.960
Currently, our error-handling remains limited. Can we improve this further? Now, when we enhance our error handling, it should not just be clear and readable, but also flexible, reusable, and encapsulated across different parts of our services.
00:16:30.960
This brings us to the next iterative version of our refactoring, which aims to encapsulate error handling inside each service rather than in the controller.
00:17:20.800
For each service, we will have separate error handling that keeps the controller logic streamlined while simplifying error management.
00:18:00.800
This facilitates a clearer separation of concerns. While we now focus on the outputs and can process errors better, we're still facing the challenge of keeping the controller agnostic about specific service-level errors.
00:18:50.200
By utilizing monads, we can standardize our error handling across the software, ensuring consistency and clarity. Monads encapsulate the results of operations and manage them predictably.
00:19:57.840
They allow us to handle the complexity of error management smoothly, focusing on binary outcomes (success or failure) and creating a clean, maintainable error handling process.
00:20:31.280
When implementing monadic handling, the result consists of either a success with a value or an error. This binary approach simplifies how we handle function results, allowing us to maintain clarity and consistency across our software.
00:21:46.320
Now, let’s explore how we can effectively represent the monadic result in our Ruby programs.
00:22:20.880
We can use constructs like OpenStruct to define a structure that holds both the success value and any accompanying error details. This way, we can operate on error handling and function results clearly and consistently.
00:23:17.120
Another crucial component is the Service Object, which is designed to execute a single action in our business logic while centralizing our set of operations such as validations and calculations.
00:23:51.640
The Service Object approach leads to better-defined APIs for all parts of our application.
00:24:29.200
In our proposed monadic approach, we leverage the Service Object and monadic results to create a resilient system that gracefully handles both success and error states of operations.
00:25:16.560
Through this strategy, we ensure error handling is cleaner, more maintainable, and allows separate handling of different parts of our software.
00:26:06.560
In actual implementation, we can create a base class for our service that encapsulates the monadic results and operates on our business logic, enhancing the clarity of our controllers.
00:27:09.760
As we implement this in our application workflow, we see that error handling is done more clearly, leading to better maintainability and a smoother overall flow.
00:27:56.680
Additionally, we have other libraries like Dry Monads that offer robust tools for implementing monadic strategies in Ruby.
00:28:51.040
In the context of error handling, we constantly ensure the validity of inputs to avoid undesired function outcomes.
00:29:34.560
By doing this, we reduce the chances of unexpected errors and improve the robustness of our applications. It is crucial to distinguish between validity and execution errors to minimize disruptions in our software development.
00:30:29.680
Our encapsulation strategy enhances our flow management. Utilizing traditional exception handling is typically more reactive, while a structured approach allows us to handle input issues effectively before they become runtime exceptions.
00:31:57.920
In our talks, we emphasize that both error handling techniques—traditional and monadic—are complementary rather than mutually exclusive.
00:32:58.720
As we raise exceptions, it's essential to consider whether the error is due to an expected failure that should be handled proactively, further developing our software's robustness.
00:33:45.440
This talk aimed to provide a new strategy for error handling by understanding different error types in software applications and creating robust business logic layers to encapsulate errors effectively.
00:34:37.447
Thank you for your attention! Do you have any questions?
00:35:04.680
I appreciate your questions! Yes, we often face errors while calling services and need to handle the errors effectively to maintain a seamless user experience.
00:35:46.800
Understanding the distinction between expected and unexpected errors is crucial in implementation. By utilizing a standard and clear approach, we can differentiate these errors and address them appropriately.
00:36:37.720
As software engineers, we should strive for clarity in our error handling to improve our systems. The goal is not just to fix errors as they arise but to build solutions that minimize these errors from the start.
00:37:52.400
Thank you all for your insightful questions. Let's continue to improve our error handling strategies as we work together in teams. Remember, consistency is key, and clear communication among team members about error handling can enhance collaboration and efficiency.
00:39:43.680
So now, let's take a break until 1:30 PM when we'll continue with the afternoon sessions. Thank you all!