【入門編】Slim3で始める!GAE/JでWebアプリケーション開発 (第1回)

Serviceの作成 (その3)

「記事へのコメント登録」の実装

最後にコメントの投稿処理を実装します。

  • テストケースの作成今回は新しいテストメソッド postCommentTest() を作成しました。
    @Test
    public void postCommentTest() throws Exception {
        // 記事の作成 
        Head head = new Head();
        head.setSubject("初めての記事");
        head.setUsername("ユーザ1");
        head.setPostDate(new Date());
        // 本文の作成 
        Body body = new Body();
        body.setText("初めての本文です。");
        // データストアへ更新 
        service.insert(head, body);
        // 記事一覧の取得 
        List<Head> headList = service.getAll();
        // 投稿後の記事一覧は1件であること 
        assertNotNull(headList);
        assertTrue(headList.size() == 1);
        // 記事を取得 
        Head storedHead = headList.get(0);
        // この記事のコメント一覧を取得 
        List<Comment> commentList = storedHead.getCommentRef().getModelList();
        // ========== assertion start ========== // 
        // コメント投稿前なので0件であること 
        assertNotNull(commentList);
        assertTrue(commentList.isEmpty());
        // ========== assertion end ========== // 
    
        // コメントの作成 
        Comment comment =
            createComment("ユーザ2", new Date(), "コメントの投稿です。");
        // データストアへ更新 
        service.insert(storedHead, comment);
        // 記事の再取得 
        storedHead = service.get(storedHead.getKey());
        // コメント一覧の再取得 
        commentList = storedHead.getCommentRef().getModelList();
        // ========== assertion start ========== // 
        // 1件のコメントが投稿されていること 
        assertNotNull(commentList);
        assertTrue(commentList.size() == 1);
        // 以降、1件の中身のチェック 
        Comment postedComment = commentList.get(0);
        assertEquals(comment.getUsername(), postedComment.getUsername());
        assertEquals(comment.getComment(), postedComment.getComment());
        assertEquals(comment.getPostDate(), postedComment.getPostDate());
        // 主キーであるコメントIDが1であること 
        assertEquals(postedComment.getKey().getId(), 1L);
    
        // コメント件数が1であること 
        assertTrue(storedHead.getLastCommentId() == 1L);
        // 最終コメント日時がコメント投稿日時と同じであること 
        assertEquals(storedHead.getLastCommentDate(), comment.getPostDate());
        // ========== assertion end ========== // 
    }
    
    private Comment createComment(String username, Date postDate, String text) throws Exception {
        // コメントの作成 
        Comment comment = new Comment();
        comment.setUsername(username);
        comment.setComment(text);
        comment.setPostDate(postDate);
        return comment;
    }

    テストの要約

    1. 記事の新規登録
    2. 登録した記事のコメント一覧を取得(コメント投稿前)
    3. 【検査】コメント一覧は0件であること
    4. 記事に対しコメントを新規登録
    5. 再度コメント一覧を取得(コメント投稿後)
    6. 【検査】コメント一覧は1件であること
    7. 【検査】取得した1件のコメントが、先に登録したコメントの内容と完全に一致していること
    8. 【検査】記事のコメント件数が1件であること
    9. 【検査】記事の最終コメント日付が、先に登録したコメントの投稿日時と一致していること
  • 必要な処理の実装続いて、BbsService#insert(Head, Comment)の実装を見てみます。
    public void insert(Head head, Comment comment) throws Exception {
    
        long newCommentId = head.getLastCommentId() + 1L;
    
        Key commentKey = Datastore.createKey(head.getKey(), CommentMeta.get(), newCommentId);
        comment.setKey(commentKey);
        comment.getHeadRef().setModel(head);
    
        head.setLastCommentId(newCommentId);
        head.setLastCommentDate(comment.getPostDate());
    
        Transaction tx = Datastore.beginTransaction();
        try {
            Datastore.get(tx, HeadMeta.get(), head.getKey(), head.getVersion());
            Datastore.put(tx, head, comment);
            Datastore.commit(tx);
        }
        catch (Exception e) {
            if (tx.isActive()) {
                Datastore.rollback(tx);
            }
            throw e;
        }
    }

    基本的に「記事の新規登録」のロジックと流れは同じです。少し違うのは、CommentのKeyは採番ではなく、1から始まる連番を id として生成しています。これはアプリ要件的にコメント一覧に”コメント番号”を、記事の見出しに”コメント数”を表示したいためです。

    最新のコメント番号は、Headの lastCommentId プロパティで管理しているので、この値に+1した値でKeyを生成し、それをCommentの主キーとして設定します。

    long newCommentId = head.getLastCommentId() + 1L;
    Key commentKey = Datastore.createKey(head.getKey(), CommentMeta.get(), newCommentId);
    comment.setKey(commentKey);

    また、HeadとCommentはまとめて更新するので、親キーを指定してEGを形成しておきます。

    後のロジックについては「記事の新規登録」とほぼ同じ流れなので割愛します。

  • テストの実施結果がグリーンになればコメントの投稿処理も完成です。

以上でBbsServiceに必要な処理は全て実装完了・・・と言いたいところですが、テストケースに漏れがあったので最後に追加しておきます。

「記事一覧」「コメント一覧」のソート順のテストケースの追加

これまでのテストでは記事、コメントともに1件ずつの登録しかしておらず、一覧の並び順のテストケースが不足していました。

  • テストケースの作成以下、一覧のソート順をそれぞれテストするケースを2つ追加しました。
    // 記事一覧のソート順テスト(投稿日時降順) 
    @Test
    public void headListSortOrderTest() throws Exception {
        // 投稿日付をランダムに並べ記事を順次登録 
        insertHead("題名", "ユーザ", toDate("2010/10/18 15:45:00"), "本文");
        insertHead("題名", "ユーザ", toDate("2011/01/01 00:00:00"), "本文");
        insertHead("題名", "ユーザ", toDate("2011/01/01 12:34:56"), "本文");
        insertHead("題名", "ユーザ", toDate("2011/05/05 10:30:30"), "本文");
        insertHead("題名", "ユーザ", toDate("2010/10/22 15:45:45"), "本文");
        insertHead("題名", "ユーザ", toDate("2012/01/01 23:59:10"), "本文");
        insertHead("題名", "ユーザ", toDate("2011/05/05 11:30:30"), "本文");
        insertHead("題名", "ユーザ", toDate("2010/10/20 15:45:00"), "本文");
        insertHead("題名", "ユーザ", toDate("2010/10/22 15:45:00"), "本文");
        // 一覧の取得 
        List<Head> headList = service.getAll();
        assertNotNull(headList);
        assertTrue(headList.size() == 9);
        // 投稿日付の降順に並んでいるかチェック 
        Head preHead = null;
        for (Head head : headList) {
            if (preHead == null) {
                preHead = head;
                continue;
            }
            // 一件前の日付より古い投稿日付であること 
            assertTrue(head.getPostDate().before(preHead.getPostDate()));
            preHead = head;
        }
    }
    
    // コメント一覧のソート順テスト(コメントID順) 
    @Test
    public void commentListSortOrderTest() throws Exception {
        // 記事の登録 
        insertHead("題名", "ユーザ", new Date(), "本文");
        // 一覧の取得 
        List<Head> headList = service.getAll();
        assertNotNull(headList);
        assertTrue(headList.size() == 1);
        // 記事の取得 
        Head head = headList.get(0);
        // この記事にコメントを1000件投稿してみる 
        int max = 1000;
        for (int i = 0; i < max; i++) {
            Comment newComment = createComment("ユーザ", new Date(), "コメント");
            service.insert(head, newComment);
        }
        // コメント投稿された記事を再取得 
        head = service.get(head.getKey());
        // コメント一覧をリレーションシップで取得 
        List<Comment> commentList = head.getCommentRef().getModelList();
        assertNotNull(commentList);
        assertTrue(commentList.size() == max);
        // コメントID昇順に並んでいるかチェック 
        Comment preComment = null;
        for (Comment comment : commentList) {
            if (preComment == null) {
                preComment = comment;
                continue;
            }
            // 一件前のコメントIDより+1大きいIDであること 
            assertEquals(comment.getKey().getId(), preComment.getKey().getId() + 1L);
            preComment = comment;
        }
        // 記事のコメント数が1000であること 
        assertEquals(head.getLastCommentId(), Long.valueOf(max));
        // 記事の最終コメント日付が最後のコメントの日付と同じであること 
        assertEquals(head.getLastCommentDate(), preComment.getPostDate());
    }
    
    private Date toDate(String yyyyMMddHHmmss) {
        return DateUtil.toDate(yyyyMMddHHmmss, "yyyy/MM/dd HH:mm:ss");
    }
    
    // 記事を新規登録する 
    private void insertHead(String subject, String username, Date postDate, String text) throws Exception {
        // 記事の作成 
        Head head = new Head();
        head.setSubject(subject);
        head.setUsername(username);
        head.setPostDate(postDate);
        // 本文の作成 
        Body body = new Body();
        body.setText(text);
        // データストアへ更新 
        service.insert(head, body);
    }
  • 必要な処理の実装実装済みなのではずなので早速テストを実行してみましょう。
  • テストの実施テスト結果がグリーンなら、BbsServiceはようやく完成です。

作成したソースコードは以下になります。

  • simplebbs.service.bbs.BbsService.java
  • simplebbs.service.bbs.BbsServiceTest.java

以上で、ModelとServiceおよびテストケースの作成は完了です。

いかがでしょうか。Slim3を使えばデータストア周りの実装も思ったより難しくないと感じられたでしょうか。

まだまだサンプルが少ない、と感じた方は是非いろんなテストケースを作って試してみて下さい。手軽にテストケースを作成して試せるのがSlim3のいいところです。

次回は、ViewおよびControllerの実装を行い今回作成したBbsServiceを使って画面表示までを行います。