Introducción a las URL (@Path)

Las URL o Paths de aquí en adelante son una de las partes fundamentes de todos los servicios REST, pues en esta arquitectura, solo puede existir un único recurso por URL, por lo que definir una estructura adecuada y simple es clave para el éxito de nuestra API.

 

NOTA: Este artículo es parte de un tutorial completo para crear API REST con JAX-RS, si quieres ver el índice completo entra aquí.

 

El path es conocido como la dirección URL que aparece en la que se encuentra un determinado recurso, por ejemplo, la página de google se encuentra en https://google.com mediante el Método GET, por lo que ninguna otra página podría existir en esa misma URL, porque estaría violando una de las principales reglas de REST. Así como no puede haber otra página en una misma URL, tampoco podría existir, por ejemplo, tener los datos de dos usuarios en la misma URL. Por ejemplo: https://miapi.com/users/1,  está URL nos dice que estamos buscando el usuario con ID = 1, por lo que sin importar que pase, los datos del usuario 1 siempre deberían estar en esa URL.

 

En JAX-RS, el Path se define como una expresión que se utilizará para identificar hacia qué servicios se debe de direccionar una llamada entrante al API. Para esto, tenemos la anotación @Path  del paquete javax.ws.rs  que nos permite definir la URL en que un determinado método responde. Veamos el siguiente ejemplo:

@GET
@Path("users")
public Response findAllUsers() {
    return Response.ok(userDao.getAllUsers()).build();
}

 

En este ejemplo, vemos el método findAllUsers  utilizado para obtener todos los usuarios del sistema, lo interesante en este método el valor de la anotación @Path , la cual indica que este servicio estará disponible en la URL /users. Por lo tanto, podríamos ejecutar nuestro servicio en una URL como la siguiente http:<host>:<port>/<app_context>/users.

En este punto el host y el puerto es más que obvio que es, por lo que no entraremos en detalle sobre estos. El <app_context> es la URL base que nos asigna el servidor de aplicación, el cual por lo general corresponde con el nombre del proyecto, y finalmente, el /users está dado por la anotación @Path .

 

 

Path Expressions

Con la finalidad de crear servicios para procesar determinadas solicitudes, es necesario crear expresiones que ayuden a JAX-RS a distinguir que método debe de procesar una solicitud determinada, es por ello que @Path  permite definir expresiones complejas, así convertir ciertas secciones de la URL en parámetros que podrán ser utilizados por los métodos para saber cómo procesar la solicitud.

Veamos un ejemplo, imaginemos que tenemos dos usuarios en la base de datos y requerimos poder consultarlos por medio del ID, una solución sería crear dos métodos con diferente URL, por ejemplo:

  • @Path(“users/1”)
  • @Path(“users/2”)

Los paths anteriores nos permitirían diferencia si queremos consultar el usuario 1 o el 2, sin embargo, esto no es una solución práctica, ya que, si tenemos 100 usuarios, deberíamos de tener 100 métodos con Path diferente, por ello, en lugar de eso, podemos utilizar las expresiones para indicar que ciertas partes de la URL son en realidad un parámetro, veamos el siguiente ejemplo:

@GET
@Path("users/{userId}")
public Response deleteUser( @PathParam("userId") String userId) {
    long parseUserId = Long.parseLong(userId);
    User user = userDAO.findUserById(userId);
    return Response.ok(user.build();
}

 

Este ejemplo es mucho más sensato, pues ya no hay necesidad de tener un método para cada usuario y en su lugar, hemos definido en el Path la variable {userId} , la cual le indica a JAX-RS, que todo lo que venga después de /users/ deberá ser recuperado y luego inyectado en la variable userId  del método Java. La anotación @PathParam  se utiliza para indicarle a Java en que parámetro debe de inyectar el valor.

 

A pesar de que este nuevo @Path  funciona, tenemos un detalle, pues le estaríamos diciendo que aceptaría cualquier valor en el parámetro {userId} , pero el ID de los usuarios es numérico, entonces estaríamos a expensas de que nos manden un valor no numérico, y que la instrucción donde parseamos el valor a long  falla, por eso, es necesario protegernos un poco más y complementar la expresión para indicar que el parámetro solo recibe valores numéricos:

@GET
@Path("users/{userId : \d+}")
public Response findUserById(@PathParam("userId") long userId) {
    System.out.println("userId ==> " + userId);
    List<User> found = this.users.stream().filter(x -> userId == x.getId().longValue()).collect(Collectors.toList());
              
    //Throws error in case of the user not found
    if(found.isEmpty()) return Response.status(Status.BAD_REQUEST).entity("User not found").build();
    User userFound = found.get(0);
    return Response.ok(userFound).build();
}

 

En este nuevo ejemplo, hemos actualizado el parámetro {userId}  del path para que se vea de la siguiente manera {userId : \\d+} , de esta forma, le indicamos que el parámetro debe de ser uno o más dígitos.

Mediante JAX-RS es posible crear complejas reglas basadas en expresiones regulares para que nuestros parámetros sean los más exactos posibles, sin embargo, no vamos a entrar a detalle sobre este tema, pues podríamos alargarnos muchísimo y mi objetivo es simplemente que sepas que tienes esta posibilidad.

 

A si como podemos tener una variable, podríamos tener todas las que sean necesarias, por ejemplo, que pasaría si requiere recuperar todos los códigos postales de un país, una ciudad y todas las colonias que hay dentro de un código postal determinado, aquí estaríamos hablando de 3 servicios que podrían estar en conflicto, veamos por qué:

 

  • CP país: /mexico
  • CP ciudad: /mexico/cancun
  • Colonias por CP: /mexico/cancun/80000

El primero me regresaría todos los códigos postales que están en México, el segundo, me regresaría todos los CP de la ciudad de Cancún, la cual pertenece a México, y finalmente, la tercera me debería indicar todas las colonias que están el CP 80000. El problema con estos Path es que todos comienzan con el País, lo que puede provocar que las URL colapsen, pero por suerte, en JAX-RS es muy simple saltarse este problema, veamos como quedarían los métodos para estos 3 servicios:

@GET
@Path("{country}")
public Response findColonyByCP(
    @PathParam("country") String country) {
}

@GET
@Path("{country}/{city}")
public Response findColonyByCP(
    @PathParam("country") String country,
    @PathParam("city") String city) {
}

@GET
@Path("{country}/{city}/{cp}")
public Response findColonyByCP(
    @PathParam("country") String country,
    @PathParam("city") String city,
    @PathParam("cp") String cp) {
}

 

Como vemos en este ejemplo, podemos separar cada sección de la URL por medio de variables, y así reconocer cada sección de la URL por separado.

También es posible hacer cosas como la siguiente @Path(“users/{company}-{id}”) , en esta nueva modalidad, tenemos dos parámetros en la misma subsección de la URL, en la cual podemos buscar un usuario de una determinada empresa.

 

 

Contexto de los Paths

Hasta este punto hemos visto cómo utilizar la anotación @Path  sobre los métodos, sin embargo, también es posible implementarla a nivel de clase. Cuando un path es definido a nivel de clase, afecta a todos los métodos de la clase, anteponiendo el este path al que definamos en los métodos, por ejemplo, si tenemos una clase que tiene todos los servicios relacionados a usuarios, entonces podríamos definir el @Path(“users”)  a nivel de clase, para que, de este modo, todos los servicios de esta clase inicien con el path /users, veamos un ejemplo:

@Path("/users")
@Consumes(value= MediaType.APPLICATION_JSON)
@Produces(value = MediaType.APPLICATION_JSON)
public class UserService {

    @GET
    public Response findAllUsers() {}
               
    @POST
    public Response createUser(User userRequest) {}

    @PUT
    public Response updateUser(User userRequest) {}
               
    @DELETE
    @Path("{userId}")
    public Response deleteUser( @PathParam("userId") long userId) {}

    @GET
    @Path("{userId : \d+}")
    public Response findUserById(@PathParam("userId") long userId) {}
}

 

Lo primero que podemos observar es que varios métodos ya no requieren la anotación @Path , sino solo el método (verb) por el cual responden, por ejemplo, el método findAllUsers  lo podemos ejecutar directamente en la URL GET:/users, por que asume la URL del @Path  de la clase.Por otra parte, el método deleteUsers  estará disponible en la URL /users/{userId}, esto debido a que se concatena el @Path  de la clase con el del método.

 

 

Path de la aplicación

Adicional a la anotación @Path  existe otra llamada @ApplicationPath , la cual está un poco fuera de tema, pero vale la pena recordarla. Esta anotación se utiliza definir el Path base que TODA la aplicación tendrá, esta se implementa una clase independiente, por lo general en el paquete base de la app:

package api;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("/")
public class RestApplication extends Application {

}

 

Esta anotación la analizamos en la sección “Creando un API REST en Java” al inicio de este tutorial.

 

Conclusiones

Como hemos podido constatar, JAX-RS es sumamente flexible a la hora de definir los paths, sin embargo, no hemos abordado aún los QueryParams, PathParams y HeaderParams, los cuales estaremos analizando más a detalle en las siguientes secciones de este tutorial.

7 thoughts to “Introducción a las URL (@Path)”

  1. Hola, buenas tardes.
    Me podrías ayudar? Mira este este es mi path por el cual voy a acceder a mi servicio, este mismo recibirá un JSON

    @Path(“getnames/{json}”)
    public String getJson(@PathParam(“json”) String jsonString) throws SolrServerException {
    }

    Lo que quiero saber es como escribir en la barra de direcciones una propiedad que tenga mas de un valor, por ejemplo {“preferencias”:[“Leer”,”Cantar”,”Bailar”]}
    Lo que pasa es que al tratar de guardar ese “JsonArray” no me lo guarda, pero creo que es cosa de como estoy ingresando los datos porque algunos otras propiedades de un solo valor las estoy obteniendo sin ningún problema, por ejemplo {“nombre”:”Sergio”}.
    Me puedes decir de acuerdo a tu experiencia como mandar un array por medio de la url?

    Te lo agradecería infinitamente, saludos!

    1. Hola Sergio,
      Eso que esta intentando hacer esta absolutamente mal… los query params no son para enviar json, para esto tiene el body. si lo que quieres es enviar un array de preferencias, simplemente hago esto:
      /servicio?preferencias=Leer&preferencias=Cantar&preferencias=Bailar
      De esta forma, observe que la propiedad preferencias aparece varias veces, luego, esto lo cacha en un varibla de tipo array String[] preferencias

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *