Мапери, або як написати багато коду заради чистої архітетури

Однією зі спорних тем в 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)
  }

}

Висновки

Висновки як завжди прозаічні. Мапери це той достатньо простий концепт який можно використовувати для відділення сущностей різних шарів один від одного. Я би радив уникати їх використання коли це можливо. Але знати як їх використовувати корисно

Поділись своїми ідеями в новій публікації.
Ми чекаємо саме на твій довгочит!
Владислав Лєсной
Владислав Лєсной@unnsin

563Прочитань
4Автори
17Читачі
На Друкарні з 15 квітня

Більше від автора

  • Clear Architecture для бекенда на JS

    Кожен хто займається розробкою певний час приходить до слова архітектура. Сьогодні ми подивимось на одну з парадигм проектування архітектури через призму типового JS бекенду.

    Теми цього довгочиту:

    Clean Architecture
  • Паттерн “Репозиторій“ в JS

    В попередній статті ми розібрались з паттерном Специфікація Сьогодні ми поговоримо про логічне продовження паттерн репозиторій.

    Теми цього довгочиту:

    Js
  • Паттерн “Специфікація“ в JS чи потрібен він?

    Одним з найскладніших для мене був паттерн специфікація. Сьогодні я вам пояснити для чого він. І запропонувати свою версію реалізацію

    Теми цього довгочиту:

    Js

Вам також сподобається

Коментарі (3)

Цікаво, дякую!

dovgochyt request:

Що робити коли ти отримав об’єкт типу User і тепер хочеш цьому юзеру додати новий філд. Наприклад age:32

У юзера такого поля немає, але є інтерфейс умовно UserWIthAge;

Як з юзера зробити тепер UserWIthAge. Може я тупий, але я роблю щось типу:

Вам також сподобається