異なる通知イベントのデータベース表構造


私は、私のうちの1つを造るために出発しましたfavorite projects いくつかの時間は6月ごろ、そして私はある程度までそれを完了し、それをうまく起動することができた間、私はアプリケーションの特定の機能を実装しようとしている間、いくつかの挫折に直面した.私が実装しなければならなかった最も難しい機能の1つは通知でした.この形式のデータをデータベースにモデル化する方法を見つけ出そうとしました.リレーショナルデータベース( PostgreSQL )を使用しています.私が通知モデルに直面した主な問題は、複数のテーブルの特定の行と列を参照する方法で通知を格納するために必要な要件に基づいていました.以下に詳細に説明します.
データベースでは、ここではUser ID(主キー)名、メール、プロファイリングなどの列を含む表
class User extends Model { }

User.init(
  {
    name: {
      type: DataTypes.STRING,
      allowNull: false,
    },
    bio: {
      type: DataTypes.STRING,
      allowNull: false,
      defaultValue: "Hey there!"
    },
    id: {
      type: DataTypes.UUID,
      allowNull: false,
      primaryKey: true,
      defaultValue: DataTypes.UUIDV4,
      unique: true,
    },
    username: {
      type: DataTypes.TEXT,
      allowNull: false,
      unique: true,
    },
    profile_url: {
      type: DataTypes.STRING,
      allowNull: false,
      defaultValue:
        "https://someimagesite.com/default_image.jpg",
    },
    email: {
      type: DataTypes.TEXT,
      allowNull: false,
      unique: true,
    }
  },
  {
    sequelize: instance,
    modelName: "users",
    tableName: "users",
    timestamps: true,
    createdAt: true,
    updatedAt: true,
  }
);


User.hasMany(Post, {
  foreignKey: "userId",
  foreignKeyConstraint: true,
  constraints: true,
  onUpdate: "CASCADE",
  onDelete: "CASCADE",
});


User.hasMany(Comment, {
  foreignKey: "userId",
  foreignKeyConstraint: true,
  constraints: true,
  onUpdate: "CASCADE",
  onDelete: "CASCADE",
});

Post.belongsTo(User)
Comment.belongsTo(User)

B .ここにありますPost id(primary key)、content、title、userid、timestampのような列を含む表."userid "参照をUser 表とその記事の作者を表します.これは、ユーザーの列がユーザーテーブルから削除/更新されるときに、任意のテーブル内の行への他の参照も同様に更新されることを保証するユーザーテーブルの“onupdate : cascasde”と“ondelete :カスケイド”として設定された外部キー制約を持っています.
class Post extends Model { }

Post.init(
  {
    content: {
      type: DataTypes.TEXT,
      allowNull: false,
    },
    id: {
      type: DataTypes.UUID,
      allowNull: false,
      primaryKey: true,
      defaultValue: UUIDV4,
      unique: true,
    },
    userId: {
      type: DataTypes.UUID,
      allowNull: false,
    },
  },
  {
    sequelize: instance,
    modelName: "posts",
    tableName: "posts",
    timestamps: true,
    createdAt: true,
    updatedAt: true,
  }
);
さて、特定のユーザーに通知を表示する必要があると想像してください.
「ちょうどポストを作りました」
上記のサンプル形式では、データベース内の2つのテーブルの属性を参照することができます.
  • a user (ユーザテーブル)のユーザ名
  • B .ポストサムネイル
  • c .記述(ポスト表)
  • d .ユーザのプロフィール写真
  • E .タイムスタンプ(ポスト表)
  • 通知のためのデータベースのテーブルとしてモデル化するには、次の列が必要です.
  • A .ポスティッド
  • b .ユーザID
  • c . targetuserid (通知の受取人)
  • class Notification extends Model {}
    
    Notification.init(
      {
        id: {
          type: DataTypes.UUID,
          allowNull: false,
          primaryKey: true,
          defaultValue: UUIDV4
        },
        postId: {
          type: DataTypes.UUID,
          allowNull: false,
        },
        userId: {
          type: DataTypes.UUID,
          allowNull: false,
        },
        targetUserId: {
          type: DataTypes.UUID,
          allowNull: false,
        },
        isRead: {
            type: DataTypes.BOOLEAN,
            allowNull: false,
            defaultValue: false
        },
      },
      {
        sequelize: instance,
        modelName: "notifications",
        tableName: "notifications",
        timestamps: true,
        createdAt: true,
        updatedAt: true,
      }
    );
    
    
    ここで我々のデータを確実にするために、それが参照するテーブルとNULLデータを避けるために、外部キー制約を追加しますonUpdate: CASCASDE & onDelete: CASCADEuserId , and postId ユーザの列とポストテーブル
    このモデルを使用すると、特定のユーザーによって作成された投稿についての通知をまったく問題なく問い合わせできます.しかし、『gotcha!』これは、ポスト通知のためだけに動作することです.次のイベントの通知が必要な場合は
  • A .ユーザーが投稿の内容に別のユーザーに言及するとき?
  • b .ユーザーが他の誰かの投稿に関するコメントを発行する場合は?
  • c .ユーザーがコメント/回答で別のユーザに言及するとき
  • d .ユーザがポストを好むとき
  • ユーザがコメント/回答が好きなとき
  • これらのイベントを分析すると、各イベントはPOSTとUserを越えて異なるテーブルで特定の列を参照することに気づくでしょう.我々は先に行くことができると“commentid”のようなより多くの属性、“Replyid”の通知モデルに通知するための要件に調整するには、それは私たちのモデルは冗長な列を含んで、デバッグや理解を難しくするようになります.また、実際にはほとんど2つのテーブルでしか参照できない行に対して、いくつかのNULL列を持つこともできます.

    どのように、我々はこの問題を解決しますか?


    私はデータベースに通知を格納するための従来のアプローチを探して、この特定の問題を正確に解決しなかったいくつかを見つけました、しかし、私にこれを解決する方法についての洞察をしました.私は異なった種類の通知のために別々のテーブルを作成したくなかった、特にこれらの通知が年代順に配置されることになっているとき.さて、これのおかげでstackoverflow answer , 私は通知イベントタイプのために別々のテーブルを持っていることができました(例えば、ポスト好き、コメント好き、ポスト言及、コメント言及)、そして、通知のあらゆる形式のために一般的な属性だけを保持する通知テーブル.すべての通知フォームがこれらの属性を持つことが予想されるので、この通知はtimestamp、eventid、acceptのような属性を含みます.それが解決への第一歩だった.したがって、次のようなイベントテーブルがあります.
    class NotificationEvent extends Model {}
    
    NotificationEvent.init(
      {
        id: {
          type: DataTypes.INTEGER,
          allowNull: false,
          primaryKey: true
        },
        type: {
          type: DataTypes.STRING,
          allowNull: false,
        }
      },
      {
        sequelize: instance,
        modelName: "notification_events",
        tableName: "notification_events",
        timestamps: true,
        createdAt: true,
        updatedAt: true,
      }
    );
    
    
    NotificationEvent.hasMany(Notification, {
        foreignKey: "eventId",
        foreignKeyConstraint: true,
        constraints: true,
        onUpdate: "CASCADE",
        onDelete: "CASCADE",
    })
    
    Notification.belongsTo(NotificationEvent, {
      foreignKey: "eventId",
      onDelete: "CASCADE",
      onUpdate: "CASCADE",
      foreignKeyConstraint: true,
      constraints: true
    })
    
    
    それから、私たちの改造された通知モデルは次のようになります.
    class Notification extends Model {}
    
    Notification.init(
      {
        id: {
          type: DataTypes.UUID,
          allowNull: false,
          primaryKey: true,
          defaultValue: UUIDV4
        },
        eventId: {
            type: DataTypes.INTEGER,
            allowNull: false
        },
        targetId: {
          type: DataTypes.UUID,
          allowNull: false,
        },
        isRead: {
            type: DataTypes.BOOLEAN,
            allowNull: false,
            defaultValue: false
        },
      },
      {
        sequelize: instance,
        modelName: "notifications",
        tableName: "notifications",
        timestamps: true,
        createdAt: true,
        updatedAt: true,
      }
    );
    
    上記のイベントの通知形式に戻ると、各イベントのパターンを見ました.
  • (a)は、ユーザとポストテーブルとの間の通知形式である.これはとても簡単ですので、“userid”と“postid”という列が入っているuserPostNotificationテーブルを持つことができます.
  • (b)はコメントとポスト表の通知形式である.ここで必要な主な属性はcommentid、postcommentNotificationテーブルのpostidです.これらの列を使用すると、関連するデータを参照することで簡単に通知メッセージを構築できます.あなたは、コメントが常にユーザーからであるということを知っています、したがって、それが「UserID」と呼ばれるコラムを持つということを知っていて、我々はユーザー名のようなデータを得るためにこのコラムに基づいてユーザー情報を得て、profileurlを得ることができました.また、すべてのポストは特定のユーザを参照する“userid”属性を持っているので、この属性に基づいて投稿を所有している通知を受け取るユーザを得ることもできます.
  • @ usernameがあなたのポストにコメントしたように、我々は現在何かを持つことができます
  • (c)はユーザとコメントテーブルとの間の通知形式である.以下のイメージのような通知メッセージを「userid」と「commentid」の属性だけで構成できます.
  • (d)は、ユーザとポスト表との間の通知の形態でもある.以下の画像のようなメッセージを得るには、userId and postId 属性.
  • (e)は「userid」と「userid」の間の通知の形ですcommentId とメッセージのような@echoeyecodes liked your comment これらの2つの属性を使用して構築できます.
  • これらのパターンを観察した後、私は、ユーザーと投稿、ユーザーとコメント、またはユーザーと回答の間にある通知のための単一のテーブルを持つことが実現しました.これらのテーブルにはid 主キーとして、通知テーブルの行を1対1の関係として参照します.ので、さまざまな通知の種類から様々な属性を年代順にすべての通知をクエリするには、我々はNotification テーブルとNotificationEvents テーブルに加え、UserPostNotification テーブル、およびPostCommentNotification 表.
    また、以降のフォリニキ属性を取得した後に、これらの情報の再問い合わせを行うことなく、制約を共有する親テーブルに基づいて、それぞれのサブテーブルに内部Join節を挿入することもできますUserPostNotification ユーザと投稿の間の外部キー制約を持ちます(userId , postId ), PostCommentNotification POSTとコメントの間に外部キー制約がありますuserId , commentId ).
    ここでどのようにUserPostNotification 以下のようになります:
    class UserPostNotification extends Model {}
    
    UserPostNotification.init(
      {
        notificationId: {
          type: DataTypes.UUID,
          allowNull: false,
          primaryKey: true,
        },
        sourceId: {
          type: DataTypes.UUID,
          allowNull: false
      },
        referenceId: {
            type: DataTypes.UUID,
            allowNull: false
        },
      },
      {
        sequelize: instance,
        modelName: "user_post_notifications",
        tableName: "user_post_notifications",
        timestamps: true,
        createdAt: true,
        updatedAt: true,
      }
    );
    
    PostCommentNotification :
    class PostCommentNotification extends Model {}
    
    PostCommentNotification.init(
      {
        notificationId: {
          type: DataTypes.UUID,
          allowNull: false,
          primaryKey: true,
        },
        sourceId: {
          type: DataTypes.UUID,
          allowNull: false
      },
        referenceId: {
            type: DataTypes.UUID,
            allowNull: false
        },
      },
      {
        sequelize: instance,
        modelName: "post_comment_notifications",
        tableName: "post_comment_notifications",
        timestamps: true,
        createdAt: true,
        updatedAt: true,
      }
    );
    
    特定のユーザに対して利用可能なすべての通知を問い合わせるには、通知テーブルを使って問い合わせを行い、以下のように様々な通知タイプに対して左結合を使用します.
    const NOTIFICATION_INCLUDE_OPTIONS : IncludeOptions[] = [{model: User, required: true},
        {model: NotificationEvent, required: true},
        {model: UserPostNotification, include: [{model: User}]},{model: PostCommentNotification, include: [{model: User}, {model: Comment, include: [{model: User}]}]}]
    
    
    async function getUserNotifications(userId:string(){
       const result = await Promise.all((await Notification.findAll({include: NOTIFICATION_INCLUDE_OPTIONS, where:{targetId: userId}}))
            .filter((item) => {
    //here we filter off notifications with possible all notification type as null. this kind of state cannot possibly exist since every notification object would be of at least one type, but just to be safe, we need this check
                const result = item.get({plain: true}) as NotificationType
                if(!result.user_post_notification && !result.post_comment_notification){
                    return false
                }
                return true
            })
            .map((item) => formatNotification(item.get({plain: true}))))
            return result
    }
    
    最後に、以下のような通知ごとにイベントタイプに基づいて通知メッセージを構築する小さなユーティリティ関数を書くことができます.
    async function formatNotification(instance:NotificationType){
        const type = instance.notification_event.type as NotificationEventType
    
       if(type == "post_comment"){
         //format notification based on requirements e.g @echoeyecodes commented on your post
       }else if(type == "post_create"){
         //format notification based on requirements e.g @echoeyecodes uploaded a photo
    }
    }