Refactoring Patterns for API Consumption

Setup

We will start with the consuming-apis-complete branch of the Set List API for this lesson. We will be looking at the ImagesController first.

Instructions for using Rails Encrypted Credentials can be found in the “Required Setup” section here.

Learning Goals

  • Identify SRP and MVC violations with implementing API consumption entirely in the controller

  • Refactor code to pull logic out of the controller

  • Identify the purpose of a Gateway/Service and a PORO

Vocabulary

Refactor - to change code in a way that the end functionality still works as intended, but reorganizes it in a way to make it easier to maintain, easier to test, etc.

SRP - Single Responsibility Principle; the ideal that a piece of code should be responsible for one kind of task (this can be at a class level, a method level, etc.).

Design Pattern - an implementation of code which follows as much “industry standard” as possible to achieve clean organization of our code.

MVC - “Model, View, Controller” design pattern; a way of organizing our code into logical portions where our “business logic” is managed by the Controller, the “data logic” is managed by the Models, and the “presentation logic” is managed by the Views.

Reminders

  1. There is no one right way to refactor and structure API consumption in Rails or in any framework. We will introduce you to a few ideas and design patterns but we are not picky about which patterns you use.

  2. Before refactoring anything, it is essential that robust testing is already in place. See section on “Red, Green, Refactor Below”


Identifying Candidates for Refactoring

Warm-Up

  1. What anti-patterns do you see in the current implementation of API consumption in the consuming-apis-complete branch, specifically in the ImagesController?

  2. List out all the responsibilities the controller is handling

How do we refactor?

1. Declarative Programming

Throughout this refactor, we will use a technique called Declarative Programming. This is also referred to as “dream-driven development”. Simply put, we write the code we wish existed and worry about implementation details later.

We use this strategy in life all the time. A statement such as “I need to travel to New York City.” is an example. There is no mention of how we plan to get there. We could take a train, car, plane, bicycle, or some combination but those are details we will worry about later. Depending on your origin different strategies make more sense than others.

It’s less likely, although perhaps more exciting, to select a means of travel without knowing the final destination. “I’d like to ride a train for 12 hours, a bus for 3 hours, and a boat for 2 hours. Where can I go?” There’s a good chance you won’t end up in NYC.

Writing code this way makes it more likely that we’ll end up with abstractions that aren’t vulnerable to breaking if implementation changes.

For example, currently we are using the Pexels API to retrieve this data. But maybe this data used to be provided by an API called Unsplash. By deciding how we want to interface with these objects and classes (picking our destination) prior to implementing API calls (how we are going to get there), we make this code more robust and less brittle. Imagine if we change APIs, and therefore the response we get back with the image data changes. The keys of that hash would likely change and this endpoint would suddenly stop working. When we refactor, we want to favor designs that minimize the number of layers that need to change if we switch out our API.

2. Red, Green, Refactor

We will also be using the Red, Green, Refactor technique. Red refers to a failing test, green refers to a passing test, and refactoring refers to making changes to improve code. We want to start with a failing test and then make it pass (red to green). We already did that step in the previous lesson. Then we make a refactor to improve the code. As we make that refactor, our test will most likely break, so our goal is for that refactor to end with our tests passing again. This way, we can use our tests to check our work every step along the way. We want to try to keep our refactors small and get back to green as often as possible to maintain our functionality.


Refactoring Our Controller

So far, most of the code we’ve written in Rails has fit nicely into the MVC structure, and we haven’t explored writing files much beyond controllers, models, and serializers. But now with API consumption, we’re handling logic that doesn’t fit squarely into any of these components.

Brainstorm: If you were building a vanilla Ruby application, what types of files do you think you would make to extract this logic out of the controller?

Domain Objects vs Utility Classes

You might have noticed that technically, both our ImageGateway and our Image class are POROs, in that they are both regular ol’ classes with no special Rails magic involved. However, as we design POROs, it’s important to think about the difference between these 2 types of classes.

  • Domain Objects represent a “thing” in the real world, and can be instantiated and hold state. We can think of these like a noun
  • Utility Classes perform a certain task, and typically don’t need to be instantiated. They might contain all class methods. We can think of these like a verb

Checks for Understanding

  • Describe your preferred refactoring workflow (i.e. red-green-refactor, declarative programming, etc.)
  • Describe the code smells from the controller action at the beginning of this lesson (ImagesController)
  • What is the responsibility of every new file you’ve created today?

You can find a completed implementation that uses a gateway and a PORO domain object here

Lesson Search Results

Showing top 10 results