sinaptia waves

Consequences of parrot-like APIs

Nazareno Moresco
Aug 16th, 2023

Psittacism is defined as repetitive speech, reminiscent of a parrot echoing words without necessarily comprehending them.

On occasion, our APIs can exhibit this pattern, especially when interfacing with external services. For instance, consider we are using a service to manage reservations. A “parrot-like” behavior might involve sending reservation objects directly to the client. Here’s a basic representation:

class My::ReservationsController < ApplicationController
  def index
    my_reservations = reservation_service_client
      .get_reservations
      .filter { |res| res[:guest_email] == current_user.email }

    render json: my_reservations, status: 200
  end
end

Recently, we had the task of migrating endpoints like this to another service. It was then that we realized the several disadvantages of this approach:

  • Loss of response control:
    • Direct reliance on service outputs means we have no control over their changes. Unexpected alterations can potentially expose sensitive data to our clients.
  • Ignoring our responses
    • The psittacism inhibits us from knowing our exact responses unless we request them or query the service documentation, a tedious process.
  • Tight coupling:
    • This approach binds our system closely to the external service, both in terms of data structure and semantics, making migrations challenging.

On a related note, the mimicry in API design raises another concern: is an intermediate layer even required? Sometimes, a direct client-service interaction might suffice. Introducing an unnecessary layer can increase latency, complexity, and error margins. However, if there’s a need for access control (like rate-limiting or security protocols) or logging, such a layer is justifiable. In the provided example, restricting users to access only their reservations makes the layer indispensable.

We can fix this problem by crafting a more structured response, we could also use the opportunity to ensure type validation, standardize date formats, exclusively return pertinent client data, and other improvements. Here is how it could look:

class My::ReservationsController < ApplicationController
  def index
    my_reservations = reservation_service_client
      .get_reservations
      .filter { |res| res[:guest_email] == current_user.email }

    render json: my_reservations.map do |my_reservation|
      {
        id: my_reservation[:id].to_s,
        check_in_date: Date.parse(my_reservation[:check_in_date]),
        check_out_date: Date.parse(my_reservation[:check_out_date]),
        rooms_count: my_reservation[:rooms_number].to_i,
        breakfast_vouchers_count: my_reservation[:breakfast_vouchers].length
      }
    end, status: 200
  end
end

Even if the primary concern is controlling your responses, it’s worth checking out more elaborated solutions for building the JSON responses such as Jbuilder or ActiveModel::Serialization, as they might better suit your project.

In conclusion, the next time we’re tempted to skip the construction of a more controlled response, let’s ask ourselves: “Why does my API seem like it’s flapping vibrant feathers and sporting a curved beak?”.