00:00:07.840
Hey everyone! I have more good news! I'm the last thing standing between you and beers in hand, so I'll hurry up. If you think Tom is great with his 132 slides, I have 134! But don't worry, I'll rush through it.
00:00:13.480
Now, for those of you who recognize this design from my talk five weeks ago, don't worry! It's not the same talk. I've changed five or six slides, so it won't be boring. Preparing presentations is a lot of work, and I was planning to talk more about going beyond service objects, but today we might focus solely on service objects.
00:00:28.960
As we know, time is a limited unit, and you are all too familiar with that from software development when you do scrum estimations. Service objects, in my opinion, are abstractions. The first question we should focus on is: what is abstraction? It's a very deep topic. I’m not going to mislead you into thinking I've read five books on abstractions and philosophy, so let's keep it simple.
00:01:07.960
In programming, abstraction introduces a higher concept to address more concrete, low-level problems. This can often involve the development of a higher-level language. For instance, Tom was discussing domain-specific languages (DSLs) earlier. Normally, when programming, we might think in ones and zeros. Sure, you could do that and still write great web software, but it would be a real pain. Talking about programming in binary is cumbersome—like"Hey Frank, did you see that bug online?" "No, there’s no line for 011110." So it's not practical.
00:01:51.040
So, what people did was to introduce something called assembler. An assembler is an abstraction because you’re not directly dealing with the binary anymore. Instead, it allows you to work with simple lines and instructions, essentially creating an API for binary code. A very low-level example of abstraction is CSS, which is a language used to tell the browser things like, "this should be red" or "this button should be green." I’m not a CSS expert, and there are many things I struggle with when it comes to it.
00:02:25.680
In basic CSS, you have something like class definitions, and you can add attributes and IDs to style your elements. It can get pretty tedious, and whenever I spend more than 30 seconds with CSS, I feel incredibly frustrated. But a beautiful abstraction was introduced with SCSS, which brings more progressive concepts to the CSS abstraction layer, such as nesting classes, mixins, and inheritance.
00:03:02.239
However, recently, someone decided to simplify everything by introducing Tailwind CSS. I’ve been using Tailwind for the last few weeks, and, honestly, it was not as fun as I hoped. The idea behind Tailwind is that instead of using SCSS and coming up with class names, you use a lot of utility classes directly in your HTML, resulting in a clean and functional appearance. This indicates a return to a simplified abstraction despite complexities in the earlier frameworks.
00:03:54.639
Innovations in abstraction do not signify perfection. In two years, others might create something like Headwind aimed at solving issues with Tailwind. This shows that abstraction is a balancing act: you introduce a new language designed for developers at a higher level, but you must balance it with the learning curve.
00:04:32.759
For example, Active Record is a great abstraction for working with databases since I struggle with SQL. Its learning curve isn’t steep, which makes it user-friendly. This brings us back to service objects as an abstraction. They ideally encapsulate different functionalities. But what is a service object, and what benefits does it offer? Why do we get better code with them?
00:05:04.240
In our world, service objects cover business logic. However, business logic can mean different things to different people. In the context of Rails, business logic encompasses everything that happens after routing to the rendering of the result.
00:05:28.480
This includes tasks such as authorizations, data massage from params, validations, and possibly sending notifications. Anything you encounter beyond simple rendering is business logic, a core responsibility within any application. This includes validation control flow as well, ensuring that if a certain condition isn't met, the remaining code is halted. Business logic is not limited to controller actions; it encompasses any operation your application performs.
00:06:09.720
In many cases, your design may reveal itself as faulty if you find that actions can’t be performed correctly through natural paths, often seen in legacy applications. As we build applications, every public function—saving drafts, previewing, publishing, and deleting—should be encapsulated within service objects. This leverages all of our business logic in one cohesive space.
00:06:35.920
It may involve moving reusable portions of code into service objects, which can be triggered from the controller or wherever necessary. However, the issue arises when you mix concerns across various filters and callbacks. If you over rely on specific global callbacks in Active Record, they can become unwieldy, introducing unwanted complexity.
00:07:05.440
This spread of business logic makes it a challenge to debug and understand the flow of your application. Onboarding new developers can also be challenging because they must familiarize themselves with scattered logic that exists across different layers.
00:07:37.440
Now, let’s take a step back. I wasn't satisfied with the way Rails structured its business logic, which is why there’s an emphasis on operations as abstractions in Trailblazer. The idea behind it is simple: we can take all of our core business logic and encapsulate it within operations that can be tested easily without involving the entire application stack.
00:08:02.680
My first step was to introduce the operation, a class that encapsulates the business logic and carries out its processes in a much more manageable manner. Although it produces a more isolated and coherent way to handle business logic, the challenge remains when you want it to perform additional steps.
00:08:42.400
Developers began to voice their concerns, indicating frustration with the structure of these operations. The need for a new abstraction layer emerged to facilitate easy additions to this business logic, which XYZ introduced, allowing for a DSL (domain-specific language) that maintains clarity while enabling efficient complexity management.
00:09:23.639
Moving towards current practices, in Trailblazer 2.0, we guide users to structure their code into clear steps which can easily be triggered from various parts of the application. By defining these processing steps, it grants more freedom and flexibility in addressing business logic without cumbersome overrides in existing code.
00:10:03.600
In essence, you define your operations with more encapsulated steps, invoking the call method on it in the standard format while handling parameters easily through keyword arguments, which is quite intuitive.
00:10:49.680
Trailblazer supports a model of passing contexts from one step to the next, allowing developers to manage the information flow throughout the various stages of an operation seamlessly. With the result object, you can check whether processes succeeded or failed, providing a simple outcome that allows for responsive error handling.
00:11:28.920
All these improvements lead to a more stable abstraction, one that many users have begun to embrace. The problem remains that several still misunderstand Trailblazer due to its previous coverage in literature, often overshadowed by their perceptions of earlier complexities. Regardless, ongoing education remains crucial.
00:12:06.800
As we look further to React and modern JavaScript frameworks, specific patterns have emerged that entail complex state management. The principles of service objects can similarly apply here, consolidating control logic within these operations.
00:12:43.720
I’ve personally worked extensively with several layers, extracting notions that define encapsulated logic that can be reused effortlessly amongst various parts of the application. You may implement separate solution layers based on requirements, which reflects in design patterns often discussed in other contexts.
00:13:21.200
The broader implications of Trailblazer encourage efficient control flow where you apply defined paths handling success or errors. The greater scope, supported by related libraries, showcases trade-offs users must consider but also provides immense capabilities.
00:13:59.000
In conclusion, every layer in your application can benefit from applying service objects to manage business logic effectively. Over time you can introduce them iteratively, leading to eventually more organized and effective code. A non-intrusive framework approach facilitates the addition of these structures, as demonstrated in various examples today.