Что такое паттерн Anti-Corruption Layer (ACL)?

Ответ

Паттерн Anti-Corruption Layer (ACL) — это архитектурный паттерн, который используется при интеграции двух или более систем с разными моделями данных или технологиями. ACL выступает в роли изолирующего слоя-переводчика между ними.

Основная задача — защитить модель данных новой (или основной) системы от влияния и "загрязнения" со стороны устаревшей (legacy) или внешней системы. ACL преобразует данные из формата одной системы в формат, понятный другой.

Ключевые компоненты ACL:

  • Адаптер (Adapter): Получает данные из внешней системы.
  • Транслятор (Translator): Преобразует модели данных.
  • Фасад (Facade): Предоставляет упрощенный и понятный интерфейс для основной системы.

Пример на Go:

Представим, что наша новая система работает с пользователями (User), а legacy-система отдает их в другом формате (LegacyUser).

package main

import "strconv"

// Модель данных в legacy-системе
type LegacyUser struct {
    UserID   int
    FullName string
    IsActive bool
}

// Модель данных в нашей новой системе
type User struct {
    ID     string
    Name   string
    Status string
}

// ACL: Слой-адаптер для преобразования данных

// Интерфейс для нашей системы
type UserRepository interface {
    FindByID(id string) (User, error)
}

// Реализация, которая ходит в legacy-систему через ACL
type LegacyUserAdapter struct {
    // здесь может быть клиент для legacy API
}

// Функция-транслятор внутри ACL
func (a *LegacyUserAdapter) translate(legacyUser LegacyUser) User {
    status := "inactive"
    if legacyUser.IsActive {
        status = "active"
    }
    return User{
        ID:     strconv.Itoa(legacyUser.UserID),
        Name:   legacyUser.FullName,
        Status: status,
    }
}

// Метод, который будет вызываться нашей системой
func (a *LegacyUserAdapter) FindByID(id string) (User, error) {
    // 1. Сходить в legacy-систему за данными (симуляция)
    legacyID, _ := strconv.Atoi(id)
    legacyUser := LegacyUser{UserID: legacyID, FullName: "John Doe", IsActive: true}

    // 2. Преобразовать данные с помощью транслятора
    user := a.translate(legacyUser)
    return user, nil
}

Преимущества:

  • Изоляция: Новая система не зависит от сложностей и странностей legacy-кода.
  • Независимое развитие: Команды могут работать над своими системами параллельно.
  • Чистая доменная модель: Модель данных основной системы остается чистой и неискаженной.
  • Упрощение рефакторинга: Позволяет постепенно заменять устаревшую систему, не ломая всё сразу.

Ответ 18+ 🔞

А, слушай, про этот ваш Anti-Corruption Layer! Ну, это ж классика, блядь, когда у тебя в проекте появляется какая-нибудь ёбаная legacy-система, древняя, как говно мамонта.

Представь: у тебя тут всё новенькое, красивое, микросервисы там, облака. А тут выясняется, что без вот этого старого монолита, который писал ещё дядя Вася в 90-х, нихуя не работает. И он там данные свои хранит в каком-то пиздеце: то ли в CSV-файлах, то ли в базе, где вместо boolean стоит 'Y' или 'N', а вместо даты — строка 'ДВАДЦАТЬ ПЕРВОЕ. ПН. ЗАПИСЬ К СТОМАТОЛОГУ'.

И вот твоя новая система, такая чистенькая, должна с этим говном общаться. Так вот, чтобы твою красоту не засрали этими legacy-подходами, и придумали ACL. Это типа такой переводчик-телохранитель, ёпта. Он встаёт между твоим кодом и этим старьём и говорит: «Стоять, блядь! Никаких 'Y' и 'N' в мою доменную модель! Я тут всё преобразую, как надо».

Смотри, как это выглядит в коде. У них там LegacyUser с полями UserID и IsActive. А у нас, у цивилизованных людей, User с ID и Status. ACL этот берёт ихнюю хрень и делает из неё конфетку.

package main

import "strconv"

// Это ихний древний формат, от которого воротит
type LegacyUser struct {
    UserID   int
    FullName string
    IsActive bool // у них тут true/false, а могло бы быть и '1'/'0', блядь
}

// А это наша, родная, чистая модель
type User struct {
    ID     string
    Name   string
    Status string // "active"/"inactive", а не какой-то bool, потому что так захотел архитектор, ёпта
}

// Сам слой-защитник, наш герой
type LegacyUserAdapter struct {
    // тут мог бы быть клиент к ихнему апи, которое падает раз в полчаса
}

// А вот и магия! Функция-переводчик. Берёт говно — делает конфетку.
func (a *LegacyUserAdapter) translate(legacyUser LegacyUser) User {
    status := "inactive"
    if legacyUser.IsActive { // Смотри, блядь, преобразуем bool в человеческую строку!
        status = "active"
    }
    return User{
        ID:     strconv.Itoa(legacyUser.UserID), // int в string, мало ли что им в голову взбредёт
        Name:   legacyUser.FullName,
        Status: status,
    }
}

// А это метод, который уже наша система вызывает. Чисто, красиво.
func (a *LegacyUserAdapter) FindByID(id string) (User, error) {
    // 1. Идём в эту ёбаную legacy-систему (симулируем вызов)
    legacyID, _ := strconv.Atoi(id)
    legacyUser := LegacyUser{UserID: legacyID, FullName: "John Doe", IsActive: true}

    // 2. ПРОКРУСТИ ЧЕРЕЗ ACL! Не пускаем legacy-модель в наш светлый код!
    user := a.translate(legacyUser)
    return user, nil
}

И зачем это всё? А затем, сука, чтобы когда эта legacy-система наконец-то накроется медным тазом (или её перепишут), тебе не пришлось перелопачивать всю свою основную логику. Меняешь только этот один слой-переводчик — и всё, пизда. Независимое развитие, изоляция, чистая доменная модель. Красота, блядь!

В общем, ACL — это как резиновая прослойка между твоим красивым новым миром и миром древнего, вонючего кода. Чтобы при столкновении не разбиться вдребезги.