LoginSignup
13
4

More than 3 years have passed since last update.

TypeORMエンティティの現実的な設計

Posted at

TypeORM便利ですね。
TypeORMはまだ未成熟ですが、それでもTypeORMが便利である最大の理由は、TypeScriptによる強力な静的解析の恩恵を受けられることです。
これにより、Java等の他の静的型言語で実装した場合はユニットテストか実行時エラーでしか検出できなかったエラーを実行時に検出できるようになります。

今回扱うのはいかにもJavaっぽいアノテーションもりもりのエンティティです。ここをうまく扱えなければTypeORMのメリットが半減してしまいます。

案1: よく見かける例

@Entity()
export class User {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column()
  name: string;

  @CreateDateColumn()
  createdAt: Date;
}

サンプルでもっともよく見かける例です。シンプルで分かりやすいのですが、様々な問題点があります。

saveが失敗する

上記の例ではnameは必須フィールドですが、

const user = new User();
UserRepository.save(user);

のようなコードがコンパイルエラーなしで通ってしまい、

strictPropertyInitializationが使えない

TypeScriptのstrictPropertyInitializationは、undefinedでないクラスのフィールドがコンストラクタで初期化されることを保障するための設定です。正直言って神機能です。

class Person {
  name: string;
  age: number; // 後から追記した
  constructor(name: string) {
    this.name = name;
  }
}

上記の例では、コンストラクタでageフィールドが初期化されていないため、

new Person('Taro').age.toString();

などが実行時エラーとなってしまいます。これは危ない。しかしながら、strictPropertyInitializationさえ有効にしていれば、このようなバグを未然に防ぐことができます。

案2: コンストラクタで初期化しちまえ!

こちらの方が安全性が高いでしょうか。

@Entity()
export class User {
  @PrimaryGeneratedColumn('uuid')
  id!: string;

  @Column()
  name: string;

  @CreateDateColumn()
  createdAt!: Date;

  constructor(name: string) {
    this.name = name;
  }
}

この手法の問題点は、コンストラクタの仕様変更が怖くてできないことです。例えば、

constructor(firstName: string, lastName: string);

が、

constructor(lastName: string, firstName: string);

にいつの間にか入れ替わっていたとしたら・・・?恐ろしいバグが発生してしまいます。また、フィールド数が増えたときにコンストラクタが長いとソースコードが読みにくくなってしまうのも辛いところですね。

現実的な解: AutoGeneratedなカラムをOmitしたオブジェクトをObject.assignする

賛否両論あると思いますが今のところこれで落ち着きました。

@Entity()
export class User {
  @PrimaryGeneratedColumn('uuid')
  id!: string;

  @Column()
  name!: string;

  @CreateDateColumn()
  createdAt!: Date;

  constructor(properties: UserInitializationProperties) {
    Object.assign(this, properties);
  }
}

export type UserInitializationProperties = Omit<User, 'id' | 'createdAt'>;

この手法のメリットとしては、

  • 新しいフィールドを追加したとき、何もしなければエラーとなる
    • 追加したフィールドはOmitされないので、コンストラクタの引数に渡すオブジェクトの必須フィールドとなります。
  • Rename Symbolでフィールド名を簡単に変更できる
    • 例えばnameフィールドをnamaeに変更しようと思ったとしても、VSCodeならF2キーを押すだけで簡単に変更できます。幸せ。

「こうしたほうがいいよ!」等ありましたら是非教えていただければと思いますm(_ _)m

13
4
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
13
4