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?”.