Skip to content

Latest commit

 

History

History
205 lines (170 loc) · 6 KB

backend.md

File metadata and controls

205 lines (170 loc) · 6 KB

Backend

バックエンド・データベースの実装・レビューを行う人向け。

Contribution guide

Nest

Nest Service Circular dependency / Nestでサービスの循環参照でエラーが起きた場合

forwardRef

まずは簡単にforwardRefを試してみる

export class FooService {
	constructor(
		@Inject(forwardRef(() => BarService))
		private barService: BarService
	) {
	}
}

OnModuleInit

できなければOnModuleInitを使う

moduleRef.get(BarService.name) は文字列トークンで解決するため、BarService の通常の登録に加え、同じモジュールの providers に alias を登録する必要がある。Misskey では CoreModule に登録している。

providers: [
	BarService,
	{ provide: BarService.name, useExisting: BarService },
],
import { Injectable, OnModuleInit } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import { BarService } from '@/core/BarService';

@Injectable()
export class FooService implements OnModuleInit {
	private barService: BarService // constructorから移動してくる

	constructor(
		private moduleRef: ModuleRef,
	) {
	}

	async onModuleInit() {
		this.barService = this.moduleRef.get(BarService.name);
	}

	public async niceMethod() {
		return await this.barService.incredibleMethod({ hoge: 'fuga' });
	}
}
Service Unit Test

テストでonModuleInitを呼び出す必要がある

// import ...

describe('test', () => {
	let app: TestingModule;
	let fooService: FooService; // for test case
	let barService: BarService; // for test case

	beforeEach(async () => {
		app = await Test.createTestingModule({
			imports: ...,
			providers: [
				FooService,
				{ // mockする (mockは必須ではないかもしれない)
					provide: BarService,
					useFactory: () => ({
						incredibleMethod: jest.fn(),
					}),
				},
				{ // Provideにする
					provide: BarService.name,
					useExisting: BarService,
				},
			],
		})
			.useMocker(...
			.compile();

		fooService = app.get<FooService>(FooService);
		barService = app.get<BarService>(BarService) as jest.Mocked<BarService>;

		// onModuleInitを実行する
		await fooService.onModuleInit();
	});

	test('nice', async () => {
		await fooService.niceMethod();

		expect(barService.incredibleMethod).toHaveBeenCalled();
		expect(barService.incredibleMethod.mock.lastCall![0])
			.toEqual({ hoge: 'fuga' });
	});
})

Misskey.jsの型生成

pnpm build-misskey-js-with-types

SQL / TypeORM

INSERTするときにはsaveではなくinsertを使用する

#6441

placeholder

SQLをクエリビルダで組み立てる際、使用するプレースホルダは重複してはならない 例えば

query.andWhere(new Brackets(qb => {
	for (const type of ps.fileType) {
		qb.orWhere(`:type = ANY(note.attachedFileTypes)`, { type: type });
	}
}));

と書くと、ループ中でtypeというプレースホルダが複数回使われてしまいおかしくなる だから次のようにする必要がある

query.andWhere(new Brackets(qb => {
	for (const type of ps.fileType) {
		const i = ps.fileType.indexOf(type);
		qb.orWhere(`:type${i} = ANY(note.attachedFileTypes)`, { [`type${i}`]: type });
	}
}));

Not null in TypeORM

const foo = await Foos.findOne({
	bar: Not(null)
});

のようなクエリ(barがnullではない)は期待通りに動作しない。 次のようにします:

const foo = await Foos.findOne({
	bar: Not(IsNull())
});

null in SQL

SQLを発行する際、パラメータがnullになる可能性のある場合はSQL文を出し分けなければならない 例えば

query.where('file.folderId = :folderId', { folderId: ps.folderId });

という処理で、ps.folderIdがnullだと結果的にfile.folderId = nullのようなクエリが発行されてしまい、これは正しいSQLではないので期待した結果が得られない だから次のようにする必要がある

if (ps.folderId) {
	query.where('file.folderId = :folderId', { folderId: ps.folderId });
} else {
	query.where('file.folderId IS NULL');
}

[] in SQL

SQLを発行する際、INのパラメータが[](空の配列)になる可能性のある場合はSQL文を出し分けなければならない 例えば

const users = await Users.find({
	id: In(userIds)
});

という処理で、userIdsが[]だと結果的にuser.id IN ()のようなクエリが発行されてしまい、これは正しいSQLではないので期待した結果が得られない だから次のようにする必要がある

const users = userIds.length > 0 ? await Users.find({
	id: In(userIds)
}) : [];

配列のインデックス in SQL

SQLでは配列のインデックスは1始まり。 [a, b, c]の aにアクセスしたいなら[0]ではなく[1]と書く

null IN

nullが含まれる可能性のあるカラムにINするときは、そのままだとおかしくなるのでORなどでnullのハンドリングをしよう。

enumの削除は気をつける

enumの列挙の内容の削除は、その値をもつレコードを全て削除しないといけない

削除が重たかったり不可能だったりする場合は、削除しないでおく

Migration作成方法

リポジトリルートから:

pnpm --filter backend exec typeorm migration:generate -d ormconfig.js -o --esm migration/<PascalName>
  • ワークスペースにインストール済みの TypeORM CLI を使う。設定ファイルと出力先は packages/backend/ 基準で解決され、生成ファイルは packages/backend/migration/ に配置される
  • 作成されたスクリプトは不必要な変更を含むため除去してください
  • -o (--outputJs) で JS 形式、--esm で ESM 形式に生成する。Misskey の既存 migration はすべて ESM JS なので両方のオプションが必要