In my current application, I have a requirement to create an api GET route "/inactive-users" and it should return users, who were archived ("isArchived"
) and at the same time were inactive for more than a month ("lastVisitedDate"
field should be older than new Date() - 1 month
).
Following layered architecture (controller/service/repository), anemic domain model, how should I do things in this case?
I see 2 possible ways.
1 - Create generic repository method for getting users and pass to it user fields that we need.
@Injectable()
export class UserRepository {
constructor(private readonly prisma: PrismaService) { }
findAll = async ( where: { user: Partial<User>; dateTreshold: Date } ): Promise<User[]> => {
const users = await this.prisma.user.findMany(
{ where: {...user}, lastVisitedDate: { lt: dateTreshold }; // lt - less than
);
return users.map(user => new User(user));
}
}
@Injectable()
export class UserService {
constructor(private readonly userRepository: UserRepository) {}
getInactiveUsers = async () => {
return this.userRepository.findAll(
{ where: {user: {isArchived: true}, dateTreshold: "// calculatedDate //"}
)
}
}
2 - Create repository method exatcly for retrieving inactive users and this method will know what fields it should request.
@Injectable()
export class UserRepository {
constructor(private readonly prisma: PrismaService) { }
getInactiveUsers = async (): Promise<User[]> => {
const users = await this.prisma.user.findMany(
{ where: {isArchived: false, lastVisitedDate: { lt: "// calculatedDate //" }}; // lt - less than
);
return users.map(user => new User(user));
}
}
@Injectable()
export class UserService {
constructor(private readonly userRepository: UserRepository) {}
getInactiveUsers = async () => {
return this.userRepository.getInactiveUsers()
}
}
Which way is better? The 1st one looks good for me because in that case repository doesn't know anything about domain understanding of "inactive" users. But at the same time - it might be quite difficult to build such a responsive method.
The 2nd way is easier to build, but at the same time - it has some "business" logic understanding and knows that inactive users are those who has "isArchived"
equal to false
. And also, this repository method knows about amount of days that we need to use.
Which option should be chosen in this situation? Or maybe there are some other ways to build this things?
The proper way to separate this is such that the repository knows only about data-elements in the repository. That doesn't mean you can't have multiple entry-points into your repository, there may be MANY different queries that all hit the same "tables".
It doesn't mean you need a full QBE like your first method, keep it simple. Have a query that encapsulates the database-layer bits but still asks for the things it needs.
In this case, you should have a signature that passes in the isArchived and lastVisitedDate parameters. Something like QueryUsersByStatusAndLastVisited. That way the repository handles all the bits around retrieving the data, but has no logic about why it's retrieving them. It will be "dumb" with all the intelligent bits encapsulated at the service layer.