Diseño de API de descanso: ¿trabaja con ID o cadenas literales?

8

Al diseñar un servicio web RESTful, ¿se debe diseñar la API para que funcione la ID de las cadenas de valores que se transmiten de un lado a otro entre el servidor?

Aquí hay un ejemplo: Digamos que tengo un recurso de empleado, que tiene un estado y atributos de género. En la base de datos Estado y género y tablas separadas y, por lo tanto, objetos de dominio separados, cada uno con su propio identificador.

Digamos la solicitud del cliente / empleado / 1. El servidor podría devolver algo como esto ...

Caso 1:

{
    "id": 1,
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "gender": {
        "id": 1,
        "gender": "FEMALE"
    },
    "status": {
        "id": 3,
        "status": "FULL_TIME"
    }
}

Caso 2:

{
    "id": 1,
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "gender": "FEMALE",
    "status": "FULL_TIME"
}

Caso 3:

{
    "id": 1,
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "genderId": 1,
    "statusId": 3
}

El caso 3 parece tener menos sentido ya que el cliente no tiene idea de qué género es el Id. 1 a menos que se dé la vuelta y realice otra llamada al servidor para obtener esos datos.

Sin embargo, ahora digamos que el cliente está actualizando al usuario a través de:

PUT /employee/1

¿Debería la solicitud Payload usar los identificadores o una cadena? De cualquier manera, el back-end tiene que buscarlos para asegurarse de que sean válidos, pero es mejor trabajar con ID sobre Strings.

jkratz55
fuente

Respuestas:

3

Digamos que tengo un recurso de empleado, que tiene un estado y atributos de género. En la base de datos Estado y género y tablas separadas y, por lo tanto, objetos de dominio separados, cada uno con su propio identificador.

Sus representaciones de API no deben estar estrechamente relacionadas con los detalles de su implementación. Llegaría al extremo de decir que derivar sus representaciones API de los detalles de su implementación es exactamente al revés.

Piensa Adapter Patternen el libro Gang of Four . Los mensajes de la web son los de un almacén de documentos. Su objetivo al crear una API es producir los documentos que desean sus consumidores, al tiempo que los aísla de los detalles esenciales de la producción de esos documentos.

La motivación para hacerlo es que puede cambiar los detalles de implementación en cualquier momento que desee, con la certeza de que, siempre y cuando no cambie las representaciones que devuelve, sus clientes no se romperán.

Además, tenga en cuenta que un único recurso lógico puede tener muchas representaciones, solo algunas de las cuales admiten modificaciones.

digamos que el cliente está actualizando al usuario

Como consumidor, ¿con qué representación quieres trabajar? Supongo que lo más cercano es

{
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "gender": "FEMALE",
    "status": "FULL_TIME"
}

Si pongo esa representación en una ubicación que especifique, realmente debería poder resolver el resto.

Si estaba creando representaciones para que las máquinas las usaran, entonces probablemente querría menos ambigüedad en su ortografía

{
    "https://schema.org/givenName": "Jane",
    "https://schema.org/familyName": "Doe",
    "active": true,
    "https://schema.org/gender": "https://schema.org/Female",
    "https://schema.org/employmentType": "FULL_TIME"
}

Mismo recurso lógico, dos representaciones diferentes. Caballos de carreras.

VoiceOfUnreason
fuente
1

Tanto el caso 1 como el caso 2 se ven bien. La elección se puede predecir por la forma en que organice su modelo de dominio.

Reflejaste las tablas de Empleado, Género y Estado en el Dominio (usando ORM, supongo). Cada una de estas clases en este modelo particular es una entidad que tiene su propio identificador. Exponer aún más el modelo completo a través de REST API parece lógico y se ajusta al Caso 1.

Alternativamente, puede apegarse a los principios DDD que prestan mucha atención a las diferencias entre entidades y objetos de valor . Desde este punto de vista, Employee es una entidad (con id) y Gender and Status podrían ser buenos candidatos para convertirse en objetos de valor (incrustados en la entidad Employee; sin identificadores). Esto se ajusta al caso 2.

Totalmente de acuerdo con usted en que el Caso 3 es un no ir.

Serhii Shushliapin
fuente
1
A menos que haya una razón convincente, no acoplaría estrechamente una API de servicio web con un diseño de base de datos. Las API y las bases de datos tienen diferentes clientes con diferentes necesidades.
Eric Stein
Completamente de acuerdo. Esa es solo mi observación del diseño API de los autores (ID de exposición de género y estado). Adhiriéndome a los principios DDD, los diseñaría como objetos de valor en mi Modelo de dominio y, como resultado, no expondría sus identificadores a través de la API REST (ese es el caso 2).
Serhii Shushliapin
1

El caso 2 es la única opción real. Ya ha señalado los problemas con el Caso 3. El Caso 1 proporciona información que al cliente de la API no le interesa (los ID internos para los estados, por ejemplo), y requiere que el cliente sepa sobre aquellos para construir una solicitud PUT . Sí, la solicitud PUT es un poco más breve si puede usar las ID en lugar de las cadenas completas, pero especificar "FULL_TIME" o "PART_TIME" es lo que el cliente sabe, no es que tengan algunas ID arbitrarias en su base de datos. .

Por supuesto, puede documentar los ID en la documentación de su API, pero es igual de fácil documentar los valores válidos que las cadenas pueden ser, y probablemente más claros.

David Conrad
fuente
2
Debe tener en cuenta que esto significa que no se puede cambiar el nombre de un género o estado. Si los clientes solo trabajan con el nombre, entonces el nombre es efectivamente el identificador único. Si los clientes necesitan comenzar a usar una identificación como identificador único, entonces ese es un cambio radical.
Eric Stein
0

Los datos enumerados como los que tiene aquí son altamente almacenables en caché. Use enlaces en lugar de objetos. Utilice los encabezados de almacenamiento en caché para permitir a los clientes almacenar en caché los géneros y estados localmente, por ejemplo, durante 24 horas. Entonces solo la primera llamada del día sale de la máquina del cliente. Probablemente también pueda configurar el almacenamiento en caché para permitir que los servidores intermedios retengan la información, por lo que algunas solicitudes de clientes ni siquiera llegan a su servidor.

GET /employees/1
{
    "id": 1,
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "gender": "/genders/1",
    "status": "/statuses/3"
}

// clients populate their dropdown with
GET /genders
[
    {"id":1, "gender":"FEMALE"},
    {"id":2, "gender":"MALE"},
    ...
]

// clients look up an employee's gender with
GET /genders/1
{
    "id": 1,
    "gender": FEMALE
}

Una desventaja es que /genders/1no es legible para los humanos. En su lugar /genders/female, puede usar , pero luego no puede cambiar el nombre de un género sin romper clientes. Esa es la clave sintética frente a la compensación de la clave natural: flexibilidad frente a legibilidad humana.

Es posible que también desee considerar poner todas sus listas de valores en un punto final común, como

/lists/genders/1
/lists/statuses/3

Esto aclarará a los clientes que todos son básicamente pares clave-valor que pertenecen a diferentes agrupaciones.

Eric Stein
fuente
0

Iría por algo entre 1 y 2, por las razones que David mencionó:

No desea exponer la identificación de las cosas a menos que sea necesario.

Sin embargo, exponer la ID podría ser necesario en algún momento. Si eso sucede, la compatibilidad con versiones anteriores es una preocupación. Entonces, haría esto:

{
    "id": 1,
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "gender": {
        "name": "FEMALE"
    },
    "status": {
        "name": "FULL_TIME"
    }
}

Que tiene las mismas propiedades que tiene la opción 2; pero tiene el beneficio de que agregar la ID más adelante no introduce una interrupción de BC:

{
    "id": 1,
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "gender": {
        "id": 1,
        "name": "FEMALE"
    },
    "status": {
        "id": 3,
        "name": "FULL_TIME"
    }
}

Como Eric señala en los comentarios, esto todavía usa el nombre de la entidad como un identificador único. Si la identificación se introduce más tarde, el nombre debe seguir siendo el mismo porque los clientes más antiguos podrían (o más bien lo habrían hecho) codificado.

marstato
fuente
Este enfoque introduce una nueva opción. Tener 2 recursos diferentes: el primero para consultar y el segundo foro para crear o actualizar. Si bien puede parecer demasiado código, facilita el mantenimiento.
Laiv
@Laiv: no sugerí eso; por ahora nameusaría para consultar y actualizar.
marstato
1
Debe tener en cuenta que esto significa que no se puede cambiar el nombre de un género o estado. Si los clientes solo trabajan con el nombre, entonces el nombre es efectivamente el identificador único. Si los clientes necesitan comenzar a usar un idcomo identificador único, entonces ese es un cambio radical.
Eric Stein