導入
初めまして、新卒のエンジニアです。
NestJSを使用して個人開発を進めています。
僕は処理をスマートに書くことができない、雑魚エンジニアです。
それを何とかしようと、以下の本を読んだところ、非常に感銘を受けました。
↓amazonのリンクです。
良いコード/悪いコードで学ぶ設計入門 ―保守しやすい 成長し続けるコードの書き方
クラス構造やクラス間の関係性など、なかなかイメージに落とし込むことが難しい範囲を、具体ベースに教えてくれています。これぞ良書!
それから、効率的なコードを書くことに対してモチベーションが爆上がりしています。
TypeScriptで書いたロジックを共通化してみたため、それを共有します。
ただ共通化してみただけですが、Prismaの学習にもなります。
Prisma構文内のキーに変数を用いています。
似た3つのtoggleロジック
まず、本記事で共通化するロジックは以下の機能です。
- いいね機能
- ストック機能
- フォロー機能
他ユーザが投稿したコンテンツに対していいね、ストックする機能と他ユーザをフォローする機能です。
これらの機能には共通点があります。
それは、新しい場合はcreate、重複した場合はdeleteするということです。
例えば、X(旧twitter)を想像してください。
あるツイートをいいねしてみたとします。また再度、そのツイートのいいねボタンを押すと、いいねが外れます。
この処理を表しています。
いいねもストックも同様です。
これを本記事ではtoggleロジックと表現していきます。
これら3つのロジックを共通化していきます。
解決策
ここからはコードベースで記していきます。
クラス構造
like.service.ts
コントローラがユーザからのリクエストを受け取り、サービスはコントローラからリクエスト情報を受け取ります。
import { Injectable, Request } from "@nestjs/common";
import { ToggleEvent } from "src/domain/domain-service/toggle.event";
@Injectable({})
export class LikeService {
constructor(private readonly toggleEvent: ToggleEvent
) { }
private readonly likeModel: string = 'like';
private readonly activeKey: string = 'stamper_id';
private readonly passiveKey: string = 'content_id';
private readonly uniqueKey: string = 'unique_stamper_match_review';
async toggleLikeState(@Request() req, insert_content_id: bigint) {
try {
const userId = req.user.userId;
return this.toggleEvent.actToggle(this.likeModel, this.activeKey, this.passiveKey, this.uniqueKey, userId, insert_content_id);
} catch (error) {
console.error('toggle like state :', error);
throw error;
}
}
}
これは余談ですが、NestJSはモジュールで依存関係を管理しています。
ToggleEventクラスはモジュールで読み込む必要があるため、注意してください。
他のstock, followのサービスクラスも同じ構造です。
渡す引数が多いですが、このような意味があります。
- this.likeModel
- Prisma ORMのモデル名です。likesテーブルとマッピングされています。
- this.activeKey
- 実行したユーザIDのカラム名です。他のstock、followとはカラム名が異なるため、能動的な意味を込めて、activekeyと命名しています。あくまでカラム名です。
- this.passivekey
- activeKeyの逆です。受動的な意味を込めています。いいねやストックはされたコンテンツID、フォローはフォローされたユーザIDです。あくまでカラム名です。
- this.uniqueKey
- テーブルにあるユニークキー名です。2つのカラム複合でユニークキーがあります。このキー名がそれぞれ異なるため、引数で渡しています。
- userId
- ユーザIDです。this.activeKeyカラムに挿入されます。
- passiveId
- 受動側のIDです。コンテンツIDやフォローされたユーザIDなどです。this.passiveKeyカラムに挿入されます。
toggle.event.ts
それでは共通化部分です。
import { Injectable } from "@nestjs/common";
import { PrismaService } from "src/infrastructure/prisma/prisma.service";
@Injectable({})
export class ToggleEvent {
constructor(private readonly prisma: PrismaService) {}
async checkExists(insertDB: string, activeKey: string, passiveKey: string, uniqueKey: string, userId: bigint, passiveId: bigint) {
const existedBool = await this.prisma[insertDB].findUnique({
where: {
[uniqueKey]: {
[activeKey]: userId,
[passiveKey]: passiveId
}
}
});
if (existedBool) {
await this.prisma[insertDB].delete({
where: {
[uniqueKey]: {
[activeKey]: userId,
[passiveKey]: passiveId
}
}
});
return { message: `${uniqueKey} removed` };
} else {
await this.prisma[insertDB].create({
data: {
[activeKey]: userId,
[passiveKey]: passiveId
}
});
return { message: `${uniqueKey} added` };
}
}
}
渡されたイベントごとに、テーブルに同値が存在するか判別、生成または削除を行います。
以下は重複するカラムが存在するか、という箇所です
const existedBool = await this.prisma[insertDB].findUnique({
where: {
[uniqueKey]: {
[activeKey]: userId,
[passiveKey]: passiveId
}
}
});
1行目の[insertDB]
は本当ならこういう書き方をします
const existedBool = await this.prisma.like.findUnique({
ですが、今回はロジックの共通化です。likeだけではなく、stockもfollowも実行する必要があります。そのため、this.prisma[insertDB]
と変数を入れています。
次に注目してほしいのはprisma構文内の[uniqueKey]
です。
prismaは大変良くできており、マイグレーション時点でそれぞれのモデルごとに、メソッドごとの相応しい型付けを勝手に行ってくれます。
likesテーブルのユニークキーはunique_stamper_content
という名前です。
stamper_id
とcontent_id
というカラムがあります。
likeモデルのfinduniqueメソッドでは、以上のキーが必要です。
ですがこれらのキー名は、like、stock、followで異なります。
そこで、各サービスから固有値をキー名で渡し、それを変数としてprisma構文内のキーで呼び出しています。
キー名を変数で、よくできた言語ですねえ。
改善点
サービスファイルから渡す引数が多いことが気になります。
脳筋感が満載です。
次はこれを何とかします。
また、本記事で取り上げたコードはNestJS内で書いています。
NestJSが好きな方はいいね、コメントしてくれると嬉しいです!
同志よ、立ち上がれ 。