新人研修でTwitterライクなアプリを作ってみた

## 1.開発背景

弊社([株式会社アクティブ・ワーク](https://www.active.co.jp/ "株式会社アクティブ・ワークHP"))の新人研修の最終課題としてグループ単位でのアプリ開発を行なった。
当チームではこれまでの研修で学んできたJava、HTML、CSS、JavaScript、SQLなどの技術を用いてTwitterのようなつぶやきを投稿できるSNSアプリを開発した。

## 2.アプリ概要

つぶやきを投稿できるSNSアプリケーション「**たつのこ**」
![スクリーンショット 2023-06-28 14.38.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3486832/3a5557ac-3b5d-5b10-85f3-5198f2e69893.png)


### コンセプト＆特徴
- 140字以内の文章を投稿するウェブアプリ
- 文章だけでなく、画像も投稿することが可能
- DM通話機能とスペース機能がある
- レスポンシブwebデザインによって様々な画面サイズに対応

### 使用画面のイメージ

↓ログイン画面
 ![スクリーンショット 2023-06-27 17.55.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3486832/513ac5e0-3044-6128-35bf-5c1c322d7a70.png)
↓スペース画面(ホスト)
![Screen Shot 2023-06-28 at 16.42.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3486832/bcf4ec92-da1b-804f-e9f3-c80b21ec3bd5.png)
↓スペース画面(リスナー)
![dimension02_tatsunoko_home_ (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3486832/8dd06760-d8e6-c930-3654-0643d801f721.png)

<p>
↓DM画面(スマホ版)　　　　　　　　　　　　　↓ホーム画面(スマホ版)
<img width="320" alt="DM画面" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3486832/c57a8bf7-8179-b0de-2d10-36502bf943b8.png">
<img width="320" alt="プロフィール画面" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3486832/1148f66a-eb7f-c4bd-cb60-49c62d795c01.png">
</p>

### 様々な画面サイズに対応(レスポンシブwebデザイン)
<p>
<img width="100" alt="スマホ" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3486832/d2230634-d9ff-5952-622e-43bc657178a6.png">
<img width="200" alt="タブレット" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3486832/6e56af00-8df1-6e57-86b3-6dceceb09ea8.png">
<img width="340" alt="PC" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3486832/de1053fd-9b1a-1d52-d44c-cd3f0b6d63d9.png">
</p>
スマートフォン　　　　タブレット　　　　　　　　　　　　　PC

## 3.使用技術

**フロント**：　　　　　　HTML、CSS、JavaScript、jQuery v3.3.1、Bootstrap v5.0.2
**サーバーサイド言語**：　Java8
**データベース**：　　　　Oracle
**サーバー構築**：　　　　Tomcat-8
**ソースコード管理**：　　git, GitHub
**使用マシン**：　　　　　Macbook Pro Ventura13.3
**エディタ**：　　　　　　Eclipse2023-03

## 4.機能一覧

- **アカウント関連**
  - アカウント作成・アカウント認証・アカウント情報変更・アカウント削除
  - 凍結・凍結解除
  - ログイン・ログアウト認証

- **ツイート関連**
  - ツイートの投稿・ツイートの削除・ツイート詳細の参照
  - リツイート機能・引用リツイート・リツイート解除・リツイート一覧
  - いいね・いいね解除・いいね一覧
  - リプライ機能
  - インプレッション表示機能

- **TL関連**
  - タイムライン(一覧表示)・タイムラインの更新

- **プロフィール関連**
  - プロフィール(編集・保存機能)
  - 鍵アカウント・非鍵アカウントへ変更
  - フォロー一覧・フォロワー一覧

- **フォロー関連**
  - フォロー・フォロー解除
  - ブロック・ブロック解除

- **検索、通知、スペース、DM**
  - ツイート検索
  - 通知
  - スペース機能・スペース表示
  - DM・DM通話

# 5.設計書

### テーブル定義書

|テーブル名|詳細|
|:--|:--|
|TASUNOKO_USERS|ユーザー情報|
|TEMPORAL_USER|仮ユーザー情報|
|SPASE|スペース情報|
|DIRECT_CALL|ダイレクトメッセージ通話情報|
|DIRECT_MASSAGE|ダイレクトメッセージ情報|
|NOTIFICATION|通知情報|
|TWEET|ツイート詳細情報|
|TWEET_IMG|ツイート画像情報|
|TWEET_RELATION|ツイート関係性情報|
|TIME_LINE|タイムライン|
|BLOCK|ブロック情報|
|FOLLOW|フォロー情報|
|FAVORITE|いいね情報|
|EMAIL|メール情報|
|USER_ID_SEQ|ユーザーIDシークエンス|
|SPASE_ID_SEQ|スペースIDシークエンス|
|DIRECT_MESSAGE_ID_SEQ|ダイレクトメッセージシークエンス|
|TIME_LINE_ID_SEQ|タイムラインIDシークエンス|
|TWEET_ID_SEQ|ツイートIDシークエンス|
|TWEET_IMG_ID_SEQ|ツイート画像IDシークエンス|

### DDL文
<details><summary>DDL文詳細</summary><div>

#### ユーザー情報テーブル
```sql:
CREATE TABLE tatsunoko_user (
user_id NUMBER(6) PRIMARY KEY,
mail_address VARCHAR2(254) NOT NULL,
display_id VARCHAR2(16) UNIQUE NOT NULL,
display_name VARCHAR2(60) NOT NULL,
password CHAR(128) NOT NULL,
header_image_path VARCHAR2(100),
icon_image_path VARCHAR2(100),
biography VARCHAR2(480),
private_flg NUMBER(1) DEFAULT 0 CHECK(private_flg >= 0 AND private_flg <= 1) NOT NULL,
ban_flg NUMBER(1) DEFAULT 0 CHECK(ban_flg >= 0 AND ban_flg <= 1) NOT NULL,
delete_flg NUMBER(1) DEFAULT 0 CHECK(delete_flg >= 0 AND delete_flg <= 1) NOT NULL,
admin_flg NUMBER(1) DEFAULT 0 CHECK(admin_flg >= 0 AND admin_flg <= 1) NOT NULL
);
```
#### 仮ユーザー情報テーブル
```sql:
CREATE TABLE temporal_user (
token CHAR(8) PRIMARY KEY,
mail_address VARCHAR2(254) NOT NULL,
display_id VARCHAR2(16) NOT NULL,
display_name VARCHAR2(60) NOT NULL,
password CHAR(128) NOT NULL,
expiration_date DATE NOT NULL
);
```
#### スペース情報テーブル
```sql:
CREATE TABLE space (
user_id NUMBER(6) REFERENCES tatsunoko_user(user_id) NOT NULL,
space_id NUMBER(6) PRIMARY KEY,
start_date DATE NOT NULL
);
```
#### ダイレクトメッセージ通話情報テーブル
```sql:
CREATE TABLE direct_message (
direct_message_id NUMBER(6) PRIMARY KEY,
sender_id NUMBER(6) REFERENCES tatsunoko_user(user_id) NOT NULL,
receiver_id NUMBER(6) REFERENCES tatsunoko_user(user_id) NOT NULL,
send_date DATE NOT NULL,
content VARCHAR2(440)
);
```
#### ダイレクトメッセージ情報テーブル
```sql:
CREATE TABLE direct_call (
direct_message_id NUMBER(6) REFERENCES direct_message(direct_message_id) UNIQUE,
start_date DATE,
finish_date DATE,
start_waiting_date DATE NOT NULL,
finish_waiting_date DATE
);
```
#### 通知情報テーブル
```sql:
CREATE TABLE notification (
user_id NUMBER(6) REFERENCES tatsunoko_user(user_id) NOT NULL,
content VARCHAR2(80) NOT NULL,
url VARCHAR2(200),
push_date DATE NOT NULL,
read_flg NUMBER(1) DEFAULT 0 CHECK(read_flg >= 0 AND read_flg <= 1) NOT NULL,
sender_user_id NUMBER(6) REFERENCES tatsunoko_user(user_id) ,
tweet_id NUMBER(10) REFERENCES tweet(tweet_id),
type VARACHAR2(10) NOT NULL,
sentence VARCHAR2(200)
);
```
#### ツイート詳細情報テーブル
```sql:
CREATE TABLE tweet (
tweet_id NUMBER(10) PRIMARY KEY,
quote_tweet_id NUMBER(10) REFERENCES tweet(tweet_id),
user_id NUMBER(6) REFERENCES tatsunoko_user(user_id) NOT NULL,
content VARCHAR2(440) NOT NULL,
tweet_date DATE NOT NULL,
favorite_sum NUMBER(6) DEFAULT 0 NOT NULL,
retweet_sum NUMBER(6) DEFAULT 0 NOT NULL,
impression NUMBER(8) DEFAULT 0 NOT NULL,
delete_flg NUMBER(1) DEFAULT 0 CHECK(delete_flg >= 0 AND delete_flg <= 1) NOT NULL
);
```
#### ツイート画像情報テーブル
```sql:
CREATE TABLE tweet_image (
tweet_image_id NUMBER(10) PRIMARY KEY,
tweet_id NUMBER(10) REFERENCES tweet(tweet_id),
tweet_image_path VARCHAR2(100)
);
```
#### ツイート関係性情報テーブル
```sql:
CREATE TABLE tweet_relation (
tweet_id NUMBER(10) REFERENCES tweet(tweet_id) NOT NULL,
descendant_reply_id NUMBER(10) REFERENCES tweet(tweet_id) NOT NULL,
relation_depth NUMBER(3) NOT NULL
);
```
#### タイムラインテーブル
```sql:
CREATE TABLE time_line (
time_line_id NUMBER(12) PRIMARY KEY,
tweet_id NUMBER(10) REFERENCES tweet(tweet_id) NOT NULL,
retweet_flg NUMBER(1) CHECK(retweet_flg >= 0 AND retweet_flg <= 1) NOT NULL,
user_id NUMBER(6) REFERENCES tatsunoko_user(user_id) NOT NULL,
tweet_date DATE NOT NULL,
UNIQUE(tweet_id, retweet_flg, user_id)
);
```
#### ブロック情報テーブル
```sql:
CREATE TABLE block (
user_id NUMBER(6) REFERENCES tatsunoko_user(user_id),
block_user_id NUMBER(6) REFERENCES tatsunoko_user(user_id),
block_date DATE NOT NULL
);
```
#### フォロー情報テーブル
```sql:
CREATE TABLE follow (
user_id NUMBER(6) REFERENCES tatsunoko_user(user_id),
follow_user_id NUMBER(6) REFERENCES tatsunoko_user(user_id),
follow_date DATE NOT NULL
);
```
#### いいね情報テーブル
```sql:
CREATE TABLE favorite (
user_id NUMBER(6) REFERENCES tatsunoko_user(user_id),
tweet_id NUMBER(10) REFERENCES tweet(tweet_id),
favorite_date DATE NOT NULL,
UNIQUE(user_id, tweet_id)
);
```
#### メール情報テーブル
```sql:
CREATE TABLE email (
email_address VARCHAR2(254) NOT NULL,
subject VARCHAR2(60) NOT NULL,
content VARCHAR2(300) NOT NULL,
send_date DATE NOT NULL
);
```
#### ユーザーIDシークエンス
```sql:
CREATE SEQUENCE user_id_seq
START WITH 1
INCREMENT BY 1
MAXVALUE 999999
NOCYCLE;
```
#### スペースIDシークエンス
```sql:
CREATE SEQUENCE space_id_seq
START WITH 1
INCREMENT BY 1
MAXVALUE 999999
NOCYCLE;
```
#### ダイレクトメッセージシークエンス
```sql:
CREATE SEQUENCE direct_message_id_seq
START WITH 1
INCREMENT BY 1
MAXVALUE 999999
NOCYCLE;
```
#### タイムラインIDシークエンス
```sql:
CREATE SEQUENCE time_line_id_seq
START WITH 1
INCREMENT BY 1
MAXVALUE 999999999999
NOCYCLE;
```
#### ツイートIDシークエンス
```sql:
CREATE SEQUENCE tweet_id_seq
START WITH 1
INCREMENT BY 1
MAXVALUE 9999999999
NOCYCLE;
```
#### ツイート画像IDシークエンス
```sql:
CREATE SEQUENCE tweet_image_id_seq
START WITH 1
INCREMENT BY 1
MAXVALUE 9999999999
NOCYCLE;
```
</div></details>

### ツイートの関係について
##### 「ツリー」？「スレッド」？「親子」？
DB設計で一番苦労したのが、「ツイート同士の関係性」をどう表すか、という点。ここでの関係性とは、平たく言えば「リプライ-リプライ先」のこと。
Twitterでは既存のツイートに対してリプライ(返信)ができる。1つのツイートに複数のリプライを付けられるし、リプライにリプライを付けられる。枝状に関係性が広がっていくことになる。

そして、特定のツイートをクリックした際に表示する画面(「ツイート詳細画面」)では、それらの関係性を洗い出し、メインツイートの上下にツリーとして表示させなければいけない。

#### 必要な情報の確認
本家Twitterを参考に、表示する関連ツイートは以下のように定めた。
１．主ツイート
２．主ツイートの「リプ先、リプ先のリプ先…」と遡れるツイート
３．主ツイートの「直接付いたリプライ」

下の画像を模式図として例を挙げてみる。
<img width="300" alt="模式図" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3486832/dfa0511d-e522-e806-9ebf-0959e9011bdd.png">

- 例1：ツイートA詳細画面
表示されるのは「ツイートA・B・C」。
直下のリプライではないD・Eは表示しない。

- 例2：ツイートC詳細画面
表示されるのは「ツイートA・D」。
兄弟関係に当たるツイートB、および直下ではないツイートEは表示しない。

- 例3：ツイートD詳細画面
表示されるのは「ツイートA・C・D・E」。
叔父に当たるツイートBは表示しない。

もちろん、Tweetテーブルに「リプライ先ツイートID(reply_to_tweet_id)」のような列を持たせれば容易に実装はできる。
しかしその場合、例3のような表示をするためには

1. tweetテーブルの全レコードから「WHERE reply_to_tweet_id = D」をSELECT
(ツイートEを取得)
2. ツイートDの「reply_to_tweet_id」を取得
(ツイートCを取得)
3. 「reply_to_tweet_idがあるなら」という条件で、繰り返し処理で遡り続ける
(ツイートAを取得)

…という、ループ処理を含む膨大なクエリを走らせる必要がある。これはユーザーがツイート詳細画面を開くたびに実行される。
これでは流石に応答速度に不安が残るため、外部表的なイメージでこの関係性を管理しようと考えた。

#### 完成した設計
完成したTWEET_RELATION(ツイート関係性テーブル)の設計は以下の列。
1. TWEET_ID(親ツイートID)
2. DESCENDANT_REPLY_ID(子孫リプライID)
3. RELATION_DEPTH(関係性の深度)

具体例として、先ほどの図をテーブルに表してみる。
|TWEET_ID|DESCENDANT_REPLY_ID|RELATION_DEPTH|
|:--|:--|:-|
|A|B|1|
|A|C|1|
|C|D|1|
|A|D|2|
|D|E|1|
|C|E|2|
|A|E|3|

例えばツイートEを送信すると、上記テーブルの下3行の部分だけが挿入される。
(自己結合を使えば再帰的なループも不要)

これなら、前述の作業は以下のようになる。
1. テーブルから「tweet_id = D, relation_depth = 1」をSELECT
(ツイートEを取得)
2. 「DESCENDANT_REPLY_ID」をSELECTし、relation_depthでソート
(ツイートA・Cを取得)
発行クエリ数も3回以上→2回と、かなり簡略化できた。


### 画面遷移図
![スクリーンショット 2023-06-27 18.30.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3486832/126590c5-045e-a116-1eb8-0f454ecad9cb.png)

# 6.開発期間
作業人数：　7人
作業期間：　6/5(月)〜6/28(水)

|作業|日数|参考時間|
|:-:|--:|:-:|
|要件定義|4.5日|7.5h* 4.5日 * 7人 = 236.25h|
|実装|10.5日|7.5h* 10.5日 * 7人 = 551.25h|
|テスト|3日|7.5h* 3日 * 7人 = 157.5h|
|||計945h|

※発表資料・Qiita作成時間を含む
![スクリーンショット 2023-06-29 13.42.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3486832/e9946893-35a6-71ae-4663-c8bd86d0c46c.png)


## 7.振り返り
今回のアプリケーション開発課題を終えての反省をKPT方式でまとめる。

- #### K (keep:続けていくこと)
    - **チーム制の導入**
    7人グループを2・2・3に分けることで、連絡や疑問点の解消がスムーズになった

    - **タスクを難易度で分けて割り振り**
    担当実装箇所を割り振る際に、タスクを難易度で分けることで習熟度の差に見合ったタスクを割り振ることができた

    - **実装についてコードベースの設計をした**
    実装に入る前にどのようなコードを書くか設計し、チーム間でレビューしたことでスムーズに実装を進めることができた

    - **30分ごとに進捗報告会**
      短いスパンで進捗報告をしたことでグループ全体の進捗を共有できた

- #### P (problem:問題点)
    - **相談に時間を取られてしまった**
    迅速に疑問点の解消ができる反面、相談のたびにペアのメンバーの作業を中断させていたので、作業効率が悪くなってしまった

    - **定期的振り返り**
    個人やチーム単位では振り返りをして問題点を改善するサイクルができていたがグループ単位で振り返りをしていなかった
    一週間に一度でも定期的振り返りの機会を設ければより効率的に作業ができた

    - **技術力の高いメンバーの負担が大きかった**
    技術力の高いメンバーにバックエンドからフロントエンド(デザイン面など)まで横断的にタスクが割り振られていたので、他のメンバーがフロントエンドを担当して、技術力の高いメンバーにはバックエンドに集中してもらうべきだった。

    - **追加仕様時間の見積もりが甘かった**
    バッファ込みで余裕を持たせたスケジュールを立てたつもりだったが、仕様の洗い出しが甘かったため、実装段階で追加仕様が増え、想定以上に実装に時間がかかってしまった。

- #### T (try:次からやること)
    - **チャットで相談を受ける**
      個人が集中する時間を作る。その時間中の相談はチャットで送る。
チャットで相談をすることで、要点をまとめて伝えることができ、後から見る方も読みやすい

    - **進捗報告を生かす**
    現在の進捗を報告し合うことで、詰まっている部分を把握したり、タスクを客観視するのが進捗報告の目的だったが、途中からは「今XXやってます、どのくらいかかるかわかりません〜」のように雑になってしまい、手段と目的が逆転してしまっていた
→お互いに報告内容をしっかり聞いて、積極的に声を掛け合うべきだった

## 8.まとめ

技術的な実力がバラバラなメンバー7人から成る「たつのこ」グループでしたが、作りたいアプリケーション決めからDB設計、仕様書作成と初めてやることが多く、実装に入ってからも大幅なスケジュール遅れなどがあり、作り上げる事ができるのか不安な時期もあった。
ただ、今回のアプリケーション開発で実際にサーバーにデプロイして動作するものを作ることができたことは大きな自信に変わった。
今回のアプリケーション開発で学んだことは、「報連相の重要性」である。
７人で別々の作業をしながら、開発をすすめるので報連相を的確に出来なかったことで、認識のすれ違いが起きたり、仕様の共有漏れが発生してしまった。
その一方で、チーム内で相談することでスムーズに実装を進めることができた。
今後も報連相の徹底を意識していきたい。