dynamodb-toolbox icon indicating copy to clipboard operation
dynamodb-toolbox copied to clipboard

Is There a Recommended Way to Create a Generic Repository in DynamoDB Toolbox?

Open hdrhmd opened this issue 9 months ago • 5 comments

Hello,

I've been using DynamoDB Toolbox extensively for various projects and find it to be a robust solution for working with AWS DynamoDB. In my projects, I generally define a Model and a Repository class for each entity in my database schema. While this approach works well, I notice that there's a lot of repetitive boilerplate code in each repository class that can be shared among all repositories.

To streamline this, I've been attempting to create a generic repository that contains common methods, which can be extended or aggregated in each individual repository. While I've managed to make it work to some extent using TypeScript's ts-ignore and redefining the Query object, I wonder if there is a more elegant or recommended approach to achieve this goal.

Here's a stripped-down example of my current implementation in TypeScript:

interface UserModel {
  // User attributes
}

interface PostModel {
  // Post attributes
}

export const UserEntity = new Entity({
  name: 'User',
  attributes: {
    // ...
  },
  table: Table,
} as const);

export const PostEntity = new Entity({
  name: 'Post',
  attributes: {
    // ...
  },
  table: Table,
} as const);

class GenericRespository<M> {
  constructor(
    private readonly entity: Entity
  ) {}

  async queryOne(pk: string, query?: Query): Promise<M | undefined> {
    const result = await this.entity.query(pk, query);
    if (result?.Items?.length > 0) {
      return result.Items[0] as M;
    }
    return undefined;
  }

  // Other generic methods
}

class UserRepository {
  private readonly genericRepo: GenericRespository<UserModel>;

  constructor() {
    this.genericRepo = new GenericRespository<UserModel>(UserEntity);
  }

  async getByUserId(userId: string): Promise<UserModel | undefined> {
    return await this.genericRepo.queryOne(`userId:${userId}`);
  }

  // ...
}

class PostRepository {
  private readonly genericRepo: GenericRespository<PostModel>;

  constructor() {
    this.genericRepo = new GenericRespository<PostModel>(PostEntity);
  }

  async getByPostId(postId: string): Promise<PostModel | undefined> {
    return await this.genericRepo.queryOne(`postId:${postId}`);
  }

  // ...
}

Would love to get the community's thoughts on this, especially if there's a more idiomatic way to achieve what I am aiming for.

Thank you in advance for your guidance!

hdrhmd avatar Sep 24 '23 06:09 hdrhmd