JavaRush /Java Blog /Random-JA /記事のグループを購読する機能を追加しています。(パート 1) - 「Java プロジェクトの A から Z まで」...
Roman Beekeeper
レベル 35

記事のグループを購読する機能を追加しています。(パート 1) - 「Java プロジェクトの A から Z まで」

Random-JA グループに公開済み
こんにちは!今日は、JavaRush の記事グループにサブスクリプションを追加します。これは、GitHub のJRTB-5 の問題に対応します。説明しましょう: JavaRush にはArticles というセクションがあり、その中には記事のグループがあります。このアイデアは、電報ボットを通じて 1 つ以上のグループから新しい記事に関する通知を受け取ることです。「Java プロジェクト A to Z」: 記事のグループを購読する機能を追加します。 パート 1 - 1

JRTB-5を追加

Success Storiesグループの記事に興味があるとします。したがって、このグループからの更新を購読し、新しい出版物へのリンクを毎回受け取りたいと考えています。このタスクの一環として、JavaRush でグループを操作するためのオープン API の使用方法を学ぶ必要があります。ちょうど今、こんなものが届きました。ここに、オープン API の説明へのリンクがあります。
友達!プロジェクトの新しいコードや新しい記事がリリースされるとすぐに知りたいですか? 私のTGチャンネルに参加してください。そこでは、私の記事、考え、オープンソース開発についてまとめています。

スワッガーとは何ですか?今すぐ考えてみましょう

威圧的なことについてはまだ話していません。知らない人のために簡単に説明すると、これはサーバーの API をオープンに調べて、それにいくつかのリクエストを実行できる場所です。通常、Swagger は考えられるリクエストをグループ化します。この例では、 forum-questiongrouppostの 3 つのグループがあります。各グループには、このリクエストを構築するために必要なすべてのデータ (つまり、どのような追加パラメータを渡すことができるか、それらをどう処理するか、どのような http メソッドなど) を示す 1 つ以上のリクエストがあります。これは、ほぼすべての人が遭遇する開発の部分であるため、このトピックについて詳しく読み、視聴することをお勧めします。それを理解するために、JavaRush にグループがいくつあるか調べてみましょう。これを行うには、グループ コントローラー グループを展開し、 Get request /api/1.0/rest/groups/countを選択します。JavaRush 内のグループの数が返されます。見てみましょう:「Java プロジェクト A to Z」: 記事のグループを購読する機能を追加します。 パート 1 ~ 2この画像は、このクエリが複数のパラメーター (クエリ、タイプ、フィルター) をサポートしていることを示しています。このリクエストを試すには、[Try it out]ボタンを見つける必要があります。その後、次のパラメータを設定できます。「Java プロジェクト A to Z」: 記事のグループを購読する機能を追加します。 パート 1 ~ 3そこでタイプ、フィルタ、クエリを設定することもできます (ここが実際に興味深いのです。これは、テキスト検索になります)グループ)。ただし、ここでは制限なしで実行して、JavaRush にグループがいくつあるかを確認してみましょう。これを行うには、「実行」をクリックします。すぐ下に、このリクエストに対する応答 (「サーバー応答」セクション) があります。合計30 の「Java プロジェクト A to Z」: 記事のグループを購読する機能を追加します。 パート 1 ~ 4グループがあることがわかります。このリクエストは 95 ミリ秒で完了し、応答にはいくつかのヘッダーのセットがあります。次に、いくつかのパラメータを設定してみましょう。COMPANY 値と等しい type パラメータを選択し、結果がどのように変化するかを見てみましょう:それらは 4 つあります。これを確認するには? 簡単です。Web サイトにアクセスして記事セクションを見つけ、すべてのグループを選択して、そこに適切なフィルターを追加します ( https://javarush.com/groups/all?type=COMPANY )。はい、確かに、それらは 4 つしかありません。実際には 3 つありますが :Dここまでは問題ありません。ちなみに大学を調べてみるとまだありません。参考までに、Javarush にログインしているブラウザとログインしていないブラウザで filter = MY を設定するとどうなるかを見てみましょう。スワッガーについて詳しくは、ハブレに関するこの記事をご覧ください。「Java プロジェクト A to Z」: 記事のグループを購読する機能を追加します。 パート 1 ~ 5「Java プロジェクト A to Z」: 記事のグループを購読する機能を追加します。 パート 1 ~ 6

グループ用の Javarush API のクライアントの作成

ここで、オープン API に基づいて、リクエストを作成し、レスポンスを受信し、どのオブジェクトが到着するかを正確に認識できる Java クライアントを作成します。また、モデルセクション(ページの一番下)から、Swagger からオブジェクトを取得します。新しいパッケージを作成し、サービス、リポジトリの隣に javarushclient という名前を付けましょう。将来的には、これを Javarush コミュニティ組織内の別のライブラリに移動し、依存関係としてのみ使用する予定です。 まず、http リクエストを作成するためのライブラリである Unitrest を JavaRush API に追加する必要があります。
<dependency>
  <groupId>com.konghq</groupId>
  <artifactId>unirest-java</artifactId>
  <version>${unirest.version}</version>
</dependency>
そして、プロパティ ブロックにバージョンを入力します。
<unirest.version>3.11.01</unirest.version>
依存関係を取得したら、コードの追加を開始できます。JavaRushGroupClient グループのクライアントと JavaRushGroupClientImpl クラスの実装を作成しましょう。ただし、最初に DTO (データ転送オブジェクト)、つまりクライアントに必要なすべてのデータをオブジェクトが運ぶクラスを作成する必要があります。すべてのモデルは Swagger で表示でき、一番下にModelsセクションがあり、そこでモデルを数えることができます。これは、Swagger 内でのGroupDiscussionInfoの外観です。 javarushclient パッケージでdto「Java プロジェクト A to Z」: 記事のグループを購読する機能を追加します。 パート 1 ~ 7パッケージを作成し、そこに Swagger からのデータに基づいて次のクラスを追加します。
  • MeGroupInfoStatus :

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

  • 私グループ情報:

    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;
    }

  • グループ情報タイプ:

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

  • ユーザーディスカッション情報:

    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;
    }

  • GroupVisibilitysdstatus :

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

  • 次に - GroupInfo :

    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;
    }

GroupInfoGroupDiscussionInfo はほぼ完全に同じなので、継承でそれらをリンクしましょう - 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;
}
GroupFilterリクエスト 用のフィルターも必要です。
package com.github.javarushcommunity.jrtb.javarushclient.dto;

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

   UNKNOWN, MY, ALL
}
ID による取得リクエストでは GroupDiscussionInfo が返され、グループのコレクションのリクエストでは GroupInfo と GroupDiscussionInfo の両方を取得できます。リクエストにはタイプ、クエリ、フィルター、オフセット、制限を含めることができるため、別のGroupRequestArgsクラスを作成し、それをビルダー クラスにしましょう (ビルダー パターンが何であるかを読んでください)。
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;
   }
}
グループ数の検索の場合は少し異なります。クエリ、タイプ、フィルターのみがあります。そして、コードを複製したくないようです。同時に、それらを組み合わせ始めると、ビルダーと作業するときに醜いことがわかります。そこで、それらを分離してコードを繰り返すことにしました。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;
   }
}
はい、最後の 2 つのクラスには、クエリを作成するためのマップを準備する PopulateQueries メソッドがあることには触れませんでした (これについては後で説明します)。上で説明したクラスに基づいて、 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);
}
GroupInfo または GroupDiscussionInfo 情報を追加して取得する場合の 2 つの異なるリクエスト。それ以外の場合、これらのクエリは同一であり、唯一の違いは、一方では includeDiscussion フラグが true になり、もう一方では false になることです。したがって、方法は 3 つではなく 4 つありました。それでは実装を始めましょう:
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();
   }


}
すでにおなじみの Value アノテーションを使用して、コンストラクター内の API へのパスを追加します。これは、注釈内の値がプロパティ ファイル内のフィールドに対応することを意味します。したがって、application.properties に新しい行を追加しましょう。
javarush.api.path=https://javarush.com/api/1.0/rest
この値はすべての API クライアントに対して 1 か所にまとめられるようになり、API パスが変更された場合はすぐに更新されます。以前は、顕微鏡で釘を打ち、 Unirest を介して http リクエストからの応答を受け取り、それを文字列に変換し、Jackson を介してこの文字列を解析しました...それは怖くて退屈で、多くの追加作業が必要でした。このライブラリでは、それがどのようなものかを見ることができます。手に入れたらすぐにすべてをリファクタリングします。
このライブラリを更新したい人は誰でも、Unirest ライブラリのツールのみを使用して受信オブジェクトを追加できます。個人的なメッセージを記入するか、ライブラリ自体の新しい問題として書き込んでください。これはあなたにとって実際の実務経験になりますが、気にしません。コードを完全にレビューし、必要に応じてサポートします。
ここで問題は、コードが期待どおりに機能するかどうかです。答えは簡単です。テストを作成する必要があるだけです。何度も言いましたが、開発者はテストを作成できなければなりません。したがって、Swagger UI を使用して、リクエストを送信し、レスポンスを確認し、それらを期待される結果としてテストに置き換えます。グループの数は静的ではなく、変化する可能性があることにすぐに気づいたかもしれません。そして、あなたは正しいです。唯一の問題は、この数値がどれくらいの頻度で変化するかということです。非常にまれであるため、数か月にわたってこの値は静的であると言えます。何か変化があれば、テストを更新します。会議 - 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());
   }
}
テストは以前と同じスタイルで記述されます。リクエストごとにいくつかのテストがあります。この API はすでに最良の方法でテストされていると思うので、すべてをテストすることに意味はありません。

結論

この記事の一環として、グループ用の Java クライアントを JavaRush API に追加しました。彼らが言うように、生きて学びましょう。このクライアントを作成している間、私はクライアントのドキュメントを活用し、クライアントが提供するオブジェクトの操作を便利に利用しました。私が提案した仕事に注目してもらいたい。興味のある方がいらっしゃいましたら、私にプライベートメッセージを書いてください。非常に興味深い経験になると確信しています。これが最初の部分でした。2 つ目では、追加コマンドを直接実装し、(1 つの記事に収まる場合は) ユーザーが購読しているグループのリストを取得する機能を追加します。次に、ボット用のテキストを書く意欲と才能のある方は、PM で私にメッセージを書いてください。私はこの問題の専門家ではないので、助けていただければ幸いです。これらすべてをオープンソース開発として形式化しましょう。それは興味深いものになるでしょう。さて、いつものように、いいね、購読、ベル、プロジェクトに星を付け、コメントを書いて記事を評価してください。
役立つリンク

シリーズのすべてのマテリアルのリストは、この記事の冒頭にあります。

コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION