У в нашей CRM системе есть возможность сортировать учеников по различным параметрам, таким как: возраст, пол, имя, фамилия, отчество, учителя, группы.
И на данный момент у всего этого такая реализация: клиент отправляет на сервер параметры фильтрации в формате такого DTO
export class FilterDTO {
@IsOptional()
@IsString({ each: true })
names?: string[];
@IsOptional()
@IsString({ each: true })
surnames?: string[];
@IsOptional()
@IsString({ each: true })
midnames?: string[];
@IsOptional()
@IsArray()
ages?: number[];
@IsOptional()
@IsString({ each: true })
gender?: string[];
@IsOptional()
@IsString({ each: true })
groups?: string[];
@IsOptional()
@IsString({ each: true })
tutors?: string[];
@IsOptional()
@IsObject()
balance?: { $gte?: number; $lte?: number; $lt?: number };
@IsOptional()
@IsBoolean()
emptyAge?: boolean;
@IsOptional()
@IsString({ each: true })
@IsMongoId({ each: true })
statuses?: string[];
}
И когда все эти параметры прилетают на сервер, отдельная огромная функция строит aggregation pipeline
private createFilterPipeline(filters: FilterDTO): any[] {
if (!filters) return;
const pipeline = [];
if (filters.names) {
const nameFilter = {
$match: {
$or: []
}
};
filters.names.forEach(name => {
nameFilter.$match.$or.push({
name: { $regex: new RegExp(`${name}`, 'i') }
});
});
pipeline.push(nameFilter);
}
if (filters.surnames) {
const surnameFilter = {
$match: {
$or: []
}
};
filters.surnames.forEach(surname => {
surnameFilter.$match.$or.push({
surname: { $regex: new RegExp(`${surname}`, 'i') }
});
});
pipeline.push(surnameFilter);
}
if (filters.midnames) {
const midnameFilter = {
$match: {
$or: []
}
};
filters.midnames.forEach(midname => {
midnameFilter.$match.$or.push({
midname: { $regex: new RegExp(`${midname}`, 'i') }
});
});
pipeline.push(midnameFilter);
}
if (filters.balance) {
if (filters.balance.$lte) {
pipeline.push({
$match: {
$and: [
{
balance: {
$gte: filters.balance.$gte
}
},
{
balance: {
$lte: filters.balance.$lte
}
}
]
}
});
} else {
pipeline.push({
$match: {
$or: [
{
balance: {
$gte: filters.balance.$gte
}
},
{
balance: {
$lt: filters.balance.$lt
}
}
]
}
});
}
}
if (filters.gender) {
pipeline.push({
$match: {
gender: {
$in: filters.gender
}
}
});
}
if (filters.groups) {
pipeline.push({
$match: {
groups: {
$all: filters.groups
}
}
});
}
if (filters.ages) {
const agesFilter = {
$match: {
$or: []
}
};
if (filters.emptyAge === true) {
agesFilter.$match.$or.push({ dateOfBirth: null });
}
filters?.ages?.forEach(age => {
agesFilter.$match.$or.push({
dateOfBirth: {
$gte: new Date(
moment()
.subtract(age + 1, 'years')
.add(1, 'day')
.toISOString()
),
$lt: new Date(
moment().subtract(age, 'years').toISOString()
)
}
});
});
pipeline.push(agesFilter);
} else if (!filters.ages && filters.emptyAge === true) {
pipeline.push({ $match: { dateOfBirth: null } });
}
if (filters.tutors) {
pipeline.push({
$match: {
'tutors.tutor': { $in: filters.tutors }
}
});
}
if (filters.statuses) {
pipeline.push({
$match: {
statuses: {
$all: filters.statuses.map(status =>
Types.ObjectId(status)
)
}
}
});
}
return pipeline;
}
}
Такой подход крайне не удобен, но на момент написания этого метода, ничего лучше я не придумал/не нашёл.
Сейчас проект находится в состоянии рефакторинга, и я очень хочу убрать эту функцию и заменить её на что-нибудь более лаконичное. Какие варианты правильной реализации подобного поведения существуют? Может быть есть какие-то паттерны проектирования? Тут, если я правильно понимаю, очень напрашивается builder, но как тогда организовать приём данных для сортировки с клиента?