Best practice with swagger, objects with same name

Viewed 71

Let's say I have 2 endpoints:

  • "ListUsers", returns a User[]
  • "UserDetails", returns a User

The problem is that the returned "users" are not the same object.

The detailed User might be:

{
  int id;
  string name;
  string phone;
  string email;
}

while the list User might be a light version:

{
  int id;
  string name;
}

Of course this is not a problem in C# but the problem is in swagger because (of course) openapi doesn't support the same name of two models.

I need to keep the name User for them both (not in my control). How is it possible to solve this, the ways I've been thinking of is:

  • Include the namespaces in the model-names (kind of ugly)
  • Do two different "versions" i swagger and include one endpoint in each and call the versions like "UserDetails_1.0" and "ListUsers_1.0" (if this is even possible)

Any other good ideas here?

2 Answers

The problem is that the returned "users" are not the same object.

Make them be the same.

Having multiple representations of the same entity in an API is not good RESTful design. Particularly when both representations come from the exact same route structure, for example:

  • GET /api/users/1
  • GET /api/users

Instead of returning different fields on each of these endpoints, change your API so that wherever a user is needed, the exact same User model is consistently employed. To achieve that, you use your "detailed" representation as the only representation.

If some consumer of your API doesn't need all the details of each user when requesting the user list, this is a consumer concern, and not an API one.

To optimize for use-cases such as that, you can introduce a mechanism in your API to allow user-defined projections be specified in the query, so that only interesting fields are returned to the consumer. This is usually modelled as a querystring parameter with a list of strings, each being the name of a property that the consumer wants to be present in the payload.

If the concern is related to efficiency and joining other tables to the user, you can opt to return only "direct" user properties in the payload by default and have the "navigation

As far as I'm concerned, the easiest and most robust way to support these kinds of operations is by exposing your endpoint as an OData-enabled endpoint, which naturally provides you with expansion and projection capabilities which are useful to limit the complexity of the returned payload without compromising API design.

For example, this is how your list example would look like using OData projection to restrict which properties are returned for each user:

  • GET /api/users?$select=Id, Name

Some APIs in other languages provide similar capabilities using completely custom implementations, but since you tagged the question as C#, I'd strongly recommend looking into the native OData implementation to save you a gigantic amount of manual effort and also provide you with a strong URL specification that is well-known and open source: it is usually a better idea to rely on a well-known spec than it is to come up with your own custom solution.

It ended up quite nice using versions in OpenAPI, and using the "status" part of the version do split the different endpoints. This is of course not how it was designed.. but it worked.

Related