domain-driven-hexagon icon indicating copy to clipboard operation
domain-driven-hexagon copied to clipboard

Relationship between `UserRepository` and `TypeormRepositoryBase`

Open estrehle opened this issue 2 years ago • 5 comments

While working through your database implementation, I was wondering why UserRepository does not use TypeormRepositoryBase.findOne?

https://github.com/Sairyss/domain-driven-hexagon/blob/0b55c3c290f197c920cb563add62e04f83faae96/src/modules/user/database/user.repository.ts#L35-L43

Shouldn't we do this instead:

const emailVO = new Email(email);
const user = await this.findOne({ email: emailVO });
return user;

And a second question: It seems like UserRepository.prepareQuery removes all query parameters except for id? Why?

https://github.com/Sairyss/domain-driven-hexagon/blob/0b55c3c290f197c920cb563add62e04f83faae96/src/modules/user/database/user.repository.ts#L61-L70

estrehle avatar Jun 10 '21 09:06 estrehle

  1. This is just an example to show that you can use TypeOrm repos if you have specific queries that can't be simply queried using this.findOne(). In this case either one works, choose any that you like more.
  2. prepareQuery is meant for constructing a query from value objects, since you need to extract values from them. In this example use case we allow querying users only by ID. If you want to allow querying users by, lets say, country, you'd have to add
if (params.address?.country) {
  where.country = params.address.country
}

Keep in mind that this is an example implementation and might not be the best solution for all use cases.

Sairyss avatar Jun 10 '21 10:06 Sairyss

Thank you!

To me it feels like in prepareQuery I would have to duplicate most of the logic from OrmMapper.toOrmProps, only that each line will be wrapped in a if (params...) { ... } clause. Maybe there is a way to re-use the logic from the mapper and use a more abstract if clause?

estrehle avatar Jun 10 '21 10:06 estrehle

The problem is that mapper requires all (or almost all) fields to be present to save the entity, but in a query all fields are optional. Also a query may have different forms, not just simple ones like discussed here. Here is an example of a prepareQuery from one of my projects:

      ...
      if (status) {
        receiver.status = status;
        sender.status = status;
      }
      if (params?.createdBefore) {
        receiver.createdAt = LessThanOrEqual(params.createdBefore);
        sender.createdAt = LessThanOrEqual(params.createdBefore);
      }
     return [sender, receiver];

As you can see this would be impossible to map just by using a mapper, since you can have operators in your query like <= or >=, or you may need to query in a joined tables (like sender and receiver in an example above)

Sairyss avatar Jun 10 '21 10:06 Sairyss

Ah, I see! Now I understand why one would want a more flexible way of preparing queries.

My worry, however, is that this implementation suggests a generality that is not there. The repository exposes a method

findOne(params: QueryParams<EntityProps>)

where

type QueryParams<EntityProps> = DeepPartial<BaseEntityProps & EntityProps>

To anyone who has not studied prepareQuery in detail, this looks like a method that allows one to search the repository using arbitrary combinations of entity properties.

But when I call, say, UserRepository.findOne({ country: 'Germany' }) then the country param will be silently deleted and the result will be the same as if I had called UserRepository.findOne({ }). That could lead to nasty bugs.

My "solution" was to remove the find... methods (except for findById, which works the same for every entity) from TypeormRepositoryBase and resort to custom implementations in each repository, in the spirit of UserRepository.findOneByCountry(country: string).

estrehle avatar Jun 10 '21 12:06 estrehle

That's right, you have to adjust either a type to accept only the parameters specified in prepareQuery, or modify Repository to your liking. I'll think how to improve it in the future so it's less confusing.

Sairyss avatar Jun 10 '21 13:06 Sairyss