Однією зі спорних тем в Clean Architecture як на мене це мапери. Сьогодні ми подивимось чому вони необхідні(або ні). Та реалізуємо простий мапер.
Що це і для чого
Почнемо з далека. Clean Architecture пропонує розділяти наш додаток на різні шари. І ці шари повинні бути максимально незалежні один від одного. Настільки незалежні що мають різні сущності для одних і тих самих обєктів в різних шарах.
Тобто якщо в нас є два шари, перший data layer а другий service layer. То в кожного з них буде окремий об’єкт для роботи
interface IServiceUser {
id: string;
name: string;
}
interface IDataUser {
_id: string;
name: string;
}
І ось тут ми вже бачимо проблеми. Як бути якщо поля в різних шарах у об’єкта повинні бути різні. Наприклад ми хочемо щоб данні зберігались в іншому вигляді ніж ми з ними працюємо.
Мапери
Ось тут ми і зустрічаємось з концепцією маперів. Мапер - це клас який надає дві функції для трансформації з одної форми в іншу.
Ось як би виглядав мапер для наших інтерфейсів
class UserMapper {
from(entity: IServiceUser): IDataUser {
return {
_id: entity.id,
name: entity.name
}
}
to(entity: IDataUser): IServiceUser {
return {
id: entity._id,
name: entity.name
}
}
}
Ніби все просто? Так воно і є. Концепт не є дуже складним. Тепер ми можемо використовувати цей клас для того щоб не турбуватись і однією функцією перетворювати об’єкт в нужний інтерфейс
Які проблеми?
Перше, очевидно такий підхід змущує нас писати додатковий код. Якщо ми пишемо не величкий додаток то це достатньо вагомо. А також якщо ми розділяємо різними інтерфейсами кожен з шарів то в звичайному Clean Architecture додатку який має 3 шара, ми будемо мати 3 інтерфейса і 2 мапера…..
…які треба підтримувати. По друге, важкість підтримування. Вам потрібно завжди тримати в голові що якщо ви змінюєте якись з інтерфейсів вам потрібно перевірити коректність роботи маперів.
Так а нашо вони потрібні якщо стільки мінусів?
Складне питання. Питання чи варто вводити мапери доволі хитке і залежить від ситуації. Моя рекомендація: якщо можно уникнути маперів, їх треба уникати. Але якщо для вашого сервісу важливо не залежити від жодних змін зовні і зберігати незалежність то варто їх додати.
Хто повинен викликати мапери?
Мапери повинен викликати той сервіс який залежить від другого. Тобто якщо сервіс А залежить від сервісу Б, то на його відповідальності перетворити обєкти через мапер на прийнятні для сервісу Б і передати їх. А потім відповідь знов ж таки перетворити за допомогою маперів на прийнятні для себе
class A {
constructor(private B: Service, private mapper: Mapper) {}
func(user: IUser) {
const dataUser = this.mapper.to(user)
const resultUser = this.B.someFunction(dataUser)
const serviceUser = this.mapper.from(resultUser)
}
}
Висновки
Висновки як завжди прозаічні. Мапери це той достатньо простий концепт який можно використовувати для відділення сущностей різних шарів один від одного. Я би радив уникати їх використання коли це можливо. Але знати як їх використовувати корисно