1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2024-01-06

はじめに

前回記事の続きです。
ディレクトリ構成、リファクタリングを実施しました。
DDDっぽく集約しています。

▪️シリーズ
TypeScript×Firebase(Step1:プロジェクト作成~データ取得まで)
TypeScript×Firebase(Step2:CRUD処理)
TypeScript×Firebase(Step3:ディレクトリ修正、リファクタリング)←いまここ

ソースコード

ディレクトリ構成
~/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

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

domainディレクトリ

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

2 directories, 2 files

domainディレクトリの階層です。

src/domain/users/model.ts
export type IUser = {
  name: string;
  email: string;
};

→Firestoreのドキュメントの型を定義。

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を活用し、オブジェクトや関数を定義します。

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の接続情報の定義。

src/repository/firestore/constants/document.ts
export const USERS_DOCUMENT_NAME = 'users';

→Firestoreのドキュメントを定義します。
現在は接続ドキュメントが1つのため、1つのみ定義しています。
ドキュメントの増加と共に定義を増やしていきます。

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

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

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

上記が実装クラスで利用する関数になります。

main.ts

既に部品としては完了しています。最後に実際に動かした例をmain.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);
})();

実行結果
~/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っぽく記載する事ができました。
見通しが良くなり、保守性が高まったので是非試してみてください!!!!

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?