JavaRush /Blog Java /Random-ES /Estamos agregando la posibilidad de suscribirse a un grup...

Estamos agregando la posibilidad de suscribirse a un grupo de artículos. (Parte 1) - "Proyecto Java de la A a la Z"

Publicado en el grupo Random-ES
¡Hola! Hoy agregaremos una suscripción a un grupo de artículos en JavaRush. Esto corresponde al número JRTB-5 en GitHub. Me explico: JavaRush tiene una sección llamada Artículos , y en ella hay Grupos de Artículos. La idea es recibir notificaciones sobre un nuevo artículo de uno o más grupos a través de un bot de Telegram.“Proyecto Java de la A a la Z”: Agregar la posibilidad de suscribirse a un grupo de artículos.  Parte 1 - 1

Añadir JRTB-5

Digamos que estoy interesado en artículos del grupo Historias de éxito . Por lo tanto, quiero suscribirme a las actualizaciones de este grupo y recibir un enlace a una nueva publicación cada vez. Como parte de esta tarea, debemos aprender a utilizar la API abierta para trabajar con grupos en JavaRush. Justo en ese momento llegó algo así. Aquí hay un enlace a una descripción de la API abierta .
¡Amigos! ¿Quiere saber inmediatamente cuándo se publica un nuevo código para un proyecto o un nuevo artículo? Únete a mi canal tg . Allí recopilo mis artículos, pensamientos y desarrollo de código abierto.

¿Qué es la arrogancia? Vamos a resolverlo ahora

Aún no hemos hablado de la arrogancia. Para aquellos que no lo saben, lo explicaré brevemente: este es un lugar donde pueden mirar abiertamente la API de un servidor e intentar realizarle algunas solicitudes. Por lo general, una arrogancia agrupa posibles solicitudes. En nuestro caso, hay tres grupos: pregunta-foro , grupo , publicación . En cada grupo habrá una o más solicitudes que indiquen todos los datos necesarios para crear esta solicitud (es decir, qué parámetros adicionales se pueden pasar, qué hacer con ellos, qué método http, etc.). Les aconsejo que lean y vean más sobre este tema, porque esta es la parte del desarrollo que casi todos encontrarán. Para resolverlo, averigüemos cuántos grupos hay en JavaRush. Para hacer esto, expanda el grupo de controladores de grupo y seleccione Obtener solicitud /api/1.0/rest/groups/count . Nos devolverá el número de grupos en JavaRush. Miremos: “Proyecto Java de la A a la Z”: Agregar la posibilidad de suscribirse a un grupo de artículos.  Parte 1 - 2La imagen muestra que esta consulta admite varios parámetros (consulta, tipo, filtro). Para probar esta solicitud, necesita encontrar el botón Pruébelo , después de lo cual se pueden configurar estos parámetros: “Proyecto Java de la A a la Z”: Agregar la posibilidad de suscribirse a un grupo de artículos.  Parte 1 - 3También puede configurar el tipo, el filtro y la consulta allí (aquí es realmente interesante: será una búsqueda de texto en un grupo). Pero por ahora, ejecutémoslo sin restricciones y veamos cuántos grupos hay en JavaRush. Para hacer esto, haga clic en Ejecutar. Justo debajo habrá una respuesta (en la sección Respuesta del servidor) a esta solicitud: “Proyecto Java de la A a la Z”: Agregar la posibilidad de suscribirse a un grupo de artículos.  Parte 1 - 4Vemos que hay 30 grupos en total , esta solicitud se completó en 95 ms y hay un conjunto de encabezados en la respuesta. A continuación, intentemos configurar algunos parámetros. Seleccionemos el parámetro de tipo igual al valor de EMPRESA y veamos cómo cambia el resultado: “Proyecto Java de la A a la Z”: Agregar la posibilidad de suscribirse a un grupo de artículos.  Parte 1 - 5Hay 4. ¿Cómo comprobar esto? Es fácil: puede ir al sitio web, buscar la sección de artículos, seleccionar todos los grupos y agregar el filtro apropiado allí ( https://javarush.com/groups/all?type=COMPANY ). Y sí, efectivamente, sólo hay 4. Aunque en realidad son tres :D “Proyecto Java de la A a la Z”: Agregar la posibilidad de suscribirse a un grupo de artículos.  Parte 1 - 6Hasta aquí encaja. Por cierto, si miramos las universidades, todavía no hay ninguna. Solo por diversión, vea qué sucede si configura filter = MY en un navegador en el que inició sesión en Javarush y no inició sesión. Más sobre la arrogancia en este artículo sobre Habré .

Escribir un cliente para la API de Javarush para grupos

Ahora, basándonos en la API abierta, escribiremos un cliente Java que pueda realizar solicitudes, recibir respuestas y saber exactamente qué objetos llegarán. También tomaremos objetos de la arrogancia, de la sección Modelos (en la parte inferior de la página). Creemos un nuevo paquete y llamémoslo javarushclient junto a servicio, repositorio. En el futuro, lo trasladaremos a una biblioteca separada dentro de la organización de la comunidad Javarush y lo usaremos exclusivamente como una dependencia.
Ya escribí sobre la creación de clientes Java en el artículo "Guía para crear un cliente para la API de Skyscanner y publicarlo en jCenter y Maven Central" .
En primer lugar, debe agregar Unitrest, una biblioteca para crear solicitudes http a la API JavaRush:
<dependency>
  <groupId>com.konghq</groupId>
  <artifactId>unirest-java</artifactId>
  <version>${unirest.version}</version>
</dependency>
Y ponga la versión en el bloque de propiedades:
<unirest.version>3.11.01</unirest.version>
Una vez que tengamos una dependencia, podemos comenzar a agregar código. Creemos un cliente para grupos JavaRushGroupClient y una implementación en la clase JavaRushGroupClientImpl. Pero primero es necesario crear DTO (objetos de transferencia de datos), es decir, clases cuyos objetos transportarán todos los datos necesarios para el cliente. Todos los modelos se pueden ver en Swagger, en la parte inferior hay una sección Modelos , en la que puedes contarlos. Así es como se ve GroupDiscussionInfo en swagger: en el paquete javarushclient, crearemos un “Proyecto Java de la A a la Z”: Agregar la posibilidad de suscribirse a un grupo de artículos.  Parte 1 - 7paquete dto , al que agregaremos, según los datos de swagger, las siguientes clases:
  • Estado de información de mi grupo :

    package com.github.javarushcommunity.jrtb.javarushclient.dto;
    
    /**
    * Member group status.
    */
    public enum MeGroupInfoStatus {
       UNKNOWN, CANDIDATE, INVITEE, MEMBER, EDITOR, MODERATOR, ADMINISTRATOR, BANNED
    }

  • Información de mi grupo :

    package com.github.javarushcommunity.jrtb.javarushclient.dto;
    
    import lombok.Data;
    
    /**
    * Group information related to authorized user. If there is no user - will be null.
    */
    @Data
    public class MeGroupInfo {
       private MeGroupInfoStatus status;
       private Integer userGroupId;
    }

  • Tipo de información de grupo :

    package com.github.javarushcommunity.jrtb.javarushclient.dto;
    
    /**
    * Group Info type;
    */
    public enum GroupInfoType {
       UNKNOWN, CITY, COMPANY, COLLEGE, TECH, SPECIAL, COUNTRY
    }

  • Información de discusión del usuario :

    package com.github.javarushcommunity.jrtb.javarushclient.dto;
    
    import lombok.Data;
    
    /**
    * DTO for User discussion info.
    */
    @Data
    public class UserDiscussionInfo {
    
       private Boolean isBookmarked;
       private Integer lastTime;
       private Integer newCommentsCount;
    }

  • Estado de visibilidad del grupo :

    package com.github.javarushcommunity.jrtb.javarushclient.dto;
    
    /**
    * Group Visibility status.
    */
    public enum GroupVisibilityStatus {
       UNKNOWN, RESTRICTED, PUBLIC, PROTECTED, PRIVATE, DISABLED, DELETED
    }

  • Entonces - Información del grupo :

    package com.github.javarushcommunity.jrtb.javarushclient.dto;
    
    import lombok.Data;
    import lombok.ToString;
    
    /**
    * Group Info DTO class.
    */
    @Data
    @ToString
    public class GroupInfo {
    
       private Integer id;
       private String avatarUrl;
       private String createTime;
       private String description;
       private String key;
       private Integer levelToEditor;
       private MeGroupInfo meGroupInfo;
       private String pictureUrl;
       private String title;
       private GroupInfoType type;
       private Integer userCount;
       private GroupVisibilityStatus visibilityStatus;
    }

Dado que GroupInfo y GroupDiscussionInfo son casi completamente iguales, vinculémoslos en herencia: GroupDiscusionInfo :
package com.github.javarushcommunity.jrtb.javarushclient.dto;

import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;

/**
* Group discussion info class.
*/
@EqualsAndHashCode(callSuper = true)
@Data
@ToString(callSuper = true)
public class GroupDiscussionInfo extends GroupInfo {

   private UserDiscussionInfo userDiscussionInfo;
   private Integer commentsCount;
}
También necesitaremos un filtro para la solicitud GroupFilter :
package com.github.javarushcommunity.jrtb.javarushclient.dto;

/**
* Filters for group requests.
*/
public enum GroupFilter {

   UNKNOWN, MY, ALL
}
En una solicitud para obtener ID, devuelve GroupDiscussionInfo y en una solicitud para una colección de grupos, puede obtener tanto GroupInfo como GroupDiscussionInfo. Dado que las solicitudes pueden tener tipo, consulta, filtro, compensación y límite, creemos una clase GroupRequestArgs separada y convirtámosla en una clase de creación (lea cuál es el patrón de creación):
package com.github.javarushcommunity.jrtb.javarushclient.dto;

import lombok.*;

import java.util.HashMap;
import java.util.Map;

import static java.util.Objects.nonNull;

/**
* Request arguments for group requests.
*/
@Builder
@Getter
public class GroupRequestArgs {

   private final String query;
   private final GroupInfoType type;
   private final GroupFilter filter;

   /**
    * specified where to start getting groups
    */
   private final Integer offset;
   /**
    * Limited number of groups.
    */
   private final Integer limit;

   public Map populateQueries() {
       Map queries = new HashMap<>();
       if(nonNull(query)) {
           queries.put("query", query);
       }
       if(nonNull(type)) {
           queries.put("type", type);
       }
       if(nonNull(filter)) {
           queries.put("filter", filter);
       }
       if(nonNull(offset)) {
           queries.put("offset", offset);
       }
       if(nonNull(limit)) {
           queries.put("limit", limit);
       }
       return queries;
   }
}
Para buscar el número de grupos, es ligeramente diferente. Solo tiene consulta, tipo y filtro. Y parece que no quieres duplicar el código. Al mismo tiempo, si empiezas a combinarlos, resulta feo trabajar con constructores. Entonces decidí separarlos y repetir el código. Así es como se ve GroupCountRequestArgs :
package com.github.javarushcommunity.jrtb.javarushclient.dto;

import lombok.Builder;
import lombok.Getter;

import java.util.HashMap;
import java.util.Map;

import static java.util.Objects.nonNull;

/**
* Request arguments for group count requests.
*/
@Builder
@Getter
public class GroupsCountRequestArgs {
   private final String query;
   private final GroupInfoType type;
   private final GroupFilter filter;

   public Map populateQueries() {
       Map queries = new HashMap<>();
       if (nonNull(query)) {
           queries.put("query", query);
       }
       if (nonNull(type)) {
           queries.put("type", type);
       }
       if (nonNull(filter)) {
           queries.put("filter", filter);
       }
       return queries;
   }
}
Sí, no mencioné que las dos últimas clases tienen un método populateQueries, que preparará el mapa para crear una consulta (lo verás más adelante). Basándonos en las clases descritas anteriormente, creemos una interfaz para JavaRushGroupClient :
package com.github.javarushcommunity.jrtb.javarushclient;

import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupDiscussionInfo;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupInfo;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupRequestArgs;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupsCountRequestArgs;

import java.util.List;

/**
 * Client for Javarush Open API corresponds to Groups.
 */
public interface JavaRushGroupClient {

    /**
     * Get all the {@link GroupInfo} filtered by provided {@link GroupRequestArgs}.
     *
     * @param requestArgs provided {@link GroupRequestArgs}.
     * @return the collection of the {@link GroupInfo} objects.
     */
    List<GroupInfo> getGroupList(GroupRequestArgs requestArgs);

    /**
     * Get all the {@link GroupDiscussionInfo} filtered by provided {@link GroupRequestArgs}.
     *
     * @param requestArgs provided {@link GroupRequestArgs}
     * @return the collection of the {@link GroupDiscussionInfo} objects.
     */
    List<GroupDiscussionInfo> getGroupDiscussionList(GroupRequestArgs requestArgs);

    /**
     * Get count of groups filtered by provided {@link GroupRequestArgs}.
     *
     * @param countRequestArgs provided {@link GroupsCountRequestArgs}.
     * @return the count of the groups.
     */
    Integer getGroupCount(GroupsCountRequestArgs countRequestArgs);

    /**
     * Get {@link GroupDiscussionInfo} by provided ID.
     *
     * @param id provided ID.
     * @return {@link GroupDiscussionInfo} object.
     */
    GroupDiscussionInfo getGroupById(Integer id);
}
Dos solicitudes diferentes para el caso en el que queremos agregar información de GroupInfo o GroupDiscussionInfo. De lo contrario, estas solicitudes son idénticas y la única diferencia será que en una la bandera includeDiscussion será verdadera y en la otra será falsa. Por lo tanto, había 4 métodos, no tres. Ahora comencemos a implementar:
package com.github.javarushcommunity.jrtb.javarushclient;

import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupDiscussionInfo;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupInfo;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupRequestArgs;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupsCountRequestArgs;
import kong.unirest.GenericType;
import kong.unirest.Unirest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.List;

/**
* Implementation of the {@link JavaRushGroupClient} interface.
*/
@Component
public class JavaRushGroupClientImpl implements JavaRushGroupClient {

   private final String javarushApiGroupPath;

   public JavaRushGroupClientImpl(@Value("${javarush.api.path}") String javarushApi) {
       this.javarushApiGroupPath = javarushApi + "/groups";
   }

   @Override
   public List<GroupInfo> getGroupList(GroupRequestArgs requestArgs) {
       return Unirest.get(javarushApiGroupPath)
               .queryString(requestArgs.populateQueries())
               .asObject(new GenericType<list<GroupInfo>>() {
               })
               .getBody();
   }

   @Override
   public List<GroupDiscussionInfo> getGroupDiscussionList(GroupRequestArgs requestArgs) {
       return Unirest.get(javarushApiGroupPath)
               .queryString(requestArgs.populateQueries())
               .asObject(new GenericType<list<GroupDiscussionInfo>>() {
               })
               .getBody();
   }

   @Override
   public Integer getGroupCount(GroupsCountRequestArgs countRequestArgs) {
       return Integer.valueOf(
               Unirest.get(String.format("%s/count", javarushApiGroupPath))
                       .queryString(countRequestArgs.populateQueries())
                       .asString()
                       .getBody()
       );
   }

   @Override
   public GroupDiscussionInfo getGroupById(Integer id) {
       return Unirest.get(String.format("%s/group%s", javarushApiGroupPath, id.toString()))
               .asObject(GroupDiscussionInfo.class)
               .getBody();
   }


}
Agrego la ruta a la API en el constructor usando la ya familiar anotación Valor. Implica que el valor dentro de la anotación corresponde a un campo en el archivo de propiedades. Por lo tanto, agreguemos una nueva línea a application.properties:
javarush.api.path=https://javarush.com/api/1.0/rest
Este valor ahora estará en un solo lugar para todos los clientes API y, si la ruta de la API cambia, la actualizaremos rápidamente. Anteriormente, martilleé clavos con un microscopio, recibí una respuesta de una solicitud http a través de Unirest, la traduje a una cadena y luego analicé esta cadena a través de Jackson... Era aterrador, tedioso y requería muchas cosas adicionales. En esta biblioteca puedes ver cómo se ve. Tan pronto como lo tenga en mis manos, lo refactorizaré todo.
Cualquiera que quiera intentar actualizar esta biblioteca (agregue objetos receptores solo usando las herramientas de la biblioteca unirest) escriba en un mensaje personal o como un nuevo número en la propia biblioteca. Esta será una verdadera experiencia laboral para ti, pero no me importa. Realizaré una revisión completa del código y ayudaré si es necesario.
Ahora la pregunta es: ¿nuestro código funciona como esperamos? La respuesta es fácil: sólo necesitas escribir pruebas para ellos. Como he dicho más de una vez, los desarrolladores deben poder escribir pruebas. Por lo tanto, utilizando nuestra interfaz de usuario Swagger, enviaremos solicitudes, miraremos las respuestas y las sustituiremos en las pruebas como resultado esperado. Es posible que haya notado inmediatamente que la cantidad de grupos no es estática y puede cambiar. Y tienes razón. La única pregunta es ¿con qué frecuencia cambia este número? Muy raramente, por lo que a lo largo de varios meses podemos decir que este valor será estático. Y si algo cambia, actualizaremos las pruebas. Conoce - JavaRushGroupClientTest:
package com.github.javarushcommunity.jrtb.javarushclient;

import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupDiscussionInfo;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupInfo;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupRequestArgs;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupsCountRequestArgs;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.List;

import static com.github.javarushcommunity.jrtb.javarushclient.dto.GroupInfoType.TECH;

@DisplayName("Integration-level testing for JavaRushGroupClientImplTest")
class JavaRushGroupClientTest {

   private final JavaRushGroupClient groupClient = new JavaRushGroupClientImpl("https://javarush.com/api/1.0/rest");

   @Test
   public void shouldProperlyGetGroupsWithEmptyArgs() {
       //given
       GroupRequestArgs args = GroupRequestArgs.builder().build();

       //when
       List<GroupInfo> groupList = groupClient.getGroupList(args);

       //then
       Assertions.assertNotNull(groupList);
       Assertions.assertFalse(groupList.isEmpty());
   }

   @Test
   public void shouldProperlyGetWithOffSetAndLimit() {
       //given
       GroupRequestArgs args = GroupRequestArgs.builder()
               .offset(1)
               .limit(3)
               .build();

       //when
       List<GroupInfo> groupList = groupClient.getGroupList(args);

       //then
       Assertions.assertNotNull(groupList);
       Assertions.assertEquals(3, groupList.size());
   }

   @Test
   public void shouldProperlyGetGroupsDiscWithEmptyArgs() {
       //given
       GroupRequestArgs args = GroupRequestArgs.builder().build();

       //when
       List<GroupDiscussionInfo> groupList = groupClient.getGroupDiscussionList(args);

       //then
       Assertions.assertNotNull(groupList);
       Assertions.assertFalse(groupList.isEmpty());
   }

   @Test
   public void shouldProperlyGetGroupDiscWithOffSetAndLimit() {
       //given
       GroupRequestArgs args = GroupRequestArgs.builder()
               .offset(1)
               .limit(3)
               .build();

       //when
       List<GroupDiscussionInfo> groupList = groupClient.getGroupDiscussionList(args);

       //then
       Assertions.assertNotNull(groupList);
       Assertions.assertEquals(3, groupList.size());
   }

   @Test
   public void shouldProperlyGetGroupCount() {
       //given
       GroupsCountRequestArgs args = GroupsCountRequestArgs.builder().build();

       //when
       Integer groupCount = groupClient.getGroupCount(args);

       //then
       Assertions.assertEquals(30, groupCount);
   }

   @Test
   public void shouldProperlyGetGroupTECHCount() {
       //given
       GroupsCountRequestArgs args = GroupsCountRequestArgs.builder()
               .type(TECH)
               .build();

       //when
       Integer groupCount = groupClient.getGroupCount(args);

       //then
       Assertions.assertEquals(7, groupCount);
   }

   @Test
   public void shouldProperlyGetGroupById() {
       //given
       Integer androidGroupId = 16;

       //when
       GroupDiscussionInfo groupById = groupClient.getGroupById(androidGroupId);

       //then
       Assertions.assertNotNull(groupById);
       Assertions.assertEquals(16, groupById.getId());
       Assertions.assertEquals(TECH, groupById.getType());
       Assertions.assertEquals("android", groupById.getKey());
   }
}
Las pruebas están escritas en el mismo estilo que antes. Hay varias pruebas para cada solicitud. No tiene sentido probarlo todo, ya que creo que esta API ya ha sido probada de la mejor manera.

Conclusión

Como parte de este artículo, agregamos un cliente Java para grupos a la API JavaRush. Como dicen, vive y aprende. Mientras escribía este cliente, aproveché su documentación y utilicé convenientemente el trabajo con los objetos que proporciona. Llamo su atención sobre la tarea que le propuse. Si a alguien le interesa que me escriba un privado, estoy más que segura que será una experiencia muy interesante. Esta fue la primera parte. En el segundo, implementaremos directamente el comando agregar y (si lo encajamos en un artículo) agregaremos obteniendo una lista de grupos a los que está suscrito el usuario. A continuación, cualquiera que tenga las ganas y el talento para escribir textos para el bot, que me escriba por MP. No soy un experto en este tema y cualquier ayuda sería de gran ayuda. Formalicemos todo esto como desarrollo de código abierto, ¡será interesante! Bueno, como siempre, dale me gusta, suscríbete, activa la campana , dale una estrella a nuestro proyecto , escribe comentarios y califica el artículo.
Enlaces útiles

Al principio de este artículo encontrará una lista de todos los materiales de la serie.

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION