Talks

Beyond the service object

Beyond the service object

by Nick Sutterer

In the talk titled "Beyond the Service Object," Nick Sutterer, an Open-Source developer at Trailblazer GmbH, discusses the intricacies of service objects in programming and their role in abstracting business logic within applications. The presentation, delivered at the International Ruby Conference Friendly.rb 2023, emphasizes the importance of abstraction in programming and how it simplifies complex tasks.

Key points discussed include:

  • What is Abstraction?: Nick explains that abstraction in programming introduces higher concepts to tackle lower-level problems, using examples like assembler and CSS to illustrate how abstraction helps in simplifying the coding process.
  • Service Objects Defined: Service objects encapsulate different functionalities, particularly focusing on business logic in Rails applications, covering tasks beyond the basic rendering of applications.
  • Challenges with Business Logic: As applications grow, mixing concerns across various components can lead to challenges in debugging and onboarding new developers. Nick highlights how actions like saving or deleting drafts should ideally be encapsulated within service objects to maintain cohesion.
  • Innovations from Trailblazer: Sutterer elaborates on his motivation to develop the Trailblazer framework, which provides a structured approach to managing business logic through defined operations, enhancing testing capabilities and code organization.
  • Building a Robust Abstraction: The approach allows users to manage business logic by defining clear steps, leading to improved flow and error handling through contexts and result objects. Nick stresses ongoing education to overcome misunderstandings of the Trailblazer library, derived from past opinions.
  • Implications for Modern Development: Drawing parallels to modern JavaScript frameworks, he explains that the principles of service object design can be applied universally across different stacks, enhancing efficiency in control logic.

In conclusion, Nick emphasizes that adopting service objects iteratively can lead to stronger, more organized code structures, promoting an overall efficient development process. He invites attendees to engage with him post-talk for further discussions on service objects and Trailblazer, presenting them as invaluable concepts in contemporary coding practices.

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.