TypeScript×Firebase(Step3:ディレクトリ修正、リファクタリング)

## はじめに

[前回記事](https://qiita.com/koji0705/items/3d6ec00cad126effc930)の続きです。
ディレクトリ構成、リファクタリングを実施しました。
DDDっぽく集約しています。

▪️シリーズ
[TypeScript×Firebase(Step1:プロジェクト作成~データ取得まで)](https://qiita.com/koji0705/items/7321004ed6542e5df5be)
[TypeScript×Firebase(Step2:CRUD処理)](https://qiita.com/koji0705/items/3d6ec00cad126effc930)
[TypeScript×Firebase(Step3:ディレクトリ修正、リファクタリング)](https://qiita.com/koji0705/items/d330911cbcc9fa44b63b)←いまここ

https://github.com/kouji0705/firebase_typescript/tree/refact

### ソースコード
```shell:ディレクトリ構成
~/develop/firebase_typescript$ tree src 
src
├── domain
│   └── users
│       ├── model.ts
│       └── repository.ts
├── main.ts
└── repository
    └── firestore
        ├── constants
        │   └── document.ts
        ├── index.ts
        └── users
            ├── create.ts
            ├── delete.ts
            ├── index.ts
            ├── read.ts
            └── update.ts

7 directories, 10 files
```

:::note info
注目してほしいのが、`domain`と`repository`のディレクトリです。
`domain`はtypeやinterfaceの型を定義。
`repository`は、外部接続するための実装。今回は`firestore`のみのため、1つだけディレクトリを切っていますが、この下にその他の外部接続のディレクトリを切れる様にしています。
:::


### domainディレクトリ
```shell:domainディレクトリ
~/develop/firebase_typescript$ tree src/domain 
src/domain
└── users
    ├── model.ts
    └── repository.ts

2 directories, 2 files
```
domainディレクトリの階層です。

```ts:src/domain/users/model.ts
export type IUser = {
  name: string;
  email: string;
};
```
→Firestoreのドキュメントの型を定義。

```ts:src/domain/users/repository.ts
import { IUser } from './model';

export interface IUserRepository {
  findById(id: string): Promise<IUser | null>;
  getUsers(): Promise<IUser[]>;
  createNewUser(user: IUser): Promise<void>;
  updateUser(docId: string, user: IUser): Promise<void>;
  deleteUser(docId: string): Promise<void>;
}
```
→Firestoreと疎通時のInterfaceです。
外部接続のInterfaceを定義する事で見通しが良くなり、テストも実施しやすくなります。

### repositoryディレクトリ
外部接続するFirestoreの定義を実装。
ここでは、domain層で定義したInterfaceを活用し、オブジェクトや関数を定義します。

```ts:src/repository/firestore/index.ts
import { initializeApp } from 'firebase/app';
import { getFirestore } from 'firebase/firestore';
import * as dotenv from 'dotenv';
dotenv.config();

const firebaseConfig = {
  apiKey: process.env.FIREBASE_API_KEY,
  authDomain: process.env.FIREBASE_AUTH_DOMAIN,
  projectId: process.env.FIREBASE_PROJECT_ID,
  storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.FIREBASE_APP_ID,
};

export const app = initializeApp(firebaseConfig);
export const db = getFirestore(app);

```
→Firebaseの接続情報の定義。

```ts:src/repository/firestore/constants/document.ts
export const USERS_DOCUMENT_NAME = 'users';
```
→Firestoreのドキュメントを定義します。
現在は接続ドキュメントが1つのため、1つのみ定義しています。
ドキュメントの増加と共に定義を増やしていきます。


```ts:src/repository/firestore/users/index.ts
import { Firestore } from 'firebase/firestore';
import { IUserRepository } from '../../../domain/users/repository';
import { IUser } from '../../../domain/users/model';
import { findById, getUsers } from './read';
import { createNewUser } from './create';
import { updateUser } from './update';
import { deleteUser } from './delete';

export class UserRepository implements IUserRepository {
  constructor(private db: Firestore) {}
  async createNewUser(user: IUser): Promise<void> {
    return createNewUser(this.db, user);
  }
  async updateUser(docId: string, user: IUser): Promise<void> {
    return updateUser(this.db, docId, user);
  }
  async deleteUser(docId: string): Promise<void> {
    return deleteUser(this.db, docId);
  }
  async findById(id: string): Promise<IUser | null> {
    return findById(this.db, id);
  }
  async getUsers(): Promise<IUser[]> {
    return getUsers(this.db);
  }
}

```
:::note info
Firestoreの実装クラスです。`repository.ts`の`IUserRepository`のInterfaceを元に実装しています。
実際の関数をオブジェクト内で記載すると見通しが悪くなるため、外部で定義した実装内容を呼び出しています。
:::

```ts:src/repository/firestore/users/create.ts
import { Firestore, addDoc, collection } from 'firebase/firestore';
import { USERS_DOCUMENT_NAME } from '../constants/document';
import { IUser } from '../../../domain/users/model';

// 新しいユーザーを作成する
export async function createNewUser(db: Firestore, user: IUser) {
  try {
    const docRef = await addDoc(collection(db, USERS_DOCUMENT_NAME), user);
    console.log('ドキュメント作成成功！ドキュメントID: ', docRef.id);
  } catch (e) {
    console.error('ドキュメント作成エラー: ', e);
  }
}
```

```ts:src/repository/firestore/users/read.ts
import { deleteApp } from 'firebase/app';
import {
  Firestore,
  collection,
  doc,
  getDoc,
  getDocs,
} from 'firebase/firestore';
import { IUser } from '../../../domain/users/model';
import { USERS_DOCUMENT_NAME } from '../constants/document';

// コレクションの全ドキュメントを取得する
export async function getUsers(db: Firestore): Promise<IUser[]> {
  const querySnapshot = await getDocs(collection(db, USERS_DOCUMENT_NAME));
  return querySnapshot.docs.map((doc) => doc.data()) as IUser[];
}

// コレクション内の対象ドキュメントを取得する
export async function findById(
  db: Firestore,
  docId: string
): Promise<IUser | null> {
  const docRef = doc(db, USERS_DOCUMENT_NAME, docId);
  const docSnap = await getDoc(docRef);

  if (docSnap.exists()) {
    return docSnap.data() as IUser;
  } else {
    return null;
  }
}
```

```ts:src/repository/firestore/users/update.ts
import { Firestore, doc, updateDoc } from 'firebase/firestore';
import { USERS_DOCUMENT_NAME } from '../constants/document';
import { IUser } from '../../../domain/users/model';

export async function updateUser(
  db: Firestore,
  docId: string,
  user: IUser
): Promise<void> {
  const docRef = doc(db, USERS_DOCUMENT_NAME, docId);

  await updateDoc(docRef, user);
}
```

```ts:src/repository/firestore/users/delete.ts
import { doc, deleteDoc, Firestore } from 'firebase/firestore';
import { USERS_DOCUMENT_NAME } from '../constants/document';

export async function deleteUser(db: Firestore, docId: string) {
  const docRef = doc(db, USERS_DOCUMENT_NAME, docId);

  try {
    await deleteDoc(docRef);
    console.log('ドキュメント削除成功！');
  } catch (e) {
    console.error('ドキュメント削除エラー: ', e);
  }
}
```
:::note info
上記が実装クラスで利用する関数になります。
:::


## main.ts
既に部品としては完了しています。最後に実際に動かした例をmain.tsに記載してます。

```ts:src/main.ts
import { IUser } from './domain/users/model';
import { db } from './repository/firestore';
import { UserRepository } from './repository/firestore/users';

(async () => {
  const userRepository = new UserRepository(db);
  const docId = 'sxsUyqyyuVIK4sfVktne';
  const user = await userRepository.findById(docId);
  console.log(user);
  const users = await userRepository.getUsers();
  const newUser: IUser = {
    name: 'newUser',
    email: 'oo@gmail.com',
  };
  await userRepository.createNewUser(newUser);
})();

```

```shell:実行結果
~/develop/firebase_typescript$ ts-node src/main.ts
{ name: 'koji', email: 'koji@gmail.com' }
=======users  [
  { email: 'oo@gmail.com', name: 'newUser' },
  { name: 'newUser', email: 'oo@gmail.com' },
  { name: 'newUser', email: 'oo@gmail.com' },
  { email: 'koji@gmail.com', name: 'koji' },
  { email: 'oo@gmail.com', name: 'newUser' }
]
ドキュメント作成成功！ドキュメントID:  Q5Lrbi99HQ9fnrHDSQwp
```

## まとめ
今回はディレクトリ、interfaceを上手く活用する事でDDDっぽく記載する事ができました。
見通しが良くなり、保守性が高まったので是非試してみてください！！！！