Talks

In Relentless Pursuit of REST

In Relentless Pursuit of REST

by Derek Prior

In the presentation "In Relentless Pursuit of REST" by Derek Prior at RailsConf 2017, the focus is on the significance of RESTful architecture in improving Rails applications. Derek emphasizes the benefits of adhering to REST principles, arguing that they lead to clearer, more maintainable code, which aids developers in managing the growth of Rails applications effectively.

Key Points:
- Understanding REST: Derek begins by addressing the misinterpretation of REST and the debate over its implementation, indicating that while many feel they are not adhering to REST, the primary value lies in consistency rather than strict semantic adherence.

- Importance of Nouns and Verbs: Emphasizing the distinction between resources (nouns) and actions (verbs), he advocates for a focus on these elements to simplify code structure. This approach leads to the creation of smaller, well-defined controllers and models, countering Rails anti-patterns.
- Common Anti-Patterns: Derek identifies several common anti-patterns in Rails applications:
- Custom Actions: He suggests avoiding unnecessary complexity by utilizing standard REST actions instead of creating custom ones.
- Complex Actions: He highlights problems with overly complex controller actions and promotes the use of smaller service objects to encapsulate business logic.
- Ambiguous Language: The need for a ubiquitous language throughout the application is underlined to prevent confusion and miscommunication.
- State Machines: Derek critiques the common implementation of state machines in Rails, which tend to become bloated. He suggests that breaking complex logic into smaller objects can lead to cleaner, more understandable code.
- Refactorings for Clarity: The speaker illustrates how refactoring code to better align with REST principles can lead to clearer language and more maintainable applications.
- Boring Code Philosophy: The talk concludes on the idea that boring code—simple, predictable functions and classes—is desirable in a Rails project for reducing errors and easing maintenance.

Derek’s final takeaway is that every Rails developer should embrace RESTful design principles and aim to create boring, straightforward code that inherently improves the application's longevity and reduces the likelihood of bugs. By being methodical and consistent in how REST is applied, developers can harness the full power of the Rails framework, emphasizing business logic instead of convoluted implementation details.

00:00:12 Welcome to 'In Relentless Pursuit of REST'. My name is Derek Prior, and I'm a Development Director at Papad, working out of our Boston office. I'm super excited to be here kicking off RailsConf with all of you. I feel like I've won the lottery by getting to present first, as it means I can enjoy the rest of the conference without worrying about this presentation. My Twitter handle is up there on the screen, so feel free to tweet me feedback during or after the talk, or catch me in the hallway later this week. I have some awesome swag to hand out, including cool notebooks and potentially some t-shirts, so do find me!
00:00:39 Ostensibly, this is a talk about REST. But what does that really mean? I’m not going to recap Roy Fielding's dissertation here. There have been plenty of conference talks on this topic—at every RailsConf, there has been at least one presentation about REST. So, why should this RailsConf be any different? In addition to those conference talks, there are probably hundreds of blog posts by the community, half a dozen books or so, and hours of podcast material, some of which I'm responsible for. A common refrain among much of this content is that we're somehow doing REST wrong.
00:01:16 There can be numerous ways that we, as programmers, implement REST incorrectly. Many of us love semantic arguments, and REST provides plenty of room for them. REST is fundamentally about the power of HTTP, which opens up numerous arguments we can have. For example, do you have an API? Do you version that API with a slug in your URL? Or should you instead use the Accept header for content negotiation? Perhaps this was an appropriate approach a few years ago.
00:01:39 Notably, there was a pull request to Rails to change support or add support for the PATCH verb because it was more semantically correct than how we were using PUT. I remember having strong feelings about that at the time, believing we should be using PATCH. Now that we can, it seems that nothing has changed. Then there’s hypermedia, which is somewhat alternately read as an extension or rebranding of REST. It aims to provide additional URLs for clients to use in order to gain more details about a resource. This idea suggests that clients shouldn’t be building their own URLs; instead, we should provide them.
00:02:10 In my opinion, the reality is that none of those discussions matter as much as I thought they did. The strong feelings I had about using PATCH ultimately didn’t change my happiness or productivity as a developer, nor did they contribute to my application's success. Most of us in this room likely work on a single app or a small number of APIs with clients that we control because we often are the ones writing those clients or have a business agreement with the responsible company. In all these cases, consistency is what truly matters.
00:02:44 We can and should make consistent decisions regarding our RESTful implementations, and then move on. The remainder of that discussion becomes more academic rather than practical. This isn’t to say that you shouldn’t care about those discussions at all; you should have opinions and engage in discussions about them. However, I believe that the importance we assign to them often exceeds their practical benefits.
00:03:13 So, this is a talk about REST. What am I going to talk about? I want to address how REST can improve my code and make my job easier. Specifically, I will demonstrate how REST can make code easier to read, maintain, and extend, as well as ease the growing pains that Rails applications typically experience.
00:03:40 To that end, I’m going to discuss nouns and verbs in RESTful responses, which correlate to resources and actions. Resources are our domain objects, and actions represent the things we can do with them. This is a foundational aspect of REST, albeit lacking the marketing shine of concepts like hypermedia. We will focus on resources and actions to guide us toward creating simpler components.
00:04:06 Simplicity plays a key role here; small controllers, small models, small views, and small objects are the antidote to many Rails anti-patterns I encounter from application to application. Yes, in this talk we will discuss several common anti-patterns that many of you have likely encountered as well. We will explore how RESTful design helps us avoid and correct these anti-patterns.
00:04:40 The first anti-pattern I’ll address relates to custom actions. There’s nothing inherently special about the seven default actions provided by a Rails scaffold controller. Actions like new, update, create, and others are common, yet Fielding didn’t endorse any specific set of actions for REST.
00:05:14 The best way forward is to adhere closely to the principle of convention over configuration that David alluded to. This practice helps us avoid numerous pitfalls, as it's an early step in steering clear of many anti-patterns. Before diving into more detail, let's take a moment to discuss how I typically get started on a Rails application.
00:05:55 When I come onto an established Rails application, the very first thing I do is open up the routes.rb file. A well-structured routes file informs me a lot about the application's capabilities concisely. It also expresses the business language of the application, highlighting what we are actually working with. Conversely, a poorly structured routes file reveals a lot about potential issues, such as large controllers or code that I need to read to understand what the application does.
00:06:33 I thought we could begin with an example of a simplified routes file that I commonly see in Rails applications. On the first line, we have a singular user resource, leading me to infer that the singular user likely denotes the current user. The actions we support here—edit and update—indicate that they are related to modifying the user record, possibly their user profile.
00:07:00 However, we also see two custom member actions for editing and updating passwords. At first glance, these may seem harmless, but I immediately want to know how 'edit password' and 'update password' differ from the standard edit and update actions for the user. Why are they part of the user's controller? This curiosity drives me to dive deeper into the user's controller.
00:07:37 Before we do that, let's visualize what those member routes would look like if we wrote them out in detail without using the resources helper. Breaking those down results in mappings for users' edit passwords and users' update passwords—revealing a crucial missing resource. This noun-verb pattern suggests a substantial gap, namely, that our resource—password—is effectively absent.
00:08:14 We could remedy this by creating a password resource in our routes, establishing it properly. We can use standard actions like edit and update for the passwords. Here’s how our passwords controller might look: all we've done is extract the previous ‘edit password’ and ‘update password’ functionality into a new controller while ensuring that the actions maintain their conventional naming conventions.
00:08:44 At this stage, we frequently encounter questions or objections. A common one is about the necessity of creating another controller. My response has always been that I've never regretted extracting a controller. The benefits of keeping controllers small and focused far outweigh any concerns about having too many files.
00:09:24 This practice serves as a vital step toward larger refactorings as we will observe throughout this talk. Another realization usually surfaces: ‘password’ isn't a model in this instance. It's quite common for developers to expect a one-to-one correlation between Active Record models and controllers. However, controllers can allow flexibility beyond simply mapping to a persistent model.
00:10:04 Our controllers can map to single columns on an Active Record object or even plain Ruby objects that aren't persisted at all if they fulfill some specific need. This insight has been a lightbulb moment for many developers I speak with, refining their Rails code significantly.
00:10:45 Now, let’s address the issue of complex controller actions. These often manifest as lengthy controller actions that might involve multiple private methods or complex logic. Such actions are difficult to read, test, and refactor, making it challenging to isolate pieces without disturbing the overall action.
00:11:26 It's important to recognize that we can often decompose complex actions into several smaller actions across multiple resources. This is the ideal path to take. Mostly, we write complex controller code over time, as initial actions tend to be straightforward and clean.
00:12:09 Take, for instance, a controller action that allows users to update the caption on a photo they uploaded. It’s structured simply and effectively. However, as we add requirements over time, the initial boring controller action undergoes transformations.
00:12:46 Consider a scenario where the product owner asks us to allow users to mark a photo as featured. This seemingly harmless feature leads to another layer of complexity, eventually requiring logic to ensure only one photo is featured. The additional business rules quickly add complexity.
00:13:33 One solution might involve updating our existing controller action to accommodate these changes, which begins to morph into a complicated mess. As this action develops, we often lose sight of the initial simplicity.
00:14:11 The complexity arises from differing perspectives, where we initially approached the update with the idea of merely updating attributes but later had to account for specific conditional logic and processes for attributes with varying importance. The dissonance this creates complicates our controllers.
00:14:49 However, we can resolve this by introducing new resources to encapsulate this complexity. Instead of a convoluted controller, we could introduce a featured flag resource that simply represents flagging or unflagging photos.
00:15:31 Doing this allows us to revert the photo controller to a clearer and simpler structure. By leveraging service objects, the most complex logic moves outside the controller and into these more manageable classes, thereby maintaining neat controller action shapes.
00:16:11 This simplification is beneficial as the new processes are easier to test and maintain. The overarching lesson is that during development, it’s vital to keep your controllers and models boring; this prevents unnecessary complexity and enhances readability.
00:16:47 When reviewing routes or controller actions, I often come across ambiguous language, which is the third anti-pattern I will address. How many of us have an 'orders' controller with a 'process' action? What does it mean to process an order? The answer can vary greatly depending on whom you ask.
00:17:29 If a warehouse employee is asked, they might describe it as the process for picking and preparing an order for shipment. In contrast, a finance person might interpret it as processing the corresponding credit card charge. This ambiguity means that translating business terms into code can cause widespread confusion.
00:18:01 To avoid confusion, it's critical to have a ubiquitous language across the application and the business itself, ensuring clarity and reducing miscommunication about required changes. Instead of vague terms like 'process,' we need actionable, clear resources such as 'ship' or 'shipment'.
00:18:37 Refining the language in our applications results in clearer code and more straightforward discussions within the team. With our new 'ship' resource, we can update the controller actions accordingly, eliminating ambiguity.
00:19:09 It’s also crucial to avoid cluttering our controllers with unnecessary complexities, such as callbacks and state mutations, which can lead to greater confusion. Each action and corresponding controller should serve a clear purpose and not be mired in legacy logic that complicates understanding.
00:19:56 Instead, we need to create focused resources that encapsulate meaningful actions. This ties back to understanding the resources involved. If a state machine manages your order transitions, consider what associated records need to be created when transitioning between states.
00:20:45 By focusing on those resources and their corresponding behaviors, we can derive an implementation that is much clearer and easier to manage without creating convoluted controllers. We can streamline our applications and reclaim clarity in our processes.
00:21:33 In conclusion, as we tackle anti-patterns within our applications, we should approach our designs with a mindset oriented from the outside-in. This is a crucial principle, especially when doing Test-Driven Development. However, it also applies to designing domain object interactions, resulting in clearer designs and better code overall.
00:22:17 By concentrating on the high-level functions of our application and maintaining adherence to the principles of REST, we can effectively organize our systems into manageable pieces, facilitating clearer frameworks for collaboration and reducing common misunderstandings.
00:22:55 The final objection may arise in refuting the applicability of these recommendations to projects perceived as more intricate than the standard CRUD app. Some might argue that their application is a 'snowflake' or fundamentally different. In most cases, however, this hesitation stems from a lack of imagination.
00:23:43 What we often find is that determining the actual application requirements and resources leads to a clearer understanding of the necessary functionality. Ultimately, we want our applications to be boring CRUD apps because Rails excels in that domain.
00:24:35 Focus on identifying the real resources within your application and design your system based on those insights, rather than getting bogged down by minute details of your data model or storage considerations.
00:25:12 At this time, I’d like to open the floor to any questions.
00:25:44 The first question regards how to manage state capabilities in the context of a wizard-like multi-step process. Depending on the specifics, you could start by creating the overarching resource in step one, and in subsequent steps break it down into associated records as applicable.
00:26:20 If your process involves conditional checks, think about how those can reflect the development of your resource's attributes over iterations. A simple example could be to have a shell object with gradual complexity built around it based on user inputs.
00:26:59 The next concern was about potential dissonance when transitioning between different coding paradigms in a well-established application. It’s important to encourage collaborative agreement on what defines improvement within your team dynamics. Incremental changes often inject flexibility and clarity into existing structures.
00:27:38 When it comes to extracting complexity from state machines, the key is to find common ground within service objects to encapsulate specific actions required for your domain logic effectively. Each service object can focus on a specific responsibility while retaining the overall application’s integrity.
00:28:24 The final question stemmed from a need to ensure better practices within the coding generator framework. While generators offer a solid starting point, they may lack the specificity required to address intricate requirements specific to your project. It might be worth discussing adjustments with your team as the generator framework evolves.
00:29:04 When it comes to alternative action names—like 'reorder'—for collections, recognize that these are effectively resources in their own right. Allowing for clear, meaningful naming can aid in aligning business and code narratives. Aim for clarity and consistency in how actions and resources are identified throughout the application.
00:29:43 Consistency is key, and this approach is fundamental to, ensuring all team members are aligned in the desired structures and methodologies. By framing objects and actions effectively, you're not only alleviating confusion but also simplifying maintenance through well-defined boundaries and behaviors.