Service Objects

Summarized using AI

Structure and Chain your Poros

Benjamin Roth • November 25, 2016 • Florence, Italy

In this video, Benjamin Roth presents a comprehensive overview of structuring and chaining service objects within the Ruby programming community during the RubyDay 2016 event. His talk emphasizes the importance of service objects in encapsulating business logic, highlighting their utility for making applications modular and maintainable. The main themes and key points discussed include:

  • Definition and Purpose of Service Objects:

    • Service objects encapsulate logic and express business operations independent of frameworks.
    • They serve as a clear representation of application actions, such as user creation, and help manage related tasks like validations and callbacks.
  • Best Practices for Naming:

    • Naming conventions should focus on verbs to indicate actions; minimizing public methods simplifies service object interactions.
  • Reusability and Structure:

    • Service objects should be designed for reusability, functioning seamlessly in various application contexts.
    • Different approaches to structuring service objects were presented, including class-based and instance-based triggers.
  • Handling Success and Failure:

    • Strategies discussed include using booleans for outcomes, exception handling, and event broadcasting.
    • He emphasizes error management as a critical aspect of service object design.
  • Implementation Techniques:

    • Examples include the use of libraries like Whisper for event management and the application of monads for success and failure handling.
  • Chaining Service Objects:

    • Proper chaining of service objects aids in efficient error tracking and ensures clarity within application logic.
  • Conclusion and Importance of Service Objects:

    • Roth concludes that thoughtful implementation of service objects enhances application architecture, encourages isolated business logic, and facilitates better maintenance practices.

The talk effectively advocates for using service objects to streamline software design, making complex applications more manageable. Roth invites further discussion, emphasizing the significance of dependency injection, event handling best practices, and modular design in programming workflows.

Structure and Chain your Poros
Benjamin Roth • November 25, 2016 • Florence, Italy

rubyday 2016

00:00:10.390 Thank you very much. A part of my talk is about handling failure. We had a pretty good example so far, and I hope everybody remains calm. Thank you! Hopefully, my talk will cross paths with some of the things I watched today.
00:00:21.529 I didn't make my honeymoon in Florence yet, and I used to be quite subject to a lot of uncertainty. So, everybody seems to be very cautious as well. Just like Xavier, I'm very cautious too, and I won't provide any conclusions yet. I'd like to give a state of the art overview of what we find in the Ruby community.
00:00:38.329 Maybe you'll recognize some of the techniques you use. Of course, I have my own opinions, but we will explore various techniques.
00:00:52.370 To start, let's discuss structure and how to chain your services. There’s a lot of terminology used in programming, and it can be very complicated. The term 'poor' is often used, even if it doesn’t have any real meaning.
00:01:04.820 The main use case for service objects is to encapsulate logic. Simoni just provided a great example before. The popular use of service objects is in application service operations, commonly referred to as operation comments or retreat pattern objects.
00:01:11.930 In this talk, I will refer to them as service objects, and I hope you will follow along. So, to define a service object: it is supposed to show what your app does. It's designed to express your business logic and is framework-independent.
00:01:35.600 A service object is specialized, and because of its name, it carries context. For example, you could have a service object for 'create user', which indicates that every time you create a user, you may need to send an email or perform other actions.
00:01:43.670 So, it can handle conditional validations and callbacks. It serves as a wall in itself and is very convenient. Service objects have been developed and debated for years, and it's really not a new concept. There are countless gems available; you might be using some of them, and they apply different strategies.
00:02:07.989 I couldn't resist creating my own, which is at the bottom of the list, but I know there are many others. There are even books on the subject. For example, Trev laser is quite a trend these days. Nick told me that you don't need a coupon because the 'trespasser' book one is free.
00:02:27.400 However, I do have a coupon for the 'Fearless Refactoring' book if you're interested, just let me know afterward. In terms of service objects, there are topics that are pretty much settled, while others are still much debated.
00:02:50.000 Let’s start on the same ground and talk about what is generally accepted regarding naming, which is often a pleasant surprise. Naming is indeed always complicated, but having fewer public methods can simplify this.
00:03:04.820 In service object naming, we typically use verbs since they are meant to perform actions rather than represent something. Your service object can handle data representing something, but it primarily performs actions.
00:03:23.300 The public methods are generally minimal, allowing you to trigger the object easily. We'll look at the various techniques I've observed, which really depend on the flavor of service objects you choose to utilize.
00:03:41.540 Service objects are designed to be reusable. For instance, if you have service objects to build, they should be usable right out of the box in your specs. There’s no reason to create objects differently from the rest of your application.
00:03:53.000 What everyone does on your website or application results from different actions, which are managed by your service objects. You could even configure a setup where you invoke your service objects sequentially to achieve results in a realistic environment, unlike a fantasy land of convoluted steps.
00:04:10.000 This method has its costs, as there may be many database actions involved, but that’s an important consideration. The main disputes arise around how to trigger actions, whether or not to coerce arguments, the structure of responses, and failure handling.
00:04:25.240 Let’s zoom in on triggering these actions. You have class-based approaches where common methods like 'call', 'run', or 'perform' are used. The alternative is instance-based, where you pass arguments to either the new instance or the performing method.
00:04:47.730 The choice really is up to you. There are various trends; I don't personally use them, but I know others find them useful for good reasons. There are several gems developed for this purpose; some of the most popular tools rely on service objects.
00:05:01.600 In Rails, there are countless ways to structure service objects due to how constants are loaded, leading to potentially strange cases. The first method is to create a services folder and have redundant service names for each class, though I do not prefer this approach.
00:05:12.830 Another option is to have a service named simply, like 'CreateAccount', which works well. The Trailblazer approach has its own intricacies, as Rails may not correctly locate the path if certain conditions aren’t met.
00:05:31.510 You could also create an empty folder within the services folder to ensure that Rails recognizes it, despite this method seeming absurd. It’s all about utilizing namespaces effectively.
00:05:48.700 In terms of responses, there are plenty of options. You could design a service that responds with a boolean: 'success?' which indicates if an operation succeeded, or have it render a response object that stands independently.
00:06:10.950 There could also be additional public methods, which can expose what was accomplished, such as success or failure states. Conversely, some services will have no response at all, which can be more straightforward.
00:06:29.980 Previously, we talked about being optimistic in Ruby, and that's okay. However, we need proper flow control as we implement this in our code. For example, in a service, you might create a new instance with arguments and call a public method to access the necessary data.
00:06:42.500 It's simple, yet it relies on uncertainty because you don't know what might occur during this process. Introducing an exception implementation can make things clearer. The structure can remain similar, where you'll call your service.
00:06:59.000 Based on the response from an external service, you will either return the data or raise an exception. Opinions on exceptions in Rails vary widely, with some suggesting they should be avoided due to performance considerations.
00:07:15.000 Others argue against using specific exceptions if they lead to complications elsewhere in the application. Ultimately, it’s a highly debated topic—with no definitive truth, as opinions differ significantly.
00:07:30.000 In this implementation example, you call the exception service, and if everything goes smoothly, you handle the desired path; if not, the rescue block comes to your aid.
00:07:48.000 Here’s the boolean implementation of the service. A success instance variable indicates whether the operation succeeded. If it did, you can execute a certain action; if not, you'll take alternative steps.
00:08:03.000 This raises the question of how to implement error handling effectively within your services. We'll explore various implementations of error handling throughout our examples.
00:08:12.720 For instance, there are event-based services, such as the popular library Whisper, which is widely used for broadcasting events. In this case, you'd have two paths: the success path, broadcasting a successful outcome, and the error path, providing the reason for the failure.
00:08:27.230 To use it, you'd create an instance of a service, invoking methods for success or error in the corresponding cases. On calling the service, you can execute specific blocks based on successful broadcast or errors.
00:08:39.300 Another approach involves using monads to manage success and failure. In Ruby implementations, this typically looks like returning success or failure objects, executing different blocks of code as appropriate.
00:08:51.720 However, it’s essential to be cautious with their syntax, as you must return a confirmation for success or failure to correctly navigate the chaining process without causing unintended issues.
00:09:05.000 In conclusion, while the various implementations may differ, the principle of structuring your service objects to manage success and failure effectively remains constant. Achieving proper chaining helps ensure your application behaves as intended.
00:09:20.000 Chaining helps maintain efficiency and error visibility within the application. Each service step could either fail or succeed, and you need to ensure you understand why it happened. Proper chaining means failing efficiently and ensuring that error handling is coherent.
00:09:51.600 It’s essential that in your implementations, you bear in mind the importance of success and failure paths to correctly handle outcomes. This control allows you to maintain sanity within your code.
00:10:02.250 With regards to the exceptions earlier mentioned, the main setup allows services to capture useful data and ensure clear parameters for success or failure outcomes. When properly structured, the resulting implementation can manage failures gracefully.
00:10:23.740 If you're implementing boolean checks on service operations, this can lead to tricky situations in your code. Effective error-handling maintains clarity and simplicity.
00:10:33.230 Let’s review the monad functions as part of our exploration. Although it can get cumbersome, especially when the services become interdependent, maintaining a logical and organized structure goes a long way.
00:10:46.000 You’ll want to ensure that added complexity through chaining doesn’t create confusion. This can be done by ensuring that failure handling is consistently implemented across all service objects.
00:11:09.000 As we conclude, remember that implementing effective service objects takes time. These objects can help simplify your architecture by isolating business logic and making it easier to test and manage.
00:11:20.000 In essence, any solution you choose, provided you shape your approach around service objects, contributes to a better architectural framework. The principles and layers you apply can significantly impact your workflows and ease of maintenance.
00:11:34.000 Once your business logic is appropriately isolated within service objects, you can better test it and manage changes. This isolation leads to cleaner architectures that can evolve without compromising the core functionalities.
00:12:04.000 It's important to recognize that your choice of architecture, whether it aligns with certain patterns, serves as a facade, helping keep your core logic intact and consistent.
00:12:27.000 Feel free to ask about event handling, as it's a common topic. When you use events solely for success and error handling, you might inadvertently create callback hell, where callbacks become unruly.
00:12:41.000 If you have specific requirements, such as creating events based on particular instances, you can structure them more effectively. My general recommendation would be to use dependency injection where necessary and apply best practices for modular design.
00:13:04.000 JavaScript, for example, has tools designed to assist in these contexts. When responding to HTTP requests, ensure you prompt users efficiently; prefer a quick response over prolonged waits.
00:13:19.000 Fail fast in situations requiring quick responses, and delegate longer wait processes to worker engines.
00:13:26.000 Red laser—though I don’t use it directly—offers valuable design patterns, and if you need alternatives in your coding journey, be sure to explore service objects thoroughly.
00:13:46.000 A service object is useful for sign-up forms where you need to validate a set of fields like names and emails. When time presses, using proper forms keeps your application data valid.
00:14:10.000 Design patterns provide structured approaches for various needs in programming. Service objects, for instance, can embody commands and often simplify interactions.
00:14:27.000 Not all design patterns are created equal; they each bring added value, with service objects focused on command functions. Though I don’t see a direct replacement, multiple patterns can help simplify workflows.
00:14:51.000 Your service objects could be further enhanced by orchestrating actions, potentially sharing context among them without complicating your code structure.
00:15:07.000 Think of orchestrated services as organized interactions that maintain clarity amidst complex relationships.
00:15:15.000 In summary, leveraging service objects thoughtfully can greatly enhance the architecture of your application. Thank you once again for your attention!
Explore all talks recorded at rubyday 2016
+4