Refactoring

Summarized using AI

The Curse of Service Object

Ivan Nemytchenko • May 15, 2024 • Wrocław, Poland

In the presentation titled 'The Curse of Service Object' by Ivan Nemytchenko at the wroc_love.rb 2024 event, the speaker critically examines the prevalent use of service objects in software development, particularly within Ruby on Rails applications. The talk begins with Nemytchenko's background and his intentions to challenge the conventional wisdom surrounding service objects. He introduces a method he created called 'O in pictures' for visualizing class interactions, which he uses throughout the presentation.

Key points discussed include:
- Definition and Functionality: Service objects are widely accepted for encapsulating business logic, improving code organization, and facilitating testing. However, many developers have criticized them for being overly complex and ineffective.
- Critique of Existing Models: Nemytchenko questions the core benefits claimed by proponents of service objects. He presents several examples where service objects are overburdened with responsibilities or contain unnecessary boilerplate code, thus complicating rather than simplifying functionality.
- References to Authority: The speaker cites industry giants such as Martin Fowler, Eric Evans, and Robert Martin, analyzing their definitions of services and advocating for a clearer understanding of service objects. He notes the lack of emphasis on service objects in Fowler's work on enterprise architecture.
- The Service Ruler: Nemytchenko introduces a 'service ruler' to measure service objects against criteria such as being stateless, activity-focused, and aligned with the principle of single responsibility.
- Proposed Solutions: Instead of using large service objects, he suggests separating code responsibilities into defined layers or 'shelves' to enhance clarity and modularity, proposing that this layered architecture could simplify processes and limit complexity.

In conclusion, the speaker urges a reevaluation of how service objects are structured. By refining the organization of services and emphasizing clear boundaries between functionality, developers can foster maintainability and resilience in their applications. He leaves the audience with a challenge to reconsider their reliance on service objects and encourages further discussion on the topic during the event.

The Curse of Service Object
Ivan Nemytchenko • May 15, 2024 • Wrocław, Poland

wroclove.rb 2024

00:00:11.480 I’m Ian. I’m originally from Russia and I have been living in Serbia for 10 years now. These are two startups I have been helping in the last few years. I’ve been in Rails since 2006 and I’m the owner of a small Ruby on Rails agency. I used to work for GitLab as a fun fact.
00:00:23.760 Those are two small projects that are not completely finished, but you might still find them useful. So, why am I here?
00:00:36.840 I’m here to play the role of this guy. I know that the service object is a widely accepted pattern in the community, yet we’re going to question this.
00:00:49.039 The plan is as follows: first, I’ll share a method that I invented a few years ago. I call it 'O in pictures,' and we will use it later in the talk. Then, we will see what developers say about service objects in their articles. We will examine the benefits they claim service objects provide and analyze the roots of the concepts. We’ll look into what figures like Martin Fowler, Eric Evans, and Robert Martin have said about service objects and try to derive insights from that.
00:01:16.920 Finally, I will show you how I do it and how I think about it. The last part is, in my opinion, the most important. So, regarding service objects, I’ll be bold: service objects are a very strange, controversial, and harmful abstraction that doesn’t make much sense. Furthermore, it contradicts the main architectural principles and provides no long-term benefits. Initially, it might seem that they offer benefits, but we will see that they actually do not.
00:02:08.720 If you’re interested, there is a whole presentation on O in pictures from my talk at Railsconf; there is a QR code that will be shown later if you’d like to check it out. The fun part is that this method was developed while I was teaching my students about object-oriented programming. I enjoyed using illustrations—specifically, drawing little characters to represent classes. I figured, why not draw classes as those little figures?
00:02:54.200 Eventually, I started developing the idea that you could visualize hands to represent how you interact with a class or object. So, if you want to interact with it, you would simply shake one of their hands. From there, I built the ontology of how classes interact, realizing that hands could become robotic arms with fingers through which methods could accept arguments. The number of fingers would represent the number of arguments, and those arguments would then get processed as instance variables. The end result could then be accessed through a hatch that opens to reveal the outcome.
00:03:35.040 Private methods are interesting because they’re still represented as arms, but they function internally—no one can interact with them from outside. So, if something goes wrong, you might still get a result, but from a different place, potentially leading to antipatterns. For instance, you might encounter too many arguments, too many methods, overly lengthy methods, or an abundance of conditionals.
00:04:28.080 We’ve discussed objects, but how can we visually express classes? I conceived a platform that spawns cartridges, each containing instructions on which object to create. When you pull the lever, the magic happens, and you spawn an object. Interestingly, the lever correlates to the hand; it can also have fingers, making it comparable to a method.
00:05:02.440 I used this method to analyze the talks of Sunday Mets, visualizing how the complexity of classes evolved over time. I found it very useful since you can assess the complexity without diving into the code. Today, we will utilize this method as well. Now, what is a service object?
00:05:32.560 I analyzed articles from various developers to gather insights. They argue that service objects enhance code efficiency and maintainability, improve structure and organization, and make testing easier. They encapsulate business logic, keeping code clean and organized. Service objects are generally seen as single responsibility. However, they also face criticism, with some suggesting that 'crappy service objects' might not be the best solution, yet many authors don’t mention viable alternatives.
00:06:45.991 Let’s look at some quotes from these articles. For example, we see an object created to handle book creation, but it’s laden with boilerplate code that, while necessary for structure, doesn’t provide much useable code. Another example is validating a discount code—it might better fit as a validation method within a model. Yet, for some reason, it’s also categorized as a service object. Additionally, we see validations and operations such as sending emails, which again feel more business-oriented.
00:07:59.959 Another example involves updating trip information, where external services like Google Maps are summoned to fetch distance and duration, further complicating it. Then, we have an authentication service that will be dissected later in detail. Lastly, we encounter a tweet creator, which operates as a wrapper around the Twitter API for creating tweets. In reviewing this code, we notice validation, calculations, creations, mutations, external service calls; it all feels a bit convoluted.
00:09:18.319 We’re left with a definition that seems like they just do some stuff, but is there a deeper philosophical idea behind service objects? To get clarity on this, I reviewed famous books from Martin Fowler, Eric Evans, and Robert Martin. In Fowler's work on patterns of enterprise architecture, he doesn’t mention service objects; rather, he describes something like a service layer.
00:10:24.200 This service layer defines the application boundaries, sets available operations within which domain object coordination occurs, and controls transactions. It's significant because Fowler notes that services can be called either remotely or locally. If our application operates within a traditional monolith rather than a microservice architecture, we might conclude that this layer might not be necessary for us.
00:11:24.760 Eric Evans discusses domain services, stating that they are stateless, named for activities rather than entities, and represent significant business domain processes. They may have side effects—an aspect I believe is crucial—and could be distinct from technical services, though he doesn’t specify those. Robert Martin, in his book Clean Code, emphasizes that services must have well-defined interfaces.
00:12:35.160 The context in which he mentions this is in regards to microservices and service-oriented architecture. Unfortunately, this is often irrelevant in our standard, low-complexity monolithic world.
00:13:35.120 However, Martin’s emphasis on the single responsibility principle is insightful. I’ve attempted to distill these disparate views into a 'service ruler' for measuring our service objects. This ruler will guide us as we evaluate domain services, highlighting their properties such as being stateless, activity-focused, representing business operations while coordinating domain objects, controlling transactions, and respecting the single responsibility principle.
00:14:43.760 As we analyze various service objects, we can see which comply with these criteria. I found instances of service objects that are not stateless—having constructors with assigned states. Some failed to be named for activity or entity, while others did meet criteria related to business operations, coordination of domain objects, and controlling transactions.
00:15:48.640 However, many service objects exhibited discrepancies when evaluated against our defined ideals. When naming a service object, ensuring it performs a single business operation is pivotal, aligning it with Martin Fowler’s perspective of having a single reason for change.
00:16:55.920 The final observations involve realizing that the so-called service objects do vastly differing kinds of work, which results in a lack of clarity in roles and functionality. When interconnections allow one service to call another, you risk creating circular dependencies—a messy outcome that can undermine cohesion.
00:18:11.480 The problem is compounded by the tendency of services to expand their responsibilities, leading to an amalgamation of application and domain logic. Therefore, we reconsider the structure and purpose of service objects, examining the caveat that they don’t need to be shaped like objects at all.
00:19:39.000 Perhaps instead of clustering everything together under the service object banner, we should designate distinct areas for varying functionality. I propose we can find an appropriate 'shelf' for different types of work rather than force everything into the same mold.
00:20:51.760 Throughout history, standardization has enabled the creation of greater complexity, attainable through defining and creating distinct components fit for purpose.
00:22:05.200 Now, I’ll illustrate how I implement these thoughts. By evaluating a piece of code, my first step is to determine the kinds of work it performs. I do favor procedural coding, as long as methods maintain consistency in terms of the work they handle. For instance, I can identify controller-level logic versus rendering versus mutation and where external system interaction occurs.
00:23:19.280 We had identified code types and now I can break them into sections or 'shelves' where similar work would reside. For example, standard business rules might belong within a model, while more intricate mutation logic fits within a mutator layer. By properly segmenting the code, I can avoid half-finished entities that cause issues.
00:24:31.920 Thus, I would construct a folder for mutators and build functions accordingly that capture these details, keeping them separate from business logic and focusing purely on operations for which they are responsible. That way, each method remains straightforward, and clearer procedural integrity is maintained.
00:25:45.440 When I examined controllers using this model of enforcement, we find each focuses on identifying users, ensuring the validity of their actions and preparing any necessary responses without entangling more complex business logic.
00:27:05.760 Ultimately, we establish a layered architecture that respects each component's purpose, with models, mutators, and business services holding their own responsibilities. This plays into principles of single responsibility, and having the necessary layering facilitates better modularity.
00:28:16.880 So, moving away from the increasingly large service objects that tend to develop is a healthier practice. By sourcing responsibilities correctly, we create more functional building blocks leading to clarity and maintainability as opposed to swelling monolithic structures.
00:29:45.160 This ethos reinforces our project quality by keeping the statelessness of objects while permitting each action to remain focused on singular operations.
00:31:08.000 Picture the increased comprehension achievable with distinct layers and thoughtful boundaries separating our code. Instead of merely copying functions across layers for lack of insight into organization, appraising where work aligns appropriately becomes essential. Leaning into the resources rails offers, we can more easily derive this sense of order.
00:32:35.120 So again, I urge you to think decisively where each piece of code belongs instead of forcing hybrid solutions. We've refined our service objects into more compact and manageable segments, allowing them to fulfill their prescribed roles without overstepping their domains.
00:34:18.079 Refactoring service objects into their rightful places asks us to reconsider a wider range of operational strategies. We have the potential to streamline processes while enhancing clarity, leaving us poised for success.
00:35:29.719 Ultimately, we find that each component must serve its purpose distinctly. This renewed approach responds fittingly to our primary intent—creating a resilient system that supports thorough implementation while keeping complexities at bay.
00:36:45.040 So, do you still think service objects are a good idea? I'd love to hear your arguments. Perhaps we could chat more about this during lunch. If there's any time left for questions now, I’d be happy to answer!
Explore all talks recorded at wroclove.rb 2024
+6